动态代理的几种实现方式及优缺点
动态代理:是使用反射和字节码,在运行期间创建指定接口或类的子类以及它的实例对象的一项技术,通过这个技术可以对代码进行无侵入式的增强。
源文件生成实例对象的过程如下:
关于动态代理的底层原理,在另一篇文章中已经介绍过了。 你必须要学会的动态代理
java的动态代理技术的实现主要有两种方式:
- JDK原生动态代理
- CGLIB动态代理
JDK原生动态代理:
使用到一个类Proxy和一个接口InvocationHandler。Proxy是所有动态类的父类,它提供一个静态方法来创建动态代理的class对象和实例,这个方法就是newProxyInstance(),
InvocationHandler:每个动态代理实例都有一个相关联的InvocationHandler方法,在代理实例调用时,方法调用将被转发到InvocationHandler的invole方法。
代码实验:
User类有两个属性,name和age,代码省略
UserService:
public interface UserService {
public void addUser(User user);
}
UserServiceImpl:
public class UserServiceImpl implements UserService {
@Override
public void addUser(User user) {
System.out.println("新增用户成功,数据为:"+user.toString());
}
}
UserServiceProxy:实现InvocationHandler,重写invoke方法,做前置增强,判断用户年龄是否大于0,如果小于等于0就报运行时异常
public class UserServiceProxy implements InvocationHandler {
private Object realObj;
private static Logger logger = Logger.getLogger(UserServiceProxy.class.getName());
public Object getRealObj() {
return realObj;
}
public void setRealObj(Object realObj) {
this.realObj = realObj;
}
public UserServiceProxy(Object realObj) {
super();
this.realObj = realObj;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
if(objects!=null && objects.length>0 && objects[0] instanceof User){
User user = (User)objects[0];
if(user.getAge()<=0){
throw new RuntimeException("用户年龄不符合");
}
}
Object ret = method.invoke(realObj,objects);
logger.info("数据添加成功");
return ret;
}
}
Client:
public class Client {
public static void main(String[] args) {
User user =new User();
user.setName("张三");
user.setAge(0);
UserService us = new UserServiceImpl();
UserServiceProxy usp = new UserServiceProxy(us);
UserService proxy = (UserService) Proxy.newProxyInstance(us.getClass().getClassLoader(),us.getClass().getInterfaces(),usp);
proxy.addUser(user);
}
}
运行结果:
Exception in thread "main" java.lang.RuntimeException: 用户年龄不符合
at DynamicProxy.JDK.UserServiceProxy.invoke(UserServiceProxy.java:30)
at com.sun.proxy.$Proxy0.addUser(Unknown Source)
at DynamicProxy.JDK.Client.main(Client.java:13)
看来过滤起了作用。将年龄改为大于0的数时,能够正常运行。
CGLIB动态代理
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理:
- Enhancer:来指定要代理的目标对象,实际处理代理逻辑的对象,最终通过调用create()方法得到代理对象,对这个对象的所有非final方法的调用都会转发给MethodInterceptor;
- MethodInterceptor:动态代理对象的方法调用丢回转发到intercept方法进行增强
UserServiceImpl类:无需接口
public class UserServiceImpl {
public void addUser(User user) {
System.out.println("新增用户成功,数据为:"+user.toString());
}
}
UserServiceProxy:逻辑几乎一样
public class UserServiceProxy implements MethodInterceptor {
private static Logger logger = Logger.getLogger(UserServiceProxy.class.getName());
public Object intercept(Object o, Method method, Object[] objects,MethodProxy proxy) throws Throwable {
if(objects!=null && objects.length>0 && objects[0] instanceof User){
User user = (User)objects[0];
if(user.getAge()<=0){
throw new RuntimeException("用户年龄不符合");
}
}
Object ret = proxy.invokeSuper(realObj,objects);
logger.info("数据添加成功");
return ret;
}
}
Client
public class Client {
public static void main(String[] args) {
User user =new User();
user.setName("张三");
user.setAge(0);
Enhancer enhancer = new EnHancer();
enhancer.setSuperClass(UserServiceImpl.class);
enhancer.setCallback(new UserServiceProxy());
UserServiceImpl usi = (UserServiceImpl) enhancer.create();
usi.addUser(user);
}
}
总结:
- JDK原生动态代理是Java原生支持的,不需要任何外部依赖,但是它只能基于接口进行代理(需要代理的对象必须实现于某个接口)
- CGLIB通过继承的方式进行代理(让需要代理的类成为Enhancer的父类),无论目标对象有没有实现接口都可以代理,但是无法处理final的情况。
上一篇: Java静态代理和动态代理
下一篇: java实现线程通信的几种方式