先简单举个例子,再进行概念介绍。
举个栗子
简单场景
首先定义一个用户登录的简单业务场景。
定义一个接口,和对应的实现类。
public interface UserService {
void login();
}
public class UserServiceImpl implements UserService {
@Override
public void login() {
System.out.println("登录");
}
}
假如我们要在里面进行日志打印工作,你可能会想到直接在login()
方法中进行操作,但是这种情况下会不符合设计模式的开闭原则,简单来说就是应该对于扩展是开放的,但是对于修改是封闭的,在不改变它的源代码的前提下变更它的行为
工作说明
日志打印并不属于相关的登录业务操作,直接在login()
方法中进行日志打印工作不利于后续代码的维护。
创建代理
这种情况下我们通过动态创建代理对象可以解决,具体操作如下:
public class ProxyInvocationHandler implements InvocationHandler {
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), userService.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理开始执行");
Object result = method.invoke(userService, args);
System.out.println("代理执行结束");
return result;
}
}
-
作用:实现了
InvocationHandler
接口,用于动态地创建代理对象,并在方法调用前后添加额外的逻辑。 -
代码详解:
1. invoke()
主要作用就是出在
invoke()
方法上。这是InvocationHandler
接口的核心方法,当代理对象的方法被调用时,会调用此方法。参数解释如下:proxy
:代理对象本身。method
:被调用的方法对象。args
:被调用方法的参数数组。
然后通过
method.invoke(rent, args)
调用目标对象的对应方法。2. Proxy.newProxyInstance()
是 Java 动态代理中的一个核心方法,用于创建动态代理对象。它允许你在运行时为一个或多个接口动态地创建实现类的代理实例,并在调用这些代理实例的方法时,通过自定义的调用处理器(
InvocationHandler
)来拦截和处理方法调用。ClassLoader loader
:指定定义代理类的类加载器。类加载器用于加载代理类及其依赖的类。Class<?>[] interfaces
:指定代理类要实现的接口列表。代理对象必须实现这些接口。InvocationHandler h
:指定调用处理器,用于在代理对象的方法被调用时,自定义方法的执行逻辑。
测试
假如在之前情况下,添加日志相关操作,要么像上文提到的在业务代码中添加;要么在具体的调用方法中添加。不管什么方式都会影响原本的代码内容。
那么通过代理方式的话,具体的日志相关操作可以完全交给代理类完成,不论日志操作有哪些内容,我的主要代码都只用创建代理对象就可以,当日志操作修改的时候,我也不需要修改我的业务代码,只需要在代理类中进行相关业务操作。
public class Client {
public static void main(String[] args) {
UserService userService =new UserService();
userService.login();
}
}
=>
public class Client {
public static void main(String[] args) {
// 创建 ProxyInvocationHandler 实例
ProxyInvocationHandler handler = new ProxyInvocationHandler();
// 指定目标对象
handler.setUserService(new UserServiceImpl());
// 获取代理对象
UserService userService = (UserService) handler.getProxy();
userService.login();
}
}
也可以再次对ProxyInvocationHandler
进行简化,将set和get集成到一个方法中
public class ProxyInvocationHandler2 {
public static UserService getProxy(UserService userService) {
return (UserService) Proxy.newProxyInstance(
ProxyInvocationHandler.class.getClassLoader(),
new Class[]{UserService.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理开始执行");
Object result = method.invoke(userService, args);
System.out.println("代理执行结束");
return result;
}
});
}
}
在使用的时候只需要,基本和之前new对象操作方式一样简单。并且可以完全将不相关的非业务代码移交给代理对象处理器来完成
public class Client {
public static void main(String[] args) {
UserService userService = ProxyInvocationHandler2.getProxy(new UserServiceImpl());
userService.login();
}
}
spring动态代理介绍
原理
上面的情况就是Spring 动态代理,相信用过spring的朋友们会发现,这个作用就和面向切面编程(aop)原理很类似。其实spring动态代理就是 Spring AOP 的核心实现机制之一,它允许在运行时动态地为接口或类创建代理对象,从而在目标方法执行前后添加额外的行为,如事务管理、日志记录、权限验证等。
以下是关于 Spring 动态代理的详细介绍:
spring动态代理有两种方式:
- 基于接口的动态代理(JDK 动态代理) :这是上面案例中使用的方法,也是最常用的动态代理技术之一。当目标类实现了一个或多个接口时,JDK 的
Proxy
类可以为这些接口动态地创建一个代理类。这个代理类实现了与目标类相同的接口,并在方法调用时通过InvocationHandler
来拦截方法调用,并执行自定义的逻辑。- Proxy 类和 InvocationHandler 接口:JDK 的
Proxy
类用于生成动态代理对象,其newProxyInstance()
方法需要三个参数:类加载器、目标对象实现的接口列表以及一个调用处理器对象(实现了InvocationHandler
接口)。 - InvocationHandler 的 invoke 方法:当代理对象的方法被调用时,会自动调用
InvocationHandler
对象的invoke()
方法。这个方法会接收三个参数:代理对象自身、被调用的方法对象、方法参数数组。 - 创建代理对象和方法调用拦截:在 Spring 中,通过
ProxyFactory
创建代理对象,并通过ProxyProcessor
处理代理逻辑。当调用代理对象的方法时,会首先调用ProxyInvocationHandler
的invoke()
方法,在其中可以添加自定义的逻辑,如事务管理、日志记录等,然后再调用目标对象的相关方法。
- Proxy 类和 InvocationHandler 接口:JDK 的
- 基于类的动态代理(CGLIB 动态代理) :如果目标类没有实现任何接口,Spring 会使用 CGLIB 库来创建动态代理。CGLIB 通过生成目标类的子类来实现动态代理功能。它会在内存中动态地生成一个新的类,该类继承了目标类,并重写了目标类的方法,在方法调用前后插入自定义的逻辑
应用场景
- 事务管理 :在企业级应用中,事务管理是必不可少的。Spring 动态代理可以在方法执行前后自动开始和提交事务,从而确保数据的一致性和完整性。
- 日志记录 :通过动态代理,可以在方法执行前后添加日志记录功能,记录方法的执行时间、参数、返回值等信息,方便进行问题排查和性能监控。
- 权限验证 :在方法执行前进行权限检查,确保只有具有相应权限的用户才能调用特定的方法。
- 性能监控 :在方法执行前后添加性能监控代码,测量方法的执行时间,帮助开发人员优化应用的性能。
优点
- 低侵入性 :可以在不修改原有业务代码的情况下,通过动态代理的方式添加额外的功能,实现了业务逻辑与横切关注点的分离。
- 可维护性强 :将通用的逻辑集中在一个地方,便于统一管理和维护,减少了重复代码。
- 灵活性高 :可以在运行时动态地决定是否应用代理逻辑,以及应用哪些代理逻辑。