代理模式
1、概念
使用代理类间接调用目标方法,将目标方法核心逻辑的代码从目标方法中剥离出来——实现解耦。
二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
2、静态代理
创建静态代理类:
public class CalculatorStaticProxy implements Calculator {
// 将被代理的目标对象声明为成员变量
private Calculator target;
public CalculatorStaticProxy(Calculator target) {
this.target = target;
}
@Override
public int add(int i, int j) {
// 附加功能由代理类中的代理方法来实现
System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
// 通过目标对象来实现核心业务逻辑
int addResult = target.add(i, j);
System.out.println("[日志] add 方法结束了,结果是:" + addResult);
return addResult;
}
}
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。
提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。
3、动态代理
3.1、概念
个人理解:动态代理主要是利用反射机制来获取class对象,通过这个class对象动态得到目标类的相关的方法,然后在该方法前后添加一些功能性的代码,而静态代理是将获取核心方法的代码写死了,不利于扩展到其他的对象以及后续的维护。
参考:https://blog.csdn.net/qq_16570607/article/details/118360338
动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。
3.2、创建动态代理对象的两个方法
代理类在程序运行期间,创建的代理对象称之为动态代理对象。
创建代理对象的两个方法:
//JDK动态代理
Proxy.newProxyInstance(三个参数);
//CGLib动态代理
Enhancer.create(两个参数);
3.3、两种常用的动态代理方式
-
基于接口的动态代理
- 提供者:JDK
- 使用JDK官方的Proxy类创建代理对象
- 注意:代理的目标对象必须实现接口
-
基于类的动态代理
- 提供者:第三方 CGLib
- 使用CGLib的Enhancer类创建代理对象
- 注意:如果报 asmxxxx 异常,需要导入 asm.jar包
以下是两种代理方式的实例代码:
生产代理对象的工厂类
public class ProxyFactory{
/**
* 生成对象的代理对象,对被代理对象进行所有方法日志增强
* 参数:原始对象
* 返回值:被代理的对象
* JDK 动态代理
* 基于接口的动态代理
* 被代理类必须实现接口
* JDK提供的
*/
public static Object getProxy(final Object obj){
/**
* 创建对象的代理对象
* 参数一:生成代理类的类加载器
* 参数二:被代理对象实现的接口
* 参数三:实现了 InvocationHandler 接口的对象,作为代理对象的调用处理器,对代理对象的方法进行拦截和处理。
*/
Object proxyInstance = Proxy.newProxyInstance(obj.getClass().getClassLoader()
, obj.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* proxy:代理对象
* method:代理对象需要实现的方法,即其中需要重写的方法
* args:method所对应方法的参数
*/
//方法执行前
long startTime = System.currentTimeMillis();
Object result = method.invoke(obj, args);//执行方法的调用
//方法执行后
long endTime = System.currentTimeMillis();
SimpleDateFormat sdf = new SimpleDateFormat();
System.out.printf(String.format("%s方法执行结束时间:%%s ;方法执行耗时:%%d%%n"
, method.getName()), sdf.format(endTime), endTime - startTime);
return result;
}
});
return proxyInstance;
}
/**
* 使用CGLib创建动态代理对象
* 第三方提供的的创建代理对象的方式CGLib
* 被代理对象不能用final修饰
* 使用的是Enhancer类创建代理对象
*/
public static Object getObjectByCGLib(final Object obj){
/**
* 使用CGLib的Enhancer创建代理对象
* 参数一:对象的字节码文件
* 参数二:方法的拦截器
*/
Object proxyObj = Enhancer.create(obj.getClass(), new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
/**
* o 被代理的对象(需要增强的对象)
* method 被拦截的方法(需要增强的方法)
* args 方法入参
* methodProxy 用于调用原始方法
*/
//方法执行前
long startTime = System.currentTimeMillis();
Object invokeObject = method.invoke(obj, objects);//执行方法的调用
//方法执行后
long endTime = System.currentTimeMillis();
SimpleDateFormat sdf = new SimpleDateFormat();
System.out.printf(String.format("%s方法执行结束时间:%%s ;方法执行耗时:%%d%%n"
, method.getName()), sdf.format(endTime), endTime - startTime);
return invokeObject;
}
});
return proxyObj;
}
}
3.4、测试
@Test
public void testDynamicProxy(){
ProxyFactory factory = new ProxyFactory(new CalculatorLogImpl()); // 传入目标对象作为参数创建一个代理类工厂的实例对象
Calculator proxy = (Calculator) factory.getProxy(); // 调用代理类的方法传入相关的日志
proxy.div(1,0); // 调用接口的相关重写方法
//proxy.div(1,1);
}
3.5、CGLib
底层原理:
-
通过查看 Enhancer 类源码,最终也是生成动态代理类的字节码,动态代理类继承要被代理的类,然后实现其方法。
-
和 JDK Proxy 的实现代码比较类似,都是通过实现代理器的接口,再调用某一个方法完成动态代理的,唯一不同的是,CGLib 在初始化被代理类时,是通过 Enhancer 对象把代理对象设置为被代理类的子类来实现动态代理的。
3.6、JDK Proxy 和 CGLib 的区别
- 一个是JDK自带的,一个是第三方提供的工具类
- JDK Proxy 是通过拦截器加反射的方式实现的
- JDK Proxy 只能代理实现接口的类;
- CGLib 无需通过接口来实现,它是针对类实现代理,主要是对指定的类生成一个子类,它是通过实现子类的方式来完成调用的。
AOP
1、AOP思想
基于动态代理思想,对原来目标对象创建代理对象,在不修改原对象代码情况下,通过代理对象调用增强功能的代码,从而对原有业务方法进行增强。
2、AOP 使用场景
- 记录日志(调用方法后记录日志)
- 监控性能(统计方法运行时间)
- 权限控制(调用方法前校验是否有权限)
- 事务管理(调用方法前开启事务,调用方法后提交关闭事务 )
- 缓存优化(第一次调用查询数据库,将查询结果放入内存对象, 第二次调用,直接从内存对象返回,不需要查询数据库 )
3、AOP 实现原理
Spring AOP 的有两种实现方式:JDK proxy 和 CGLib 动态代理
- 当 Bean 实现接口时,Spring 使用 JDK proxy实现。当 Bean 没有实现接口时,Spring 使用 CGlib 代理实现。
- 通过配置可以强制使用 CGlib 代理(在 spring 配置中加入 aop:aspectj-autoproxy proxy-target-class=“true”)。
4、相关术语
tips:
通过切入点定位到特定的连接点。通俗说,就是要实际去增强的方法 。
切面即代理类对象
各种通知:
前置通知:使用@Before注解标识,在被代理的目标方法前执行
返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝)
异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命)
后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论)
环绕通知:使用@Around注解标识,使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
5、AOP实现步骤
基于注解的AOP的实现:
- 将目标对象和切面(切入点+通知)交给IOC容器管理(注解+扫描)
- 在XML中开启AspectJ(是AOP思想的一种实现。本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的)的自动代理,为目标对象自动生成代理
- 将切面类通过注解@Aspect标识