AOP

1.官方文档

AOP讲解:下载的spring文件-->spring-framework-5.3.8/docs/reference/html/core.html#aop

AOP APIs:下载的spring文件-->spring-framework-5.3.8/docs/reference/html/core.html#aop-api

2.动态代理

2.1案例说明

需求说明:

  1. 有Vehicle(交通工具接口,有一个run方法),下面有两个实现类Car,Ship

  2. 当运行Car对象的run()方法和Ship对象的run()方法时,输出如下内容,注意观察前后有统一的输出


  3. 请思考如何完成?

2.2传统方式解决

Vehicle接口:

package com.li.proxy;

/**
* @author 李
* @version 1.0
* 接口,定义了run方法
*/
public interface Vehicle {
public void run();
}

Ship类,实现Vehicle接口:

package com.li.proxy;

/**
* @author 李
* @version 1.0
*/
public class Ship implements Vehicle { @Override
public void run() {
System.out.println("交通工具开始运行了...");
System.out.println("大轮船在水上 running...");
System.out.println("交通工具停止运行了...");
}
}

Car类,实现Vehicle接口:

package com.li.proxy;

/**
* @author 李
* @version 1.0
*/
public class Car implements Vehicle { @Override
public void run() {
System.out.println("交通工具开始运行了...");
System.out.println("小汽车在公路 running...");
System.out.println("交通工具停止运行了...");
}
}

TestVehicle测试类:

package com.li.proxy;

import org.testng.annotations.Test;

/**
* @author 李
* @version 1.0
*/
public class TestVehicle {
@Test
public void run() {
Vehicle vehicle = new Car();//Vehicle vehicle = new Ship();
vehicle.run();//动态绑定,根据实际运行类型调用run方法
}
}

上面的方式,代码冗余,其实就是单个对象的调用,并没有很好的解决问题。

2.3动态代理方式解决

解决思路:在调用方法的时候,使用反射机制,根据方法去决定调用哪个对象方法

Vehicle接口不变:

package com.li.proxy;

/**
* @author 李
* @version 1.0
* 接口,定义了run方法
*/
public interface Vehicle {
public void run();
}

Ship:

package com.li.proxy;

/**
* @author 李
* @version 1.0
*/
public class Ship implements Vehicle { @Override
public void run() {
System.out.println("大轮船在水上 running...");
}
}

Car:

package com.li.proxy;

/**
* @author 李
* @version 1.0
*/
public class Car implements Vehicle { @Override
public void run() {
System.out.println("小汽车在公路 running...");
}
}

创建VehicleProxyProvider,该类返回一个代理对象:

