这是我参与8月更文挑战的第21天,活动详情查看:8月更文挑战
1. 代理模式
什么是代理模?
举个例子,你对外提供服务,但是你又不想什么阿猫阿狗都来烦你,于是你找了个人作为你的代理人,用来代替他人对你的直接访问,这就是代理模式。
简单来说,就是使用代理对象来代替对真实对象的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
而代理模式又分为两种:
- 静态代理模式:静态代理中,我们对目标对象的每个方法的增强都是手动完成的
- 动态代理模式:相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类(CGLIB 动态代理机制)
2. 静态代理
静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活。实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景,他的实现步骤是这样的:
- 定义一个接口以及其实现类
- 创建一个代理类,同样实现这个接口
- 将被代理的类注入这个代理类中,然后在代理类中调用具体的方法
这样做虽然屏蔽了被代理类的具体实现,可以通过代理类来访问具体的业务逻辑,但是这种做法,一旦要具体实现要改话,那么代理类和被代理类都要修改具体的代码,非常的不方便
3. 动态代理
对于动态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类(通过 CGLIB 动态代理机制)
对于 Java 来说,实现动态代理的方式有很多种,比如 JDK 的动态代理,CGLB 动态代理;依赖于动态代理机制的框架也有很多,比如 Spring AOP,RPC 框架;所以,学会动态代理对我们理解其他框架也是很有帮助的
3.1 JDK 的动态代理机制
3.1.1 实现步骤
- 定义一个接口及其实现类
- 再创建一个类实现
InvocationHandler接口并重写invoke方法,在invoke方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑。 - 通过
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法创建代理对象
接下来通过代码演示一下:
步骤一:定义一个接口及其实现类,一个实现类
定义一个发送消息的接口:
public interface MsgService {
String send(String message);
}
public class MsgServiceImpl implements MsgService {
@Override
public String send(String message) {
System.out.println("发送了一次消息");
return message;
}
}
步骤二:创建一个类实现 InvocationHandler 接口并重写 invoke 方法,在 invoke 方法中我们会调用被代理类的方法
动态代理类:
public class MsgInvocationHandler implements InvocationHandler {
/** 被代理的真实对象 */
Object target;
public MsgInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(target, args);
return result;
}
}
步骤三:实际使用
public static void main(String[] args) {
MsgService msgService = (MsgService) Proxy.newProxyInstance(
MsgServiceImpl.class.getClassLoader(),
MsgServiceImpl.class.getInterfaces(),
new MsgInvocationHandler(new MsgServiceImpl()));
msgService.send("你愁啥");
}
运行结果:
发送了一次消息
3.1.2 小结
- 可以看到,在 JDK 动态代理的实现中,主要是通过
Proxy.newProxyInstance()方法,来动态的生产代理对象 - 通过
Proxy.newProxyInstance()创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler接口的类的invoke()方法
JDK 动态代理有它的问题,那就是只能代理实现了接口的类,为了解决这个问题,就有了 CGLIB 动态代理机制
3.2 GLIB 动态代理机制
3.2.1 实现步骤
- 定义一个类
- 自定义
MethodInterceptor并重写intercept()方法,intercept()用于拦截增强被代理类的方法,和 JDK 动态代理中的invoke()方法类似; - 通过
Enhancer类的create()创建代理类;
接下来通过代码示例演示一下:
步骤一:引入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
步骤二:定义一个需要被代理的类
public class MsgService {
public String send(String message) {
System.out.println("发送了一次消息");
return message;
}
}
步骤三:使用 MethodInterceptor
public class MsgMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = methodProxy.invokeSuper(o, objects);
return result;
}
}
步骤四,实际使用:
public static void main(String[] args) {
Class clazz = MsgService.class;
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new MsgMethodInterceptor());
// 创建代理类
MsgService msgService = (MsgService) enhancer.create();
msgService.send("你瞅啥瞅");
}
程序输出:
发送了一次消息
3.2.2 小结
- CGLIB 动态代理机制中
MethodInterceptor接口和Enhancer类是核心 - 你需要自定义
MethodInterceptor并重写intercept方法,intercept用于拦截增强被代理类的方法 - 通过
Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是MethodInterceptor中的intercept方法
4. 动态代理与静态代理之间的区别
- 灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的
- JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的