Spring基础知识之Aop动态代理

代理模式:

  代理模式是常用的Java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息,代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务,简单的说就是,我们在访问实际对象时,是通过代理对象来访问的.代理模式就是在访问实际对象时,引入一定程度的间接性,因为这种间接性,可以附加多种用途.

代理模式结构图(图片来自<<大话设计模式>>)

 静态代理:

  静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来,在程序运行之前,代理类.class文件已经生成.

静态代理简单实现:

  1.首先创建一个接口,这个接口是代理类和被代理类公共的接口,它们有一个共同的实现方法.

1 public interface Person {
2     // 上交班费
3     void giveMoney();
4 }

  2.创建两个实现类

 1 public class Student implements Person {
 2     private String name;
 3     public Student(String name){
 4         this.name = name;
 5     }
 6 
 7     public void giveMoney() {
 8         System.out.println("student: "+name+" giveMoney");
 9     }
10 }
 1 public class StudentProxy implements Person {
 2     //被代理的学生
 3     Student student;
 4     public StudentProxy(Person student){
 5         // 只代理学生对象
 6         if(student.getClass() == Student.class){
 7             this.student = (Student)student;
 8         }
 9     }
10     // 实现被代理类的方法
11     public void giveMoney() {
12         student.giveMoney();
13     }
14 }

  3.测试类

 1 public class Test {
 2     public static void main(String[] args) {
 3         //被代理对象
 4         Person student = new Student("张三");
 5         //生成代理对象,并将张三传给代理对象
 6         Person monitor = new StudentProxy(student);
 7         //代理对象调用方法,间接调用被代理对象的方法
 8         monitor.giveMoney();
 9     }
10 }

  4.测试结果

   这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式.

  代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。

 1 public class StudentProxy implements Person {
 2     //被代理的学生
 3     Student student;
 4     public StudentProxy(Person student){
 5         // 只代理学生对象
 6         if(student.getClass() == Student.class){
 7             this.student = (Student)student;
 8         }
 9     }
10     // 实现被代理类的方法
11     public void giveMoney() {
12         System.out.println("在执行被代理类方法之前执行");
13         student.giveMoney();
14     }
15 }

  可以看到,只需要在代理类中执行被代理方法之前,执行其他操作就可以了。这种操作,也是使用代理模式的一个很大的优点。最直白的就是在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。

动态代理:

   代理类在程序运行时创建的代理方式被称为动态代理。动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。

动态代理的实现:

  在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

  1.创建一个接口

1 public interface Person {
2     void giveMoney();
3 }

  2.创建一个需要被代理的实际类

public class Student implements Person {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    public void giveMoney() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("student "+name +" giveMoney");
    }
}

  3定义一个检测方法执行时间的工具类

 1 public class MonitorUtil {
 2     private static ThreadLocal<Long> t1 = new ThreadLocal<Long>();
 3     public static void start(){
 4         t1.set(System.currentTimeMillis());
 5     }
 6     //结束打印时耗时
 7     public static void finish(String methodName){
 8         long finishTime = System.currentTimeMillis();
 9         System.out.println(methodName+"方法耗时:"+(finishTime - t1.get())+"ms");
10     }
11 }

  4.创建StudentInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。再在invoke方法中执行被代理对象target的相应方法.

 1 public class StudentInvocationHandler<T> implements InvocationHandler {
 2     // invocationHandler持有的被代理对象
 3     T target;
 4 
 5     public StudentInvocationHandler(T target) {
 6         this.target = target;
 7     }
 8 
 9     /**
10      *
11      * @param proxy 代表动态代理对象
12      * @param method 代表正在执行的方法
13      * @param args 代表调用目标方法时传入的实参
14      * @return
15      * @throws Throwable
16      */
17     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
18         System.out.println("代理执行"+method.getName()+"方法");
19         //代理过程插入检测方法,计算方法耗时
20         MonitorUtil.start();
21         Object result = method.invoke(target,args);
22         MonitorUtil.finish(method.getName());
23         return result;
24     }
25 }

  5.创建测试类:

 1 public class Test {
 2     public static void main(String[] args) {
 3         //创建一个实例对象,这个对象是被代理的对象
 4         Person student = new Student("张三");
 5         //创建一个与代理对象相关联的invocationHandler
 6         InvocationHandler stuHandler = new StudentInvocationHandler<Person>(student);
 7         //创建一个代理对象stuProxy来代理student,代理对象的每个执行方法都会替换执行InvocationHandler的invoke()方法
 8         Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(),new Class[]{Person.class},stuHandler);
 9         //代理执行方法
10         stuProxy.giveMoney();
11     }
12 }

  6.上面也有说到所有执行代理对象的方法都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法.

   7.总结:

  动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了,动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体样子,因为动态代理中被代理对象和代理对象是通过InvocationHandler来完成的代理过程的

  Java动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。

