1. 关于代理
23种Java设计模式之一称为代理模式,这种类型的代理称为静态代理。提供了访问目标对象的附加方法,即通过代理对象。这提供了附加的功能操作来扩展目标对象的功能而不改变原始目标对象。 Spring AOP 通常被称为动态代理。 AOP(面向切面编程)分为静态代理和动态代理(JDK和CGLIB动态代理)。
静态代理:编译时实现,编译后代理类成为实际的类文件。动态代理:运行时动态生成。这意味着编译后并没有实际的类文件,而是在运行时动态生成类的字节码并加载到JVM 中。代理方式
达到
优势
有缺点
特征
JDK静态代理
代理类和委托类实现相同的接口,并且该接口必须硬编码到代理类中。
易于实施和理解
代理类需要硬编码接口,这可能会导致重复编码、浪费存储空间并降低实际应用程序的效率。
好像没有什么特色
JDK动态代理
代理类和委托类实现相同的接口。代理类实现InvocableHandler 并重写invoke 方法来执行动态代理。
无需硬编码接口,代码复用率高
只有实现该接口的委托类才能被代理
底层使用反射机制来调用方法。
CGLIB动态代理
代理类将委托类作为自己的父类,并为非最终委托方法创建两个方法。一个方法与委托方法具有相同的签名,另一个方法通过super 调用委托方法。委托类有自己的方法。代理方法判断是否有实现MethodInterceptor接口的对象,如果有,则调用拦截方法来代理委托方法。
类或接口可以在运行时扩展,并且委托类不需要实现该接口。
Final类和final方法不能被代理
底层将所有方法存储在一个数组中,并通过数组索引直接调用方法。
1.1 静态代理
用法: 1. 创建一个接口,然后创建一个实现该接口的代理类,并在该接口上实现抽象方法。
public Interface ProxyDao { voidrentHouse();}@Slf4jpublic class ProxyDaoImplements ProxyDao { @Override public voidrentHouse() { log.info('House Rental'); 2. 创建代理类并实现该接口。代理类保存对被代理对象的引用,并且代理类的方法调用该对象的方法。
public class Transactionhandlerimplements ProxyDao { //目标代理对象private ProxyDao proxyDao; //构造代理对象时传递目标对象public Transactionhandler(ProxyDao proxyDao) { this.proxyDao=proxyDao } @Override public voidrentHouse(){ //调用前的处理目标方法System.out.println('启用事务控制.'); //调用目标对象的方法proxyDao.rentHouse() //调用目标方法后的处理println('关闭事务.控制.'); 创建客户端测试。
public class Client {public static void main(String[] args) { //创建一个新的目标对象ProxyDaoImpl target=new ProxyDaoImpl() //创建一个代理对象并使用接口ProxyDao proxyDao=new Transactionhandler(target); 引用它; //在接口上调用proxyDao.rentHouse() 。 }} JDK静态代理可以让你轻松完成类的代理操作。然而,JDK静态代理的缺点也变得明显。一个代理只能服务一个类,所以如果你有很多类需要代理,你就必须创建很多代理类,这会变得更加麻烦。
1.2 动态代理
从实现原理来看,动态代理可以分为两大类。
编译时和运行时增强
1.2.1 编译时增强
编译时增强,与Lombok 类似。也就是说,代理类是在以后运行时的编译生成阶段直接生成的。 AspectJ 是一个编译时扩展工具。
AspectJ的正式名称为Eclipse AspectJ,其位置如下:
一种基于Java语言的面向方面的编程语言。与Java兼容。易于学习和使用。要使用AspectJ,必须使用特殊的编译器ajc。
1.2.2 运行时增强
运行时扩展是指借助JDK动态代理或CGLIB动态代理在内存中临时生成AOP动态代理类。 Spring AOP中经常引用的动态代理通常就是指这个运行时扩展。
2. AspectJ 和 Spring AOP
AspectJ实际上是AOP的实现,但它是编译时扩展。
AspectJ 的一些优点:
Spring AOP生成动态代理类,因此某些静态或最后修改的方法无法被代理。这是因为这些方法不能被覆盖,并且最后修改的类不能继承。不过,AspectJ不需要动态生成代理类,一切都是在编译时完成的,所以这个问题在AspectJ中自然就解决了。 Spring AOP 有局限性。即只能在Spring容器管理的bean中使用;其他类没有这个限制。 Spring AOP只能在运行时扩展,而AspectJ支持编译时、编译后和运行时扩展。 Spring AOP支持扩展方法,AspectJ支持扩展方法、属性、构造函数、静态对象、final类/方法等。由于AspectJ 是一个编译时扩展,因此它的运行效率也比Spring AOP 更高。
2.1 Spring AOP
开发Spring AOP时,实际使用了AspectJ注解。我们平时使用的@Aspect、@Around、@Pointcut等都是AspectJ提供的,但是Spring AOP并没有利用AspectJ编译。不使用AspectJ的编译器和编织器,但Spring AOP仍然使用运行时扩展。要启用Spring对@AspectJ配置的支持,以便Spring容器中的目标bean自动扩展一个或多个方面,您需要将配置添加到Spring配置文件aop:aspectj-autoproxy/.aop:aspectj-autoproxy/.要使用Spring AOP通过CGLIB生成代理,只需将其引入到您的Spring配置文件中:aop:aspectj-autoproxy proxy-target-class='true'/
运行时扩展可以使用JDK 动态代理或CGLIB 动态代理来实现。
2.1.1 JDK 动态代理
JDK 动态代理有要求。也就是说,代理对象必须有一个接口。 CGLIB动态代理没有这个要求。
jdk实现的动态代理由两个重要成员组成:Proxy和InitationHandler。
Proxy:所有动态代理的父类,提供类对象和用于创建动态代理实例的静态方法。 InvocalHandler:每个动态代理实例都有一个关联的InvocalHandler。当在代理实例上调用方法时,该方法调用将转发到InvocableHandler 的调用方法。@Slf4jpublic class MyInitationHandlerimplements InvocableHandler { //目标对象private Object target; public MyInitationHandler(Object target) { this.target=target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //调用目标方法前的处理log.info('------插入提前通知代码-----') //调用目标对象的方法Object rs=method.invoke (target , args); //调用目标方法后的处理log.info('-----通知后插入代码-----');
@Slf4jpublic class Client { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InitationTargetException, InstantiationException { //====================第一个类型====================log.info('第一种类型:===================================如下:'); //1. 生成$Proxy0的类文件。 System.getProperties().put('sum.misc.ProxyGenerator.saveGeneeratedFiles', true); //2. 获取动态代理类。 Class proxyClazz=Proxy.getProxyClass(ProxyDao.class.getClassLoader(), ProxyDao.class); //3. 获取代理类构造函数,并将参数类型传递给InvocableHandler.class 构造函数。 constructor=proxyClazz.getConstructor(InvocalHandler.class); //4.通过构造函数类创建动态代理对象,并将自定义的InvocableHandler 实例传递给ProxyDao proxyDao=(ProxyDao)constructor.newInstance(new MyInitationHandler(new ProxyDaoImpl( ))) ; //5.通过代理对象调用目标方法proxyDao.rentHouse()。 //===================2nd==================/** * 代理类有, 还有也是一个简单的方法,封装了创建动态代理对象的步骤2-4。 * 其方法签名为: newProxyInstance(classLoaderloader,class. [] instance,InitationHandler h) * Loader : 类加载器。用于加载代理对象。 * Interface : 代理类实现的一些接口* h : 实现InvocationHandler接口的对象*/log.info('第二个方法1:(内部类)===================================类似: ') ProxyDao proxyDao1=(ProxyDao) Proxy.newProxyInstance(ProxyDao.class.getClassLoader( ), new Class[] { ProxyDao.class}, new InvocalHandler() { @Override public Object invoke(Object proxy, Method Method, Object[] args) throws Throwable { //调用目标方法前的处理log.info ('-------- Insert提前通知代码------'); //调用目标对象的方法Object invoke=method.invoke(new ProxyDaoImpl(), args); //调用目标方法后Process log.info(' - --- --插入发布通知代码-----') } }); 第二种方法:===================================如下:'); ProxyDao proxyDao2=(ProxyDao) Proxy.newProxyInstance(ProxyDao.class.getClassLoader( ),//接口加载new Class[]{ProxyDao.class}的类加载器,//接口集new MyInvocalHandler(new ProxyDaoImpl()));//自定义InitationHandler proxyDao2.rentHouse() }} run;结果:
2..2 CGLIB 动态代理
从SpringBoot2开始,AOP默认使用的动态代理是CGLIB动态代理。与JDK动态代理相比,CGLIB动态代理支持类代理。如果用户想使用JDK动态代理,必须手动配置。 spring.aop.proxy-target-class=false。
CGLIB(代码生成库)包的底层使用ASM这种小型且快速的字节码处理框架来转换字节码并生成新的类。这是一个基于ASM的字节码生成库,允许您在运行时修改和动态生成字节码。 CGLIB通过继承来实现代理。
CGLIB 实现还包含两个重要成员:Enhancer 和MethodInterceptor。其实这两个的用法和JDK实现的动态代理Proxy和InitationHandler非常相似。
增强器:指定要代理的目标对象,对该对象的所有非最终方法调用都会检索实际处理代理逻辑的对象。 MethodInterceptor:动态代理对象方法调用被转发到拦截方法进行扩展。CGLIB代理的实现如下:
实现MethodInterceptor 将方法调用转发到类的拦截()方法。如果需要,通过CGLIB动态代理获取代理对象。这是代码:它是一个实现类,没有接口。
@Slf4jpublic class HelloService { public HelloService(){ log.info('--------HelloServiceconstructor------') } /** * 该方法不能被子类重写,cglib 是最终代理不能被执行。 */final public String SayOthes(String name){ log.info('-----HelloService:sayOthes------'+name); } public void SayHello(){ log .info(' ----- -HelloService:sayHello------'); } public voidsayHello2(){ log.info('-----HelloService:sayHello2------'); 实现拦截方法。对于MethodInterceptor 接口:
@Slf4jpublic class MyMethodInterceptorimplements MethodInterceptor { @Override public ObjectIntercept(Object o, Method method, Object[] events, MethodProxy methodProxy) throws Throwable { //调用目标方法前的处理log.info('-------- Insert advance notification code ------'); //调用目标对象的方法Object result=methodProxy.invokeSuper(o,objects) //调用目标方法后的处理log.info('---) -- -插入post通知代码------') 测试类:
public class CGLIBClient { public static void main(String[] args) { //将agent类文件保存到本地,方便编译查看源码。 //System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, 'D:\) \log'); //通过CGLIB动态代理获取agent i对象的过程Enhancerextender=newEnhancer() //设置enhancer对象的父类Setextender.setSuperclass (HelloService.class); //设置增强器回调对象enhancer.setCallback(new MyMethodInterceptor() );
创建代理对象 HelloService helloService = (HelloService) enhancer.create(); // 通过代理对象调用目标方法 helloService.sayHello(); helloService.sayHello2(); }}运行结果: