zoukankan      html  css  js  c++  java
  • 死磕Spring AOP (学习持续更新中。。)

    https://www.cnblogs.com/xiaoxi/p/5945707.html

    在 Spring 中 AOP 代理使用 JDK 动态代理CGLIB 代理来实现,默认如果目标对象是接口,则使用 JDK 动态代理,否则使用 CGLIB 来生成代理类。

    JDK 动态代理

    定义一个业务接口IUserService,如下:

    package com.spring.aop;
    
    public interface IUserService {
        //添加用户
        public void addUser();
        //删除用户
        public void deleteUser();
    }

    实现类UserServiceImpl,如下:

    package com.spring.aop;
    
    public class UserServiceImpl implements IUserService{
        
        public void addUser(){
            System.out.println("新增了一个用户!");
        }
        
        public void deleteUser(){
            System.out.println("删除了一个用户!");
        }
    }

    创建一个实现接口InvocationHandler的类,他必须实现invoke方法,实例DynamicProxy。

    package com.spring.aop;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class DynamicProxy implements InvocationHandler{
        
        //被代理对象(就是要给这个目标类创建代理对象)
        private Object target;
        
        //传递代理目标的实例,因为代理处理器需要,也可以用set等方法。
        public DynamicProxy(Object target){
            this.target=target;
        }
        
        /**
         * 覆盖java.lang.reflect.InvocationHandler的方法invoke()进行织入(增强)的操作。
         * 这个方法是给代理对象调用的,留心的是内部的method调用的对象是目标对象,可别写错。
         * 参数说明:
         * proxy是生成的代理对象,method是代理的方法,args是方法接收的参数
         */
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
            //目标方法之前执行
            System.out.println("do sth Before...");
            //通过反射机制来调用目标类方法
            Object result = method.invoke(target, args);
            //目标方法之后执行
            System.out.println("do sth After...
    ");
            return result;
        }
    }

     下面是测试:

    package com.controller.test;
    
    import java.lang.reflect.Proxy;
    
    public class DynamicTest {
        
         public static void main(String[] args){
             
                IUserService target = new UserServiceImpl();
                
                DynamicProxy handler= new DynamicProxy(target);
                
                IUserService proxy = (IUserService)Proxy.newProxyInstance(
                        target.getClass().getClassLoader(),//目标类的类加载器
                        target.getClass().getInterfaces(), //目标类的接口
                        handler); //横切类
                proxy.addUser();
                proxy.deleteUser();
            }
    
    }

    结果:

    实现动态代理步骤:
    A.  创建一个实现接口InvocationHandler的类,他必须实现invoke方法(上例DynamicProxy)。
    B.创建被代理的类(UserServiceImpl)以及接口(IUserService)。
    C.通过Proxy的静态方法newProxyInstance(ClassLoader loader, Class<?>[]interfaces, InvocationHandler handler)创建一个代理。
    D.通过代理调用方法。

    JDK动态代理的实现主要使用java.lang.reflect包里的Proxy类和InvocationHandler接口。

    InvocationHandler接口

    来看看java的API帮助文档是怎么样描述InvocationHandler接口的:

    InvocationHandler is the interface implemented by the invocation handler of a proxy instance. 
    
    Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and 
    dispatched to the invoke method of its invocation handler.

    说明:每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。同时在invoke的方法里 我们可以对被代理对象的方法调用做增强处理(如添加事务、日志、权限验证等操作)。

    我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:

    public interface InvocationHandler { 
         public Object invoke(Object proxy,Method method,Object[] args) throws Throwable; 
    }

    参数说明:
    Object proxy:指被代理的对象。
    Method method:要调用的方法。(指代的是我们所要调用代理对象的某个方法的Method对象)
    Object[] args:方法调用时所需要的参数。(指代的是调用真实对象某个方法时接受的参数)

    Proxy类
    Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法:

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 

    参数说明:
    ClassLoader loader:类加载器
    Class<?>[] interfaces:得到全部的接口
    InvocationHandler h:得到InvocationHandler接口的子类实例

    使用JDK动态代理有一个很大的限制,就是它要求目标类必须实现了对应方法的接口,它只能为接口创建代理实例。我们在上文测试类中的Proxy的newProxyInstance方法中可以看到,该方法第二个参数便是目标类的接口。如果该类没有实现接口,这就要靠cglib动态代理了。


    CGLIB动态代理

    CGLib采用非常底层的字节码技术,可以为一个类创建一个子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势植入横切逻辑

    字节码生成技术实现AOP,其实就是继承被代理对象,然后Override需要被代理的方法,在覆盖该方法时,自然是可以插入我们自己的代码的。因为需要Override被代理对象的方法,所以自然用CGLIB技术实现AOP时,就必须要求需要被代理的方法不能是final方法,因为final方法不能被子类覆盖。

    a.使用CGLIB动态代理不要求必须有接口,生成的代理对象是目标对象的子类对象,所以需要代理的方法不能是private或者final或者static的。
    b.使用CGLIB动态代理需要有对cglib的jar包依赖(导入asm.jar和cglib-nodep-2.1_3.jar)

    CGLibProxy与JDKProxy的代理机制基本类似,只是其动态代理的代理对象并非某个接口的实现,而是针对目标类扩展的子类。换句话说JDKProxy返回动态代理类,是目标类所实现接口的另一个实现版本,它实现了对目标类的代理(如同UserDAOProxy与UserDAOImp的关系),而CGLibProxy返回的动态代理类,则是目标代理类的一个子类(代理类扩展了UserDaoImpl类)。

    cglib 代理特点:
    CGLIB 是针对类来实现代理,它的原理是对指定的目标类生成一个子类,并覆盖其中方法。因为采用的是继承,所以不能对 finall 类进行继承

    我们使用CGLIB实现上面的例子:

    代理的最终操作类:

    package com.spring.aop;
    
    import java.lang.reflect.Method;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    public class CglibProxy implements MethodInterceptor{
        
        //增强器,动态代码生成器
        Enhancer enhancer = new Enhancer();
        
        /**
         * 创建代理对象
         * @param clazz
         * @return 返回代理对象
         */
        public Object getProxy(Class clazz){
            //设置父类,也就是被代理的类(目标类)
            enhancer.setSuperclass(clazz);
            //设置回调(在调用父类方法时,回调this.intercept())
            enhancer.setCallback(this);
            //通过字节码技术动态创建子类实例(动态扩展了UserServiceImpl类)
            return enhancer.create();
        }
        
        /**
         * 拦截方法:在代理实例上拦截并处理目标方法的调用,返回结果
         * obj:目标对象代理的实例;
         * method:目标对象调用父类方法的method实例;
         * args:调用父类方法传递参数;
         * proxy:代理的方法去调用目标方法
         */
        public Object intercept(Object obj,Method method,Object[] args,MethodProxy proxy) 
            throws Throwable{
            
            System.out.println("--------测试intercept方法的四个参数的含义-----------");
            System.out.println("obj:"+obj.getClass());
            System.out.println("method:"+method.getName());
            System.out.println("proxy:"+proxy.getSuperName());
            if(args!=null&&args.length>0){
                for(Object value : args){
                    System.out.println("args:"+value);
                }
            }
    
            //目标方法之前执行
            System.out.println("do sth Before...");
            //目标方法调用
            //通过代理类实例调用父类的方法,即是目标业务类方法的调用
            Object result = proxy.invokeSuper(obj, args);
            //目标方法之后执行
            System.out.println("do sth After...
    ");
            return result;
        }
    }

    测试类:

    package com.spring.aop;
    
    public class CglibProxyTest {
        
        public static void main(String[] args){
            CglibProxy proxy=new CglibProxy();
            //通过java.lang.reflect.Proxy的getProxy()动态生成目标业务类的子类,即是代理类,再由此得到代理实例
            //通过动态生成子类的方式创建代理类
            IUserService target=(IUserService)proxy.getProxy(UserServiceImpl.class);
            target.addUser();
            target.deleteUser();
        }
    }

    我们看到达到了同样的效果。它的原理是生成一个父类enhancer.setSuperclass(clazz)的子类enhancer.create(),然后对父类的方法进行拦截enhancer.setCallback(this). 对父类的方法进行覆盖,所以父类方法不能是final的。

    总结:
      (1).通过输出可以看出,最终调用的是com.spring.aop.UserServiceImpl的子类(也是代理类)com.spring.aop.UserServiceImpl$$EnhancerByCGLIB$$43831205的方法。
      (2). private,final和static修饰的方法不能被代理。

    注意:
      (1).CGLIB是通过实现目标类的子类来实现代理,不需要定义接口。
      (2).生成代理对象使用最多的是通过Enhancer和继承了Callback接口的MethodInterceptor接口来生成代理对象,设置callback对象的作用是当调用代理对象方法的时候会交给callback对象的来处理。
      (3).创建子类对象是通过使用Enhancer类的对象,通过设置enhancer.setSuperClass(Class class)和enhancer.setCallback(Callback callback)来创建代理对象。

    MethodInterceptor接口的intercept方法

    Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;

    参数说明:

    Object var1代表的是子类代理对象,

    Method var2代表的是要调用的方法反射对象,

    第三个参数是传递给调用方法的参数,前三个参数和JDK的InvocationHandler接口的invoke方法中参数含义是一样的,

    第四个参数MethodProxy对象是cglib生成的用来代替method对象的,使用此对象会比jdk的method对象的效率要高。

    如果使用method对象来调用目标对象的方法: method.invoke(var1, var3),则会陷入无限递归循环中, 因为此时的目标对象是目标类的子代理类对象。

    MethodProxy类提供了两个invoke方法:

    public Object invokeSuper(Object obj, Object[] args) throws Throwable;
    public Object invoke(Object obj, Object[] args) throws Throwable;

    注意此时应该使用invokeSuper()方法,顾名思义调用的是父类的方法,若使用invoke方法,则需要提供一个目标类对象,但我们只有目标类子类代理对象,所以会陷入无限递归循环中。

    CGLIB所创建的动态代理对象的性能比JDK所创建的动态代理对象的性能高很多,但创建动态代理对象时比JDK创建动态代理对象要花费更长的时间。

    JDK代理和CGLIB代理的总结(生成代理对象的前提是有AOP切入)
    (1)、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。 如果就是单纯的用IOC生成一个对象,也没有AOP的切入不会生成代理的,只会NEW一个实例,给Spring的Bean工厂。
    (2)、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
    如何强制使用CGLIB实现AOP
    * 添加CGLIB库
    * 在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>就能强制使用
    (3)、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换(没有实现接口的就用CGLIB代理,使用了接口的类就用JDK动态代理)

    JDK动态代理和CGLIB字节码生成的区别:
    (1)、JDK动态代理只能对实现了接口的类生成代理,而不能针对类。CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法最好不要声明成final。
    (2)、JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,它有几个要求
    * 实现InvocationHandler;
    * 使用Proxy.newProxyInstance产生代理对象;
    * 被代理的对象必须要实现接口;
    CGLib 必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承。
    (3)、jdk的核心是实现InvocationHandler接口,使用invoke()方法进行面向切面的处理,调用相应的通知。cglib的核心是实现MethodInterceptor接口,使用intercept()方法进行面向切面的处理,调用相应的通知。

    五、小结

         AOP 广泛应用于处理一些具有横切性质的系统级服务,AOP 的出现是对 OOP 的良好补充,它使得开发者能用更优雅的方式处理具有横切性质的服务。不管是哪种 AOP 实现,不论是 AspectJ、还是 Spring AOP,它们都需要动态地生成一个 AOP 代理类,区别只是生成 AOP 代理类的时机不同:AspectJ 采用编译时生成 AOP 代理类,因此具有更好的性能,但需要使用特定的编译器进行处理;而 Spring AOP 则采用运行时生成 AOP 代理类,因此无需使用特定编译器进行处理。由于 Spring AOP 需要在每次运行时生成 AOP 代理,因此性能略差一些。

    Spring的AOP关于拦截private方法

    private方法 在Spring使用纯Spring AOP(只能拦截public/protected/包)都是无法被拦截的 因为子类无法覆盖;包级别能被拦截的原因是,如果子类和父类在同一个包中是能覆盖的。 

    如果想要实现拦截private方法的 可以使用 原生 AspectJ 编译期/运行期织入。 

    。。。。。。。。。

  • 相关阅读:
    设计模式-适配器模式
    设计模式-模板方法模式
    设计模式-策略模式
    spring-消息
    spring-集成redis
    spring-mvc高级技术
    spring-AOP
    restful规范
    十一,装饰器详解
    十,函数对象,嵌套,名称空间与作用域,闭包函数
  • 原文地址:https://www.cnblogs.com/dingpeng9055/p/11661695.html
Copyright © 2011-2022 走看看