package com.li.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; /**
* @author 李
* @version 1.0
* VehicleProxyProvider类可以返回一个代理对象
*/
public class VehicleProxyProvider {
//定义一个属性
//target_vehicle 表示真正要执行的对象
//要求该对象的类实现了Vehicle接口
private Vehicle target_vehicle; //构造器
public VehicleProxyProvider(Vehicle target_vehicle) {
this.target_vehicle = target_vehicle;
} //编写一个方法,可以返回一个代理对象
public Vehicle getProxy() {
//(1)得到类加载器
ClassLoader classLoader =
target_vehicle.getClass().getClassLoader(); //(2)得到要代理的对象/被执行的对象 的接口信息,底层通过接口来完成调用
Class<?>[] interfaces = target_vehicle.getClass().getInterfaces(); //(3)创建一个调用处理对象
/**
* public interface InvocationHandler {
* public Object invoke(Object proxy, Method method, Object[] args)
* throws Throwable;
* }
* invoke 方法在将来执行我们的 target_vehicle的方法时,会调用到
*/
//如上,因为InvocationHandler是接口,不能直接实例化
// 以匿名内部类的方式来获取 InvocationHandler 对象
//这个对象有一个方法:invoke, 到时可以通过反射,动态调用目标对象的方法
InvocationHandler invocationHandler = new InvocationHandler() {
/**
* invoke()方法,在将来执行我们的target_vehicle的方法时会调用到
* @param proxy 表示代理对象
* @param method 就是通过代理对象调用方法时,的哪个方法
* @param args 表示调用代理对象的方法时,传入方法的参数
* @return 表示代理对象.方法(xx) 执行后的结果
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("交通工具开始运行了...");
//这个地方的方法,就是你调用时动态传入的,可能是 run , 可能是 hi 等
Object result = method.invoke(target_vehicle, args);
System.out.println("交通工具停止运行了...");
return result;
}
}; /*(4)
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
1.Proxy.newProxyInstance() 可以返回一个代理对象
2.ClassLoader loader :类加载器
3.Class<?>[] interfaces:要代理的对象的接口信息
4.InvocationHandler h:调用处理器/对象,该对象有一个非常重要的方法-invoke
*/
//将上面的 loader, interfaces, invocationHandler构建一个 Vehicle的代理对象.
Vehicle proxy =
(Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); return proxy;
} }

TestVehicle测试类:

@Test
public void proxyRun(){
//创建Ship对象
Vehicle vehicle = new Ship(); //创建VehicleProxyProvider对象,并且传入要代理的对象
VehicleProxyProvider vehicleProxyProvider = new VehicleProxyProvider(vehicle); //获取代理对象,该对象可以代理执行方法
//1.proxy 编译类型是Vehicle
//2.proxy 运行类型是 代理类型: class com.sun.proxy.$Proxy3
Vehicle proxy = vehicleProxyProvider.getProxy();
System.out.println("proxy 的运行类型是="+proxy.getClass()); proxy.run();
}

2.4动态代理机制debug

如上所示,当执行proxy.run();时,成功输出了目标信息,那么代码是怎么执行到代理对象的invoke方法上的?

1.在proxy.run();旁打上断点,点击step into,光标跳转至VehicleProxyProvider的invoke方法:

2.点击step over,光标跳转到下一行,此时method,target_vehicle代表了什么?


如上,此时的method就是我们Vehicle接口中的run方法,target_vehicle对象的实际运行类型是Ship,因为我们没有传值进去所以args为null。

3.在这行点击step into,进入invoke方法的源码,点击step over跳到return ma.invoke(obj, args);

在这行点击step into:

可以看到,底层最终执行了invoke0()方法:

这里可以发现method 就是Vehicle的run方法,该方法会通过动态绑定,根据实际运行对象的类型(在这里即Ship),调用Ship中的run方法;var2就是我们调用代理对象的方法时传入的参数,这里为null:

4.在这行再次点击step into,可以看到光标跳转到了Ship对象的run方法中,此时调用了Ship中的run方法:

5.点击step over,因为底层代码执行完毕。光标层层返回到之前的method.invoke(target_vehicle,args)语句:

6.因为Ship的run方法返回void,因此result的值为null:

7.当执行到return result;时,相当于使用代理对象执行run方法执行完毕,因此返回上一层:

至此,总体的流程就执行完毕了。

总结梳理:

1.proxy的编译类型是Vehicle,因此可以调用run方法;proxy的运行类型是class com.sun.proxy.$Proxy3,所以当执行run方法时会执行到代理对象的invoke方法

2.invoke 方法使用反射机制来调用 run()方法(注意这个 run 方法也可以是Vehicle 的其它方法),这时就可以在调用 run()方法前,进行前置处理和后置处理

3.也就是说 proxy 的 target_vehicle 运行类型只要是实现了 Vehicle 接口,就可以去调用不同的方法,是动态的,变化的,底层就是使用反射完成的。

3.动态代理练习

3.1案例说明

需求说明:

有一个SmartAnimal接口,可以完成简单的加减法,要求在执行getSum()和getSub()时,输出执行前、执行过程、执行后的日志输出(参考如下),请思考如何实现

方法执行开始-日志-方法名-getSub-参数 [10.0, 2.0]
方法内部打印 result = 8.0
方法执行正常结束-日志-方法名-getSub-结果 result= 8.0
//可能的异常信息
方法最终结束-日志-方法名-getSub//finally的输出
======================
方法执行开始-日志-方法名-getSum-参数 [10.0, 2.0]
方法内部打印 result = 12.0
方法执行正常结束-日志-方法名-getSum-结果 result= 12.0
//可能的异常信息
方法最终结束-日志-方法名-getSum//finally的输出
  1. 请使用传统方法完成
  2. 请使用动态代理方式完成,并要求考虑代理对象调用方法(底层是反射调用)时,可能出现的异常

3.2传统方式解决

SmartAnimal接口:

package com.li.aop.proxy2;

/**
* @author 李
* @version 1.0
*/
public interface SmartAnimal {
//求和
float getSum(float a, float b); //求差
float getSub(float a, float b);
}

SmartCat类实现接口SmartAnimal:

package com.li.aop.proxy2;

/**
* @author 李
* @version 1.0
*/
public class SmartCat implements SmartAnimal {
@Override
public float getSum(float a, float b) {
System.out.println("日志-方法名-getSum-参数 " + a + " " + b);
float result = a + b;
System.out.println("方法内部打印 result = " + result);
System.out.println("日志-方法名-getSum-结果result= " + result);
return result;
} @Override
public float getSub(float a, float b) {
System.out.println("日志-方法名-getSub-参数 " + a + " " + b);
float result = a - b;
System.out.println("方法内部打印 result = " + result);
System.out.println("日志-方法名-getSub-结果result= " + result);
return result;
}
}

AopTest测试类:

package com.li.aop.proxy2;

import org.testng.annotations.Test;

/**
* @author 李
* @version 1.0
*/
public class AopTest {
@Test
public void testSmartAnimal() {
SmartAnimal smartAnimal = new SmartCat();
smartAnimal.getSum(10, 2);
System.out.println("=======================");
smartAnimal.getSub(10, 2);
}
}

传统方法的特点:

优点:实现简单直接

缺点:日志代码维护不方便,代码复用性差

解决思路:

  1. 使用动态代理来更好地处理日志记录问题
  2. 其他比如封装函数,或者类的继承在这里都不是特别合适

3.3动态代理方法解决

SmartAnimal接口不变。

SmartDog实现SmartAnimal接口:

package com.li.aop.proxy2;

/**
* @author 李
* @version 1.0
*/
public class SmartDog implements SmartAnimal {
@Override
public float getSum(float a, float b) {
float result = a + b;
System.out.println("方法内部打印 result = " + result);
return result;
} @Override
public float getSub(float a, float b) {
float result = a - b;
System.out.println("方法内部打印 result = " + result);
return result;
}
}

MyProxyProvider类:

package com.li.aop.proxy2;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays; /**
* @author 李
* @version 1.0
* 返回一个动态代理对象,可以执行被代理的对象的方法
*/
public class MyProxyProvider { //定义要执行的目标对象,该对象需要实现 SmartAnimal接口
private SmartAnimal target_animal; //构造器
public MyProxyProvider(SmartAnimal target_animal) {
this.target_animal = target_animal;
} //定义方法返回代理对象,该代理对象可以执行目标对象
public SmartAnimal getProxy() {
//(1)先得到类加载器对象
ClassLoader classLoader = target_animal.getClass().getClassLoader();
//(2)得到要执行的目标对象的接口信息
Class<?>[] interfaces = target_animal.getClass().getInterfaces();
//(3)使用匿名内部类 创建 InvocationHandler对象
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
System.out.println("方法执行开始-日志-方法名-" + method.getName() +
"-参数 " + Arrays.toString(args));//这里从AOP的角度看,就是一个横切关注点-前置通知
//使用反射真正调用方法
result = method.invoke(target_animal, args);
System.out.println("方法执行正常结束-日志-方法名-" + method.getName()
+ "-结果 result = " + result);//也是一个横切关注点-返回通知
} catch (Exception e) {
//如果反射出现异常,就会进入到catch块
System.out.println("方法执行异常-日志-方法名" + method.getName()
+ "-异常类型=" + e.getClass().getName());//也是一个横切关注点-异常通知
e.printStackTrace();
} finally {//无论是否出现异常,最终都会执行到 finally{}
//也是一个横切关注点-最终通知
System.out.println("方法最终结束-日志-方法名-" + method.getName());
}
return result;
}
}; //(4)创建代理对象
SmartAnimal proxy = (SmartAnimal) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}

测试方法:

@Test
public void testSmartAnimal() {
SmartAnimal smartAnimal = new SmartDog(); MyProxyProvider myProxyProvider
= new MyProxyProvider(smartAnimal); SmartAnimal proxy = myProxyProvider.getProxy(); proxy.getSum(10, 2);
System.out.println("=======================");
proxy.getSub(10, 2); }

3.4问题提出

在MyProxyProvider类中,我们的输出语句功能比较弱,在实际开发中,我们希望是以一个方法的形式,嵌入到真正执行的目标方法前,怎么办?

