详解介绍代理模式及Spring AOP
代理模式-Proxy
概述
定义:为其他对象提供一种代理,已控制对这个对象的访问。
代理对象在客户端和目标对象之前起到中介的作用
类型:结构型设计模式
适用场景:
- 保护目标对象
- 增强目标对象
优点:
- 代理模式能将代理对象与真实调用的目标对象分离
- 一定程度上降低了程序的耦合性,提高可扩展性
- 保护目标对象
- 增强目标对象
缺点:
- 代理模式会造成系统设计中类的数目增加
- 在客户端和目标对象之间添加代理对象,会造成请求处理速度变慢
- 增加了系统的复杂度
使用场景:
- 按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。
比较
装饰者模式与代理模式
- 代理模式是控制访问
- 装饰者模式是为对象加上行为
适配器模式与代理模式
- 适配器模式是主要改变所考虑对象的接口
- 代理模式是不能改变代理对象的接口的
静态代理
Coding
订单Service接口:public interface IOrderService { int saveOrder(Object order); }
订单Service实现类:
public class OrderServiceImpl implements IOrderService { @Override public int saveOrder(Object order) { System.out.println("Service层调用Dao层添加Order"); return 1; } }
静态代理类:
public class OrderServiceStaticProxy { private IOrderService iOrderService = new OrderServiceImpl(); public int saveOrder(Object order){ beforeMethod( ); int result = iOrderService.saveOrder(order); afterMethod(); return result; } private void beforeMethod(){ System.out.println("订单保存前,静态代理处理逻辑。。。。"); } private void afterMethod(){ System.out.println("静态代理 after code"); } }
测试类:
public class StaticProxyMain { public static void main(String[] args) { OrderServiceStaticProxy orderServiceStaticProxy = new OrderServiceStaticProxy(); orderServiceStaticProxy.saveOrder("订单1"); } }
静态代理总结:
优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
缺点:我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
动态代理
JDk动态代理
在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK再运行时为我们动态的来创建。
coding
/** * @Description: JDK动态代理 * @Author: yong.zheng * @Date: 2021/12/21 19:38 **/ public class DynamicProxyHandler implements InvocationHandler { private Object object; public DynamicProxyHandler(final Object object) { this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { beforeMethod(); Object result = method.invoke(object, args); afterMethod(); return result; } private void beforeMethod(){ System.out.println("订单保存前,静态代理处理逻辑。。。。"); } private void afterMethod(){ System.out.println("静态代理 after code"); } }
测试类
public class DynamicProxyMain { public static void main(String[] args) { OrderServiceImpl orderService = new OrderServiceImpl(); DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler(orderService); IOrderService proxyService = (IOrderService) Proxy.newProxyInstance(orderService.getClass().getClassLoader(), orderService.getClass().getInterfaces(), dynamicProxyHandler); proxyService.saveOrder("order .."); } }
注意Proxy.newProxyInstance()方法接受三个参数:
- ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
- Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
- InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法
动态代理总结:
虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。有很多条理由,人们可以否定对 class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。但是,不完美并不等于不伟大,伟大是一种本质,Java动态代理就是佐例。
CGlib动态代理
- JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
- Coding
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
private Object target;
public Object getInstance(final Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
/**
* 1. 代理对象的类是被代理类的子类
* <p>
* 2. 代理对象重写了父类的所有方法
* 重写的结果时:调用代理对象的任何方法都会触发MethodInterceptor.intercept
* <p>
* 3. 当调用代理对象的方法时会将 代理对象自身/真实方法/调用参数/代理方法(调用时直接触发的方法,代理对象对父类方法的重写) 封装起来作为MethodInterceptor.intercept方法的入参
* <p>
* 4. method.invoke(真实对象,params) 等价于 methodProxy.invokeSuper(代理对象,params)
* <p>
* 5. 注意:第一个参数(代理对象)慎用
* 5.1 切勿在MethodInterceptor.intercept方法中调用第一个参数proxy的任何方法,会引发无限递归最终OOM
* proxy.toString->MethodInterceptor.intercept->proxy.toString->MethodInterceptor.intercept->......
*
* @param proxy 代理对象
* @param method 被代理的类中的真实方法
* @param objects 调用时传入的参数
* @param methodProxy 代理对象中的代理方法
* @return 方法返回值
*/
@Override
public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
beforeMethod();
//这样也是允许的,等价于
Object result = method.invoke(target, objects);
//这样调用是允许的
//Object result = methodProxy.invokeSuper(proxy, objects);
//这样调用是不允许的,将会报强转异常,方法中会尝试将第一个参数强转为代理对象
//methodProxy.invokeSuper(new T(), objects);
//这样会导致无限递归 method.invoke(proxy, objects)->MethodInterceptor.intercept->method.invoke(proxy, objects)->MethodInterceptor.intercept->....
//method.invoke(proxy, objects);
//这样将会导致无限递归,原因和method.invoke(proxy, objects);相同
//methodProxy.invoke(proxy, objects);
afterMethod();
return result;
}
private void beforeMethod() {
System.out.println("订单保存前,静态代理处理逻辑。。。。");
}
private void afterMethod() {
System.out.println("静态代理 after code");
}
测试类
public class CglibProxyMain {
public static void main(String[] args) {
IOrderService orderService = new OrderServiceImpl();
CglibProxy cglibProxy = new CglibProxy();
OrderServiceImpl buyHouseCglibProxy = (OrderServiceImpl) cglibProxy.getInstance(orderService);
buyHouseCglibProxy.saveOrder("order ..");
}
}
- Cglib动态代理注意事项:
- 代理对象的类是被代理类的子类
- 代理对象重写了父类的所有方法
重写的结果时:调用代理对象的任何方法都会触发MethodInterceptor.intercept- 当调用代理对象的方法时会将 代理对象自身/真实方法/调用参数/代理方法(调用时直接触发的方法,代理对象对父类方法的重写) 封装起来作为MethodInterceptor.intercept方法的入参
- method.invoke(真实对象,params) 等价于 methodProxy.invokeSuper(代理 对象params)
- 注意:第一个参数(代理对象)慎用
切勿在MethodInterceptor.intercept方法中调用第一个参数proxy的任何方法,会引发无限递归最终OOM
proxy.toString->MethodInterceptor.intercept->proxy.toString-> MethodInterceptor.intercept->…
- CGLIB代理总结:
CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。.
Jdk动态代理与cglib动态代理对比:
- jdk动态代理只能针对有接口的接口方法进行动态代理;
- cglib基于继承实现的动态代理,无法对static、final修饰的类进行动态代理;
- cglib基于继承实现的动态代理,无法对static、private修饰的方法进行动态代理;
Spring AOP
- AOP:面向切面编程。
使用详解
略!
实现原理
- 代理的创建
创建代理工厂:拦截器数组,目标对象接口数组,目标对象。
创建代理工厂时,默认会在拦截器数组尾部再增加一个默认拦截器 —— 用于最终的调用目标方法。
当调用 getProxy 方法的时候,会根据接口数量大余 0 条件返回一个代理对象(JDK or Cglib)。
注意:创建代理对象时,同时会创建一个外层拦截器,这个拦截器就是 Spring 内核的拦截器,用于控制整个 AOP 的流程。 - 代理的调用
当对代理对象进行调用时,就会触发外层拦截器。
外层拦截器根据代理配置信息,创建内层拦截器链。创建的过程中,会根据表达式判断当前拦截是否匹配这个拦截器。而这个拦截器链设计模式就是职责链模式。
当整个链条执行到最后时,就会触发创建代理时那个尾部的默认拦截器,从而调用目标方法,最后返回。
代理方式
- 如果目标对象实现了接口就默认会采用jdk的动态代理;
- 如果目标对象没有实现接口就会采用cglib动态代理;
- 如果目标对象实现了接口,且强制使用cglib动态代理,就采用动态代理。
Spring AOP 如何实现链式调用的?
采用了责任链模式!暂不介绍。
Spring AOP 的经典应用
- Spring : @transactinal
- Spring Security: 安全验证@PreAuthorize
- Spring Cache:@Cacheable