静态代理模式的缺点
当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来:
1、当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,如果只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大;如果新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类。
2、 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。
动态代理
类的动态生成
这关系到Java虚拟机的类加载过程,JVM会通过一个类的全限定名来获取定义此类的二进制字节流,并将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构,也就是Java中的一个类在JVM中就是一个数据结构。
动态代理就是想办法操作类的字节码文件,在程序运行期间可以修改或新建类。
常见的字节码操作类库
- ObjectWeb ASM:一个Java字节码操作框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
- JDK动态代理:代理对象是由JDK动态生成的,JDK动态代理基于拦截器和反射来实现。JDK代理是不需要第三方库支持的,只需要JDK环境就可以进行实现。
- CGLIB(Code Generation Library):一个功能强大,高性能和高质量的代码生成库,用于扩展JAVA类并在运行时实现接口。
- Javassist:Java的加载时反射系统,它是一个用于在Java中编辑字节码的类库;它使Java程序能够在运行时定义新类,并在JVM加载之前修改类文件。
- Instrumentation 是 Java SE 5 的新特性,它把 Java 的 instrument 功能从本地代码中解放出来,使之可以用 Java 代码的方式解决问题。使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。
- AspectJ:AspectJ 库属于静态织入,原理是静态代理。
Java中动态代理的实现一般分为两种:JDK动态代理以及CGLIB动态代理。
两种最常见的方式:
- 通过实现接口的方式 -> JDK动态代理
- 通过继承类的方式 -> CGLIB动态代理
JDK动态代理
条件:
- 必须实现InvocationHandler接口
- 使用Proxy.newProxyInstance产生代理对象
- 被代理的对象必须要实现接口
JDK动态代理主要涉及两个类:java.lang.reflect.Proxy
和 java.lang.reflect.InvocationHandler
JDK实现代理需要使用InvocationHandler接口:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。
JDK实现代理需要使用Proxy类的newProxyInstance方法:
public static Object newProxyInstance(ClassLoader loader, Class [] interfaces, InvocationHandler handler)
构造实现指定接口的代理类的一个新实例,所有方法会调用给定处理器对象的 invoke 方法。
三个参数依次为:
ClassLoader loader
指定当前目标对象使用类加载器,用null表示默认类加载器
Class [] interfaces
需要实现的接口数组
InvocationHandler handler
调用处理器,执行目标对象的方法时,会触发调用处理器的方法,从而把当前执行目标对象的方法作为参数传入
UserService
/**
* 代理接口
*/
public interface UserService {
void select();
void update();
}
UserServiceImpl
/**
* 需要被代理的类
*/
public class UserServiceImpl implements UserService {
@Override
public void select() {
System.out.println("查询 select by id");
}
@Override
public void update() {
System.out.println("更新 update by id");
}
}
LogHandler
/**
* 编写一个调用逻辑处理器 LogHandler 类,提供日志增强功能,并实现 InvocationHandler 接口;
*
* 在 LogHandler 中维护一个目标对象 target,这个对象是被代理的对象(真实主题角色);
* 在 invoke 方法中编写方法调用的逻辑处理
*/
public class LogHandler implements InvocationHandler {
/**
* 被代理的对象
*/
private Object target;
public LogHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);
after();
return result;
}
private void before() {
System.out.println(String.format("task start time: %s", System.currentTimeMillis()));
}
private void after() {
System.out.println(String.format("task end time: %s", System.currentTimeMillis()));
}
}
Client
public class Client {
public static void main(String[] args) {
// 1. 创建被代理的对象,UserService接口的实现类
UserServiceImpl userService = new UserServiceImpl();
// 2. 获取对应的 ClassLoader
ClassLoader classLoader = userService.getClass().getClassLoader();
// 3. 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口UserService
Class[] interfaces = userService.getClass().getInterfaces();
// 4. 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用
LogHandler logHandler = new LogHandler(userService);
/*
5.根据上面提供的信息来创建代理对象 在这个过程中:
a.JDK会通过根据传入的参数信息动态地在内存中创建和.class文件等同的字节码
b.然后根据相应的字节码转换成对应的class,
c.然后调用newInstance()创建代理实例
*/
UserService proxyInstance = (UserService)Proxy.newProxyInstance(classLoader, interfaces, logHandler);
proxyInstance.select();
proxyInstance.update();
// ============================================== Java8 =========================================
UserService proxyInstance1 = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserService.class},
(proxy, method, args1) -> {
System.out.println(String.format("task start time: %s", System.currentTimeMillis()));
try {
return method.invoke(userService, args1);
} finally {
System.out.println(String.format("task end time: %s", System.currentTimeMillis()));
}
}
);
proxyInstance1.select();
proxyInstance1.update();
}
}
--------------------输出-------------------
task start time: 1583423120396
查询 select by id
task end time: 1583423120400
task start time: 1583423120400
更新 update by id
task end time: 1583423120400
task start time: 1583423120468
查询 select by id
task end time: 1583423120469
task start time: 1583423120469
更新 update by id
task end time: 1583423120470
CGLIB动态代理
如果目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用构建目标对象子类的方式实现代理,这种方法就叫做Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)。
Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的子类。
条件:
- 需要引入CGLIB第三方包;
- 被代理的类不能为final;
- 被代理对象的方法如果为final/static,那么就不会执行被代理对象额外的业务方法;
Cglib代理需要MethodInterceptor接口,这个接口只有一个intercept()方法:
public interface MethodInterceptor extends Callback {
Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}
这个方法有4个参数:
obj
表示增强的对象,即实现这个接口类的一个对象;
method
表示要被拦截的方法;
args
表示要被拦截方法的参数;
proxy
表示要触发父类的方法对象;
UserService
/**
* 目标对象,没有实现任何接口
*/
public class UserService {
public void save() {
System.out.println("正在保存数据...");
}
}
ProxyFactory
/**
* Cglib子类代理工厂
* 对UserService在内存中动态构建一个子类对象
*/
public class ProxyFactory implements MethodInterceptor {
/**
* 目标对象
*/
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
/**
* 给目标对象创建一个代理对象
*/
public Object getProxyInstance() {
// 1.工具类
Enhancer en = new Enhancer();
// 2.设置父类
en.setSuperclass(target.getClass());
// 3.设置回调函数
en.setCallback(this);
// 4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("==========前置处理==========");
// 执行目标对象的方法
Object result = method.invoke(target, objects);
System.out.println("==========后置处理==========");
return result;
}
}
Client
public class Client {
public static void main(String[] args) {
// 目标对象
UserService service = new UserService();
// 代理对象
UserService proxyService = (UserService) new ProxyFactory(service).getProxyInstance();
// 执行代理对象的方法
proxyService.save();
}
}
----------------输出---------------
==========前置处理==========
正在保存数据...
==========后置处理==========
源码
总结
由于JDK动态代理基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象,所以如果目标对象有实现接口,那么就用JDK代理。
由于Cglib动态代理基于ASM机制实现,通过生成业务类的子类作为代理类,所以如果目标对象没有实现接口,用Cglib代理。