day08-AOP-01的更多相关文章

  1. 初始Spring 文档 完整版

    初始Spring 1.Spring官网 spring.io 01. IOC(Inverse of Control)控制反转 02. AOP(Aspect Oritend Programming)面向切 ...

  2. 04 Spring:01.Spring框架简介&&02.程序间耦合&&03.Spring的 IOC 和 DI&&08.面向切面编程 AOP&&10.Spring中事务控制

    spring共四天 第一天:spring框架的概述以及spring中基于XML的IOC配置 第二天:spring中基于注解的IOC和ioc的案例 第三天:spring中的aop和基于XML以及注解的A ...

  3. Spring AOP学习笔记01:AOP概述

    1. AOP概述 软件开发一直在寻求更加高效.更易维护甚至更易扩展的方式.为了提高开发效率,我们对开发使用的语言进行抽象,走过了从汇编时代到现在各种高级语言繁盛之时期:为了便于维护和扩展,我们对某些相 ...

  4. Spring的IOC和AOP之深剖

    今天,既然讲到了Spring 的IOC和AOP,我们就必须要知道 Spring主要是两件事: 1.开发Bean:2.配置Bean.对于Spring框架来说,它要做的,就是根据配置文件来创建bean实例 ...

  5. Spring AspectJ基于注解的AOP实现

    对于AOP这种编程思想,很多框架都进行了实现.Spring就是其中之一,可以完成面向切面编程.然而,AspectJ也实现了AOP的功能,且实现方式更为简捷,使用更加方便,而且还支持注解式开发.所以,S ...

  6. Spring aop 记录操作日志 Aspect

    前几天做系统日志记录的功能,一个操作调一次记录方法,每次还得去收集参数等等,太尼玛烦了.在程序员的世界里,当你的一个功能重复出现多次,就应该想想肯定有更简单的实现方法.于是果断搜索各种资料,终于搞定了 ...

  7. (转)Spring AOP实现方式(转)

    我们可以通过三种方式来使用Spring AOP,它们分别是:@Aspect-based(Annotation),Schema-based(XML),以及底层的Spring AOP API 底层的Spr ...

  8. Spring AOP 由浅入深

    (分析基于Spring的版本:version = 4.1.6.RELEASE) Spring AOP工厂: org.springframework.aop.framework.DefaultAopPr ...

  9. spring源码分析(二)Aop

    创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...

  10. 转载:Spring AOP (上)

    工 作忙,时间紧,不过事情再多,学习是必须的.记得以前的部门老大说过:“开发人员不可能一天到晚只有工作,肯定是需要自我学习.第一:为了更充实自己,保 持进步状态.第二:为了提升技术,提高开发能力.第三 ...

随机推荐

  1. 深入浅出redis缓存应用

    0.1.索引 https://blog.waterflow.link/articles/1663169309611 1.只读缓存 只读缓存的流程是这样的: 当查询请求过来时,先从redis中查询数据, ...

  2. 一步一图带你深入理解 Linux 虚拟内存管理

    写在本文开始之前.... 从本文开始我们就正式开启了 Linux 内核内存管理子系统源码解析系列,笔者还是会秉承之前系列文章的风格,采用一步一图的方式先是详细介绍相关原理,在保证大家清晰理解原理的基础 ...

  3. Sql Server 数据库分页存储过程书写

    create proc 存储过程名称( @page int, //pageindex @rows int, //pagesize @rowCount int out)as begin--定义字符串变量 ...

  4. 七、kubernetes污点和容忍

    Kubernetes污点和容忍 一.Taint 和 Toleration介绍 节点亲和性,是 pod 的一种属性(偏好或硬性要求),它使 pod 被吸引到一类特定的节点.Taint 则相反,它使节点能 ...

  5. Java多线程(7):JUC(上)

    您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来- 前面把线程相关的生命周期.关键字.线程池(ThreadPool).ThreadLocal.CAS.锁和AQS都讲完了,现在就剩下怎么来用多线程了 ...

  6. K8S之prometheus-operator监控

    prometheus-operator 1. Prometheus Operator介绍 介绍文章:http://t.zoukankan.com/twobrother-p-11164391.html ...

  7. Ubuntu 20.04 开启root权限登陆、网卡配置

    个人名片: 对人间的热爱与歌颂,可抵岁月冗长 Github‍:念舒_C.ying CSDN主页️:念舒_C.ying 个人博客 :念舒_C.ying Ubuntu 20.04 开启root权限登陆.网 ...

  8. 解决linux mint内置无线网卡失效问题

    前言 同学安装了linux mint,但是内置的无线网卡失效,只能通过有线网卡连接,经过查询得到不是缺少驱动的问题,是内核不支持 解决办法 sudo apt install linux-generic ...

  9. linux 挂载 vdi 文件(virtual box虚拟机镜像文件)

    1. 下载 vdfuse 下载地址 2.解压deb文件 解压deb安装包文件,这里不使用安装命令是因为你的virtualbox 可能和vdfuse的版本不一致,导致安装失败,而我们只需要用到 vdfu ...

  10. vulnhub靶场之CONTAINME: 1

    准备: 攻击机:虚拟机kali.本机win10. 靶机:CONTAINME: 1,下载地址:https://download.vulnhub.com/containme/THM-ContainMe-v ...