静态代理和动态代理的区别:

  静态代理:编译时将增强代码植入class文件,因为是编译期进行的增强,所以代码运行时效率比动态代理高,

  动态代理:运行时生成代理类并加载,效率比静态代理要低,spring中使用了JDK动态代理和cglib两种动态代理的方式来实现代理类的生成.

CGlib代理:

  CGLIB(Code Generation Library)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口

  CGLIB是一个强大的高性能的代码生成包。它广泛的被许多AOP的框架使用,例如Spring AOP为他们提供方法的interception(拦截)。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。

  CGLIB是无法代理final修饰的方法的.

CGlib的简单实现:

  1.创建一个业务类,这个类没有实现任何接口.

 1 public class HelloService {
 2  
 3     public HelloService() {
 4         System.out.println("HelloService构造");
 5     }
 6  
 7     /**
 8      * 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的
 9      */
10     final public String sayOthers(String name) {
11         System.out.println("HelloService:sayOthers>>"+name);
12         return null;
13     }
14  
15     public void sayHello() {
16         System.out.println("HelloService:sayHello");
17     }
18 }

  2.自定义一个MethodInterceptor方法实现MethodInterceptor接口;

 1 public class MyMethodInterceptor implements MethodInterceptor{
 2  
 3     /**
 4      * sub:cglib生成的代理对象
 5      * method:被代理对象方法
 6      * objects:方法入参
 7      * methodProxy: 代理方法
 8      */
 9     @Override
10     public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
11         System.out.println("======插入前置通知======");
12         Object object = methodProxy.invokeSuper(sub, objects);
13         System.out.println("======插入后者通知======");
14         return object;
15     }
16 }

  3.测试类:

 1 public class Client {
 2     public static void main(String[] args) {
 3         // 代理类class文件存入本地磁盘方便我们反编译查看源码
 4         System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\code");
 5         // 通过CGLIB动态代理获取代理对象的过程
 6         Enhancer enhancer = new Enhancer();
 7         // 设置enhancer对象的父类
 8         enhancer.setSuperclass(HelloService.class);
 9         // 设置enhancer的回调对象
10         enhancer.setCallback(new MyMethodInterceptor());
11         // 创建代理对象
12         HelloService proxy= (HelloService)enhancer.create();
13         // 通过代理对象调用目标方法
14         proxy.sayHello();
15     }
16 }

  4.运行结果:

   5.总结:

  实现CGLIB动态代理必须实现MethodInterceptor(方法拦截器)接口,这个接口只有一个intercept()方法,这个方法有4个参数:

  1)obj表示增强的对象,即实现这个接口类的一个对象;

  2)method表示要被拦截的方法;

  3)args表示要被拦截方法的参数;

  4)proxy表示要触发父类的方法对象;

  在上面的Client代码中,通过Enhancer.create()方法创建代理对象
JDK动态代理和CGlib代理的区别:

  JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理.

  CGlib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理.

AOP的实现:

  1.如果目标对象实现了接口,默认情况下会采用JDK动态代理实现AOP

  2.如果目标对象实现了接口,可以强制使用CGlib实现AOP,实现方法

    1.添加CGlib库,SPRING_HOME/cglib/*.jar

    2,在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

  3.如果目标对象没有实现接口,必须采用CGlib库,Spring会自动在JDK动态代理和CGlib之间转换.

原文地址:https://www.cnblogs.com/wk-missQ1/p/12643353.html