简单介绍下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