代理模式
2020-03-10 本文已影响0人
lclandld
一、代理模式的概念
就是为目标对象提供另外一种访问方式,通过访问代理对象来间接访问目标对象。
优点是:
- 降低了系统的耦合度(将客户端与目标对象分离,在一定程度上降低了系统的耦合度)
- 扩展目标对象的功能(在不修改原来方法的情况下,给现有的对象中的方法追加额外的功能)
- 保护目标对象(在客户端与目标对象之间起到一个中介作用和保护目标对象的作用)
缺点是:
- 请求处理速度变慢(在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢)
- 增加了系统的复杂度
二、代理模式的分类

- 作用:其实就是在我们目标方法之前和之后实现增强/限制/修改
- 代理模式分类:静态代理模式(需要自己编写代理类)和动态代理模式(使用反射或者字节码技术生成代理类)
- 动态代理分类:JDK动态代理(采用反射机制回调、必须依赖于接口)、CGLIB动态代理(采用字节码技术调用、采用重写(覆盖)的形式)
三、静态代理
需要定义三个类(接口、目标对象、代理对象),目标对象和代理对象同时实现接口,代理对象中维护一个目标对象的引用,并追加额外的功能
3.1、静态代理的UML图

3.2、静态代理的简单DEMO
- 接口类
/**
* @author lichunlan
* @description 接口
* @since 2020-03-09
*/
public interface Subject {
void request();
}
- 目标对象
/**
* @author lichunlan
* @description 目标对象
* @since 2020-03-09
*/
public class RealSubject implements Subject {
public void request() {
System.out.println("这个是真实目标对象要处理的事情.......");
}
}
- 代理类
/**
* @author lichunlan
* @description 代理对象
* @since 2020-03-09
*/
public class Proxy implements Subject {
public RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
public void request() {
preRequest();
//目标对象的逻辑
realSubject.request();
postRequest();
}
public void preRequest(){
System.out.println("目标方法之前追加的功能.......");
}
public void postRequest(){
System.out.println("目标方法之后追加的功能.......");
}
}
- Client
public class Main {
public static void main(String[] args) {
Proxy proxy = new Proxy(new RealSubject());
proxy.request();
}
}
-
结果
image.png
优点:可以在不修改目标对象的情况下追加新的功能。
缺点:当接口中添加新的方法之后,目标对象和代理对象都要进行维护。
当代理方法越多,重复逻辑越多,前后执行的逻辑可能大部分都是一样的,于是就有了动态代理
四、动态代理
动态代理就是动态的创建代理类的技术,这样代理类就不需要手动实现接口了。
动态代理又分为JDK动态代理和CGLIB动态代理。
4.1、JDK动态代理
4.1.1 JDK动态代理主要点
- 通过java.lang.reflect.Proxy类,动态生成代理类
- 代理类实现的植入的逻辑,需要实现接口InvocationHandler
- 代理类中维护一个目标对象的引用
- 只能基于接口进行动态代理
4.1.2 JDK动态代理简单DEMO
- 接口类
/**
* @author lichunlan
* @description 接口
* @since 2020-03-09
*/
public interface Subject {
void request();
}
- 目标对象
/**
* @author lichunlan
* @description 目标对象
* @since 2020-03-09
*/
public class RealSubject implements Subject {
public void request() {
System.out.println("这个是真实目标对象要处理的事情.......");
}
}
- JDK代理类
/**
* @author lichunlan
* @description JDK动态代理
* @since 2020-03-09
*/
public class JDKProxySubject implements InvocationHandler {
private RealSubject realSubject;
public JDKProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
preRequest();
Object result = null;
//动态反射方法
result = method.invoke(realSubject, args);
postRequest();
return result;
}
public void preRequest(){
System.out.println("JDK动态代理目标方法之前追加的功能.......");
}
public void postRequest(){
System.out.println("JDK动态代理目标方法之后追加的功能.......");
}
}
- Client
public class Main {
public static void main(String[] args) {
//目标对象
RealSubject realSubject = new RealSubject();
JDKProxySubject jdkProxySubject = new JDKProxySubject(realSubject);
//给目标对象创建代理对象
Subject subject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class[]{Subject.class}, jdkProxySubject);
subject.request();
}
}
-
结果
image.png
-
接口类有多个目标方法就很容易了,假设我在接口类中,再添加8个目标方法,这个时候,只需要在RealSubject中实现方法,在Client中直接调用即可
/**
* @author lichunlan
* @description 接口
* @since 2020-03-09
*/
public interface Subject {
void request();
void request1();
void request2();
void request3();
void request4();
void request5();
void request6();
void request7();
void request8();
}
/**
* @author lichunlan
* @description 目标对象
* @since 2020-03-09
*/
public class RealSubject implements Subject {
public void request() {
System.out.println("这个是真实目标对象要处理的事情.......");
}
public void request1() {
System.out.println("这个是真实目标对象1要处理的事情.......");
}
public void request2() {
System.out.println("这个是真实目标对象2要处理的事情.......");
}
public void request3() {
System.out.println("这个是真实目标对象3要处理的事情.......");
}
public void request4() {
System.out.println("这个是真实目标对象4要处理的事情.......");
}
public void request5() {
System.out.println("这个是真实目标对象5要处理的事情.......");
}
public void request6() {
System.out.println("这个是真实目标对象6要处理的事情.......");
}
public void request7() {
System.out.println("这个是真实目标对象7要处理的事情.......");
}
public void request8() {
System.out.println("这个是真实目标对象8要处理的事情.......");
}
}
/**
* @author lichunlan
* @description
* @since 2020-02-26
*/
public class Main {
public static void main(String[] args) {
//目标对象
RealSubject realSubject = new RealSubject();
//JDK代理对象
JDKProxySubject jdkProxySubject = new JDKProxySubject(realSubject);
//给目标对象创建代理对象
Subject subject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class[]{Subject.class}, jdkProxySubject);
subject.request();
subject.request1();
subject.request2();
subject.request3();
subject.request4();
subject.request5();
subject.request6();
subject.request7();
subject.request8();
}
}
4.2、CGLIB动态代理
4.2.1 CGLIB动态代理主要点
- 需要实现接口MethodInterceptor
- 目标对象是没有接口的
- 采用字节码(其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑)
4.2.2 CGLIB动态代理简单DEMO
这种代理方式是为了解决目标对象并没有实现任何接口的时候,使用这种方式生成一个子类对象从而实现对目标对象功能的扩展
- 目标对象(在CGLIB中可以称为父类)
/**
* @author lichunlan
* @description 目标对象
* @since 2020-03-09
*/
public class RealSubject{
public void request() {
System.out.println("这个是真实目标对象要处理的事情.......");
}
}
- CGLIB动态代理
/**
* @author lichunlan
* @description CGLIB动态代理(自定义MethodInterceptor)
* @since 2020-03-09
*/
public class CglibProxySubject implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
/**
* 通过字节码技术生成CGLIB动态代理的代理对象
* @param clazz 目标对象
* @return
*/
public Object getProxy(Class clazz){
//继承被代理类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码技术动态生成代理对象
return enhancer.create();
}
/**
*
* @param o cglib生成的代理对象
* @param method 被代理对象方法
* @param objects 方法入参
* @param methodProxy 代理方法
* @return
* @throws Throwable
*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
preRequest();
Object object = null;
object = methodProxy.invokeSuper(o, objects);
postRequest();
return object;
}
public void preRequest(){
System.out.println("CGLIB动态代理目标方法之前追加的功能.......");
}
public void postRequest(){
System.out.println("CGLIB动态代理目标方法之后追加的功能.......");
}
}
- Client
/**
* @author lichunlan
* @description
* @since 2020-02-26
*/
public class Main {
public static void main(String[] args) {
CglibProxySubject proxy = new CglibProxySubject();
//通过生成子类的方式创建代理类
RealSubject realSubject = (RealSubject)proxy.getProxy(RealSubject.class);
realSubject .request();
}
}
-
结果
image.png
五、CGLIB动态代理和JDK动态代理的比较
- JDK采用反射机制回调 ,CGLIB采用字节码技术调用
- JDK反射和CGLIB字节码生成的区别?
1)JDK必须依赖于接口,不能针对类
2)CGLIB是针对类,主要是为目标类生成一个子类,覆盖其中的方法实现增强,目标类不能声明成final,因为final类不能被继承,无法生成代理。
目标方法也不能声明成final,final方法不能被重写,无法得到处理。 - 在Spring中到底是采用JDK还是采用CGLIB代理呢???
1)目标类(被代理类)实现过接口默认情况下用JDK动态代理
2)目标类(被代理类)实现了接口,可以强制使用CGLIB动态代理
3)目标类(被代理类)没有实现接口就必须采用CGLIB动态代理 - JDK和CGLIB哪个应用得广泛些
相比来说,JDK比CGLIB广泛,但是CGLIB效率比JDK高 - 为什么CGLIB效率比JDK效率高,还用JDK呢???
1)JDK支持原生
2)项目中大多数都采用面向接口编程
所以JDK使用得比CGLIB多
六 、应用场景
- Aop
- mybats mapper对象
- RPC远程调用
- 事务
- 日志
- 权限控制
既然动态代理模式是采用的反射或者字节码动态生成代理类,那下篇文章我将把反射的原理和字节码的原理学习一遍,并进行记录。因为反射在源码中是一个非常重要的概率
参考
慕课网课程"探秘Spring AOP"