1. 代理模式概述
在代理模式中,使用一个类来代表另一个类的功能。
在代理模式中,我们创建具有现有对象能力的对象,以控制对这个对象的访问,向外界提供功能接口。
(1) 适用情况
通常,可以通过代理对象来对被代理对象进行功能增强、安全控制等等,完成一些代理对象本身不具备的能力。
(2) 优点
职责清晰,且具有高拓展性。
(3) 缺点
增加了系统的复杂度,且代理对象可能会导致响应速度变慢。
2. 代理模式实例
我们现在有一个类UserDao,里面实现了save方法用来保存数据。但是为了考虑到数据库的ACID特性,需要引入事务,这里就可以考虑使用代理模式。
代理模式主要有三种实现方式:静态代理,JDK动态代理和CGLib动态代理。
(1) 实现被代理对象的接口和类
public interface UserDao {
void save();
}
复制代码
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("保存数据");
}
}
复制代码
(2) 静态代理
public class UserDaoProxy implements UserDao {
UserDao userDao;
public UserDaoProxy(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
System.out.println("开启事务");
userDao.save();
System.out.println("提交事务");
}
}
复制代码
public class StaticDemo {
public static void main(String[] args) {
// 创建被代理对象
UserDao userDao = new UserDaoImpl();
// 创建代理对象
UserDaoProxy proxy = new UserDaoProxy(userDao);
System.out.println("调用被代理对象方法:");
userDao.save();
System.out.println("\n调用代理对象方法:");
proxy.save();
}
}
复制代码
静态代理实现简单,但是却要求代理类和被代理类实现同一接口,那么当接口发生变动时,代理类和被代理类都需要进行修改。
(3) JDK动态代理
public class ProxyDemo {
public static void main(String[] args) {
// 创建被代理对象
UserDao userDao = new UserDaoImpl();
// 创建代理对象
UserDao proxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(),
userDao.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开启事务");
Object invoke = method.invoke(userDao, args);
System.out.println("提交事务");
return invoke;
}
});
System.out.println("调用被代理对象方法:");
userDao.save();
System.out.println("\n调用代理对象方法:");
proxy.save();
}
}
复制代码
JDK动态代理主要涉及到两个类:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。
Proxy类通过代理类的类加载器和实现的所有接口,以及InvocationHandler类的对象来创建代理对象。
InvocationHandler类中的invoke方法定义了代理对象调用方法时希望执行的动作,其中需要使用反射的方式来调用被代理对象中的方法。
值得注意的是,生成的代理对象不能强转为类类型,只能是接口类型,否则会抛出java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to xxx异常。
(4) 动态代理之cglib:
public class CgLibDemo {
public static void main(String[] args) {
// 创建被代理对象
UserDao userDao = new UserDaoImpl();
// 创建增强器
Enhancer enhancer = new Enhancer();
// 注意这里是类,不是接口
enhancer.setSuperclass(UserDaoImpl.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("开启事务");
// 注意这里不是反射,是方法调用,里边才是反射
Object proxy = methodProxy.invokeSuper(o, objects);
System.out.println("提交事务");
return proxy;
}
});
// 创建代理对象
UserDao proxy = (UserDao) enhancer.create();
System.out.println("调用被代理对象方法:");
userDao.save();
System.out.println("\n调用代理对象方法:");
proxy.save();
}
}
复制代码
CgLib是一种功能强大、高性能的代码生成包。
它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
SpringAOP中就广泛的使用了CgLib。
值得注意的是,增强器在设置Superclass时设置的是类类型,而不是接口类型。否则会抛出java.lang.NoSuchMethodError: java.lang.Object.save()V异常。
并且,在MethodInterceptor对象的intercept方法中,并不是反射,而是调用invokeSuper方法。如果错误的写成invoke方法,会导致递归调用自身,最终抛出java.lang.StackOverflowError异常。
运行结果:
3. 一些思考
JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
通常情况下,可以使用JDK动态代理来实现代理模式。但是上边提到过,使用JDK动态代理要求被代理的类有实现的接口,如果没有的话,CgLib就是一个更好的选择,它使用的是方法拦截的技术,比使用java反射的JDK动态代理有更高的性能。
参考引用:
代理模式:www.runoob.com/design-patt…
CGLIB(Code Generation Library) 介绍与原理
:www.runoob.com/w3cnote/cgl…
Java动态代理详解:www.cnblogs.com/whirly/p/10…
近期评论