蹭蹭动态代理

简单介绍下java实现代理对象的两种方法,JDK动态代理和CGLIB(Code Generate Library)。
JDK动态代理:针对你所调用的方法是接口所定义的方法。动态的创建一个类,通过实现目标类的接口来实现代理。
CGLIB:没有限制。通过继承目标类来创建代理类,实现代理。

先介绍JDK动态代理,上案例:

package pattern.dynamic.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Calendar;
import java.util.GregorianCalendar;

import net.sf.cglib.proxy.Enhancer;

//抽象UserDAO:抽象主题角色  
interface AbstractUserDAO {  
  public Boolean findUserById(String userId);  
}  

//抽象DocumentDAO:抽象主题角色  
interface AbstractDocumentDAO {  
  public Boolean deleteDocumentById(String documentId);  
}  

//具体DocumentDAO类:真实主题角色  
class DocumentDAO implements AbstractDocumentDAO {  
  public Boolean deleteDocumentById(String documentId) {  
      if (documentId.equalsIgnoreCase("D001")) {  
          System.out.println("删除ID为" + documentId + "的文档信息成功!");  
          return true;  
      }  
      else {  
          System.out.println("删除ID为" + documentId + "的文档信息失败!");  
          return false;  
      }  
  }  
}

//具体UserDAO类:真实主题角色  
class UserDAO implements AbstractUserDAO {  
  public Boolean findUserById(String userId) {  
      if (userId.equalsIgnoreCase("张无忌")) {  
          System.out.println("查询ID为" + userId + "的用户信息成功!");  
          return true;  
      }  
      else {  
          System.out.println("查询ID为" + userId + "的用户信息失败!");  
          return false;  
      }  
  }  
}  

//自定义请求处理程序类  
class DAOLogHandler implements InvocationHandler {  
  private Calendar calendar;  
  private Object object;  

  public DAOLogHandler() {      
  }  

  //自定义有参构造函数,用于注入一个需要提供代理的真实主题对象  
  public Object bind (Object object) {  
      this.object = object;  
      return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
  }  

  //实现invoke()方法,调用在真实主题类中定义的方法  
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
      beforeInvoke();  
      //invoke(Object proxy, Method method, Object[] args)方法中的
      //Object proxy是Proxy.newProxyInstance的返回的代理对象,不是目标对象Person,
      //因此希望执行原目标对象的方法时,method.invoke(target, args);所传对象是原目标对象,而不是代理对象proxy。 
      Object result = method.invoke(object, args); //转发调用  
      afterInvoke();  
      return result;  
  }  

  //记录方法调用时间  
  public void beforeInvoke(){  
      calendar = new GregorianCalendar();  
      int hour = calendar.get(Calendar.HOUR_OF_DAY);  
      int minute = calendar.get(Calendar.MINUTE);  
      int second = calendar.get(Calendar.SECOND);  
      String time = hour + ":" + minute + ":" + second;  
      System.out.println("调用时间:" + time);  
  }  

  public void afterInvoke(){  
      System.out.println("方法调用结束!" );  
  }  
}

public class TestJDKProxy {
    public static void main(String[] args) {
        DAOLogHandler daoLogHandler = new DAOLogHandler();  

        UserDAO userDAO = new UserDAO();
        //Proxy.newProxyInstance的返回结果和目标对象实现了同样的接口,但他们之间不能相互转化。
        //即abstractUserDAO=(UserDAO)daoLogHandler.bind(userDAO);是错误的,报java.lang.ClassCastException
        AbstractUserDAO abstractUserDAO = (AbstractUserDAO)daoLogHandler.bind(userDAO);
        abstractUserDAO.findUserById("张无忌"); //调用代理对象的业务方法  

        DocumentDAO documentDAO = new DocumentDAO();
        AbstractDocumentDAO abstractDocumentDAO = (AbstractDocumentDAO)  daoLogHandler.bind(documentDAO);
        abstractDocumentDAO.deleteDocumentById("D002");
        abstractDocumentDAO.deleteDocumentById("D001");

    }
    //这就是JDK动态代理的原理,目前这些拦截都是硬编码写死的
}

/*
调用时间:10:34:13
查询ID为张无忌的用户信息成功!
方法调用结束!
调用时间:10:34:13
删除ID为D002的文档信息失败!
方法调用结束!
调用时间:10:34:13
删除ID为D001的文档信息成功!
方法调用结束!
 */

这个案例主要参考了一篇写的很好的博客,链接在下面。我做了一些优化,添加了一些说明。

(1) Proxy类

Proxy类提供了用于创建动态代理类和实例对象的方法,它是所创建的动态代理类的父类,它最常用的方法如下:

