JAVA动态代理
动态代理(Dynamic Proxy) 是Java中实现 AOP(面向切面编程) 的核心技术之一。它允许开发者在运行时动态生成代理对象,从而在不修改原始代码的前提下,为方法调用添加额外的逻辑(如日志、事务、权限控制等)。其核心优势包括:
- 解耦业务逻辑与非功能性需求:将核心业务逻辑(如用户管理)与横切关注点(如日志、事务)分离,提升代码可维护性。
- 减少冗余代码:通过统一代理逻辑,避免为每个类重复编写相同的增强代码。
- 灵活扩展:动态代理可适应接口或类的变化,只需调整代理逻辑,无需修改大量静态代理类。
1、代理模式
JAVA的动态代理是与设计模式中的代理模式相关的,什么是代理模式呢?
代理模式是指通过创建一个**代理对象(Proxy)控制对目标对象(Target)**的访问。代理对象在调用目标方法前后可以执行额外操作。
-
ISubject:定义了Proxy和RealSubject的公共对外接口方法。
-
RealSubject:真正实现业务逻辑的类,定义Proxy所代表的真实体。
-
Proxy:用来代理和封装真实体,使得代理可以访问真实体。
如果根据字节码的创建时机来分类,可以分为静态代理和动态代理:
- 所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
- 而动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件。
2、静态代理
首先来编写静态代理实例,直接创建UserService接口与其实现类UserServiceImpl以及代理类Proxy。
UserService接口:即是代理模式结构图中的ISubject接口。
public interface UserService {
/**
* 添加用户
* @param username 用户名
*/
void addUser(String username);
/**
* 删除用户
* @param username 用户名
*/
void deleteUser(String username);
}
UserServiceImpl类:真实体类。
public class UserServiceImpl implements UserService {
@Override
public void addUser(String username) {
System.out.println("添加用户: " + username);
}
@Override
public void deleteUser(String username) {
System.out.println("删除用户: " + username);
}
}
Proxy:代理类。通过代理类输出一些日志信息,即对原本的实体类的功能进行增强。
public class UserServiceProxy implements UserService {
private UserService target; // 被代理的对象
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void addUser(String username) {
before();
target.addUser(username);
after();
}
@Override
public void deleteUser(String username) {
before();
target.deleteUser(username);
after();
}
private void before() {
// 在执行方法之前执行的操作
System.out.println("Log start time:" + LocalDateTime.now());
}
private void after() {
// 在执行方法之后执行的操作
System.out.println("Log end time:" + LocalDateTime.now());
}
}
创建一个客户端进行测试:
public class Main {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
userServiceProxy.addUser("张三");
System.out.println();
userServiceProxy.deleteUser("张三");
}
}
输出结果:
可见,静态代理就是通过代理类去访问目标对象(真实体),从而屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情,从而达到功能增强的目的。
静态代理虽然实现简单,但是存在很大缺陷。当在接口中增加一些方法时,不仅在真实体中需要进行相应的实现,在代理类中也需要进行实现,十分的麻烦,且工作量大。其次,当需要代理多个类时,通常需要为每个类创建一个代理类。这会导致代码冗余和维护困难。故而产生动态代理方法,也就是动态的生成代理类。
3、动态代理
(1)JDK动态代理
JDK动态代理主要涉及两个类:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler。
Proxy主要是用其newProxyInstance方法获取动态生成的代理类对象,通过实现InvocationHandler接口并重写invoke方法实现调用逻辑。具体的看如下代码,仍然使用之前的UserService与UserServiceImpl:
逻辑处理类:实现InvocationHandler接口并重写invoke
public class LogInvocationHandler implements InvocationHandler {
private Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
/**
* 代理的方法逻辑
*
* @param proxy 代理对象
* @param method 代理对象调用的方法
* @param args 当前method方法的参数
* @return 代理结果
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args); // 利用反射来调用传入的目标对象(target)的方法
after();
return result;
}
private void before() {
// 在执行方法之前执行的操作
System.out.println("Log start time:" + LocalDateTime.now());
}
private void after() {
// 在执行方法之后执行的操作
System.out.println("Log end time:" + LocalDateTime.now());
}
}
代理工厂类:获取动态生成的代理类对象。
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标类的类加载器
target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
new LogInvocationHandler(target) // 代理对象对应的自定义InvocationHandler
);
}
}
测试:
public class Main {
public static void main(String[] args) {
UserService proxy = (UserService) JdkProxyFactory.getProxy(new UserServiceImpl());
proxy.addUser("张三");
System.out.println();
proxy.deleteUser("张三");
}
}
结果输出:
从上可以看出,JDK动态代理并不用去编写代理类,只需要维护需要代理部分的逻辑,主要步骤如下:
- 定义一个接口及其实现类;
- 自定义
InvocationHandler并重写invoke方法,在invoke方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑; - 通过
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法创建代理对象;
也就是说:我们通过Proxy 类的 newProxyInstance()创建的代理对象在调用方法的时候,实际就会调用实现InvocationHandler 接口中的invoke方法。而这里就是实现一些额外功能的地方,但需要记得返回目标方法处理的结果,最后就可以通过代理获取到结果,并实现功能的增加。
(2)CGLIB动态代理
静态代理与JDK动态代理都基于接口的,所以要求代理类一定是有定义接口的,这也存在着一定局限。而CGLIB动态代理就避免这一问题。CGLIB动态代理是通过继承的方式生成目标类的子类来实现代理类的。其流程是与JDK动态代理类似的,但原理可不是一样的。JDK动态代理是基于反射机制实现的,而CGLIB动态代理是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。
使用CGLIB是需要添加依赖的,直接在maven工程的pom.xml添加如下依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
直接上代码,首先这里不在需要接口了,我们直接定义一个普通类UserService。
public class UserService {
/**
* 添加用户
* @param username 用户名
*/
public void addUser(String username){
System.out.println("添加用户: " + username);
};
/**
* 删除用户
* @param username 用户名
*/
public void deleteUser(String username){
System.out.println("删除用户: " + username);
};
}
处理自定义逻辑的代码,实现MethodInterceptor类
public class LogMethodInterceptor implements MethodInterceptor {
/**
*
* @param object 被代理的对象(需要增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法参数
* @param methodProxy 对方法的代理,invokeSuper方法表示对被代理对象方法的调用
* @return 代理结果
* @throws Throwable 异常
*/
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(object, args);
after();
return result;
}
private void before() {
// 在执行方法之前执行的操作
System.out.println("Log start time:" + LocalDateTime.now());
}
private void after() {
// 在执行方法之后执行的操作
System.out.println("Log end time:" + LocalDateTime.now());
}
}
获取代理类:
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new LogMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}
测试与结果输出:
public static void main(String[] args) {
UserService proxy = (UserService) CglibProxyFactory.getProxy(UserService.class);
proxy.addUser("张三");
System.out.println();
proxy.deleteUser("张三");
}
CGLIB 动态代理步骤:
- 定义一个类;
- 自定义
MethodInterceptor并重写intercept方法,intercept用于拦截增强被代理类的方法,和 JDK 动态代理中的invoke方法类似; - 通过
Enhancer类的create()创建代理类;
需要注意的是,使用CGLIB时注意JDK8版本,JDK8以上会出现报错,当然如果只能使用高版本JDK也是有方法可以解决的,可以自行度娘。
4、总结
- 动态代理 是Java实现AOP的核心技术,通过运行时生成代理对象解耦业务逻辑与非功能性需求。
- JDK动态代理 适用于接口代理,CGLIB 适用于无接口的类代理。
- 实际开发中,优先选择JDK动态代理;若目标类无接口,则使用CGLIB。