public static Class getProxyClass(ClassLoader loader,Class... interfaces):该方法用于返回一个Class类型的代理类,在参数中需要提供类加载器并需要指定代理的接口数组(与真实主题类的接口列表一致)。
public static Object newProxyInstance(ClassLoader loader, Class[]interfaces, InvocationHandler h):该方法用于返回一个动态创建的代理类的实例,方法中第一个参数loader表示代理类的类加载器,第二个参数interfaces表示代理类所实现的接口列表(与真实主题类的接口列表一致),第三个参数h表示所指派的调用处理程序类。

(2) InvocationHandler接口

InvocationHandler接口是代理处理程序类的实现接口,该接口作为代理实例的调用处理者的公共父类,每一个代理类的实例都可以提供一个相关的具体调用处理者(InvocationHandler接口的子类)。在该接口中声明了如下方法:

public Object invoke(Objectproxy, Method method, Object[] args):该方法用于处理对代理类实例的方法调用并返回相应的结果,当一个代理实例中的业务方法被调用时将自动调用该方法。invoke()方法包含三个参数,其中第一个参数proxy表示代理类的实例,第二个参数method表示需要代理的方法,第三个参数args表示代理方法的参数数组。

动态代理类需要在运行时指定所代理真实主题类的接口,客户端在调用动态代理对象的方法时,调用请求会将请求自动转发给InvocationHandler对象的invoke()方法,由invoke()方法来实现对请求的统一处理。

CGLIB代理:(案例来自于另一篇博客)

package pattern.dynamic.proxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.InvocationHandler;


interface Say {

    public void sayHello();
}

class Person implements Say{

    private String name;

    public Person() {
        super();
    }

    public Person(String name) {
        super();
        this.name = name;
    }

    @Override
    public void sayHello() {
        System.out.println("My name is "+name+"!");
        throw new RuntimeException();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }


}

class Animals implements Say{

    @Override
    public void sayHello() {
        System.out.println("I am a animal");
    }

}

class CGLIBProxy{
    @SuppressWarnings("unchecked")
    public static  T createProxy(final T t){
        Enhancer enhancer=new Enhancer();
        enhancer.setClassLoader(CGLIBProxy.class.getClassLoader());
        enhancer.setSuperclass(t.getClass());
        enhancer.setCallback(new InvocationHandler(){

            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                Object ret=null;
                if(method.getName().equals("sayHello")){
                    doBefore();
                    try {
                        ret=method.invoke(t, args);
                    } catch (Exception e) {
                        doThrowing();
                    }
                    doAfter();
                }
                return ret;
            }

        });
        return (T)enhancer.create();
    }

    private static void doThrowing() {
        System.out.println("AOP say throw a exception");
    }

    private static void doBefore() {
        System.out.println("AOP before say");
    }

    private static void doAfter() {
        System.out.println("AOP after say");
    }

}
public class TestCGLIBProxy {
    public static void main(String[] args) {
        Person p = new Person("Warrior");
        p = CGLIBProxy.createProxy(p);
        p.sayHello();

        System.out.println("-------------------------------");

        Animals animals = new Animals();
        animals = CGLIBProxy.createProxy(animals);
        animals.sayHello();
    }

/*
AOP before say
My name is Warrior!
AOP say throw a exception
AOP after say
-------------------------------
AOP before say
I am a animal
AOP after say
*/
}

使用cglib的Enhancer来创建,创建出的代理对象继承了指定的class。
(1)enhancer.setClassLoader:也是指定类加载器,将创建出来的新类加载到jvm中。
(2)enhancer.setSuperclass(t.getClass()):设置目标类作为代理对象的父类。
(3)enhancer.setCallback:设置一个回调函数,每次调用代理类的方法时,先执行该回调函数。
友情提示:CGLIB动态代理由于jar包的冲突会出现很多问题,如果你遇到啦,
Click Here

总结一下:
(1)当你所调用的目标对象的方法是接口所定义的方法时,可以使用JDK动态代理或者Cglib。即当你的目标类虽然实现了接口,但是所调用的方法却不是接口方法时,就无法使用JDK动态代理,因为JDK动态代理的原理就是实现和目标对象同样的接口,因此只能调用那些接口方法。
(2)Cglib则没有此限制,因为它所创建出来的代理对象就是目标类的子类,因此可以调用目标类的任何方法(除去final方法,final方法不可继承),都会进行拦截。

所以SpringAOP会优先选择JDK动态代理,当调用方法不是接口方法时,只能选择Cglib了。

参考:
https://blog.csdn.net/LoveLion/article/details/8116704
https://my.oschina.net/pingpangkuangmo/blog/376303

你可能感兴趣的:(spring,java)