什么是代理模式
定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
举个例子来说明:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了。用图表示如下:
为什么要用
- 让客户端类只关注接口,不需要关注接口内部。
- 开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
什么场景用
真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
设计模式六大原则
- 单一职责原则(SRP, Single Responsibility Principle):对一个类(也可以是一个方法)而言,应该只有一个引起它代码发生变化的原因。
- 开放、封闭原则(OCP,Open Closed Principle):软件实体(类、模块、方法等)应该可以扩展,但不可以修改。也就是对扩展开放(Open for extension),对更改封闭(Closed for modificatioin)
- 依赖倒转原则(DIP,Dependency Inversion Principle):高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于具体实现细节,而具体实现细节应该依赖于抽象。
- 里氏替换原则(LSP,Liskov Substitution Principle):子类必须能够替换掉它们的父类。这样父类才能真正被复用,而子类也能够在父类的基础上增加自己新的功能。
- 接口分离原则(ISP,Interface Segregation Principle):客户类不应被强迫依赖那些它们不需要的接口。
当客户类被强迫依赖那些它们不需要的接口时,则这些客户类不得不受制于这些接口。这无意间就导致了所有客户类之间的耦合。换句话说,如果一个客户类依赖了一个类,这个类包含了客户类不需要的接口,但这些接口是其他客户类所需要的,那么当其他客户类要求修改这个类时,这个修改也将影响这个客户类。通常我们都是在尽可能的避免这种耦合,所以我们需要竭尽全力地分离这些接口。 - 最少知识原则(LKP,Least Knowledge Principle)也叫迪米特法则(Law of Demeter):只和自己直接的 “朋友” 交谈。打个比方,人可以命令一条狗行走,但是不应该直接指挥狗的腿行走,应该由狗去指挥它的腿行走。
实现分类
按照代理创建的时期来进行分类,可分为静态代理和动态代理两类。
以下是一个接口和该接口的实现类,作为代理实例中被代理的对象。
一、创建一个接口
/**
* @ClassName: IService
* @Description: 一个普通接口
*/
public interface IService {
void say();
}
二、这个接口的实现类
/**
* @ClassName: ServiceImpl
* @Description: 接口的实现类,实现相应的业务
*/
public class ServiceImpl implements IService {
private static final Logger LOGGER = LogManager.getLogger(ServiceImpl.class);
@Override
public void say() {
LOGGER.info("接口的真正实现类:{}", this.getClass().getSimpleName());
// do something
}
}
静态代理
- 含义
静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。 - 实现步骤
a、接口代理实现类,实现了上面的接口IService,同时在构造函数中要传入上面的接口,在override的方法中,调用接口中的方法
public class ProxyServiceImpl implements IService {
private static final Logger LOGGER = LogManager.getLogger(ProxyServiceImpl.class);
private IService iService;
/**
* @Title: ProxyServiceImpl
* @Description: 构造函数中必须传入被代理的接口实例
* @param: iService
*/
public ProxyServiceImpl(IService iService) {
this.iService = iService;
}
/**
* @Title: say
* @Description: 实现了接口的方法
*/
@Override
public void say() {
LOGGER.info("实现相同接口的代理类:{},准备开始真正的接口实现类", this.getClass().getSimpleName());
//调用接口中的方法
iService.say();
LOGGER.info("实现相同接口的代理类:{},真正的接口实现类方法运行完成", this.getClass().getSimpleName());
}
}
b、应用
public class Main {
private static final Logger LOGGER = LogManager.getLogger(Main.class);
public static void main(String[] args) {
IService service = new ServiceImpl();
service.say();
LOGGER.info("-----");
IService proxyService = new ProxyServiceImpl(service);
proxyService.say();
}
}
- 总结:静态代理的实现就是:一个接口被多次实现,而在某个实现中,又会调用这个接口。
- 优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
- 缺点:需要为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
动态代理
- 动态代理是在程序运行时通过反射机制动态创建的。
- 动态代理根据底层实现不同又分为JDK和Cglib。
JDK的动态代理
- 被代理的类、方法,必须是实现了某个接口,否则将报错:java.lang.IllegalArgumentException: object is not an instance of declaring class
- 底层其实是根据ProxyGenerator.java类及传入的参数等内容,创建出一个class类,也就是通过Java的反射,创建出一个临时实现接口的类,这个类就像我们写静态代理时的那个代理
- 实现步骤(实现的接口是上面的IService)
a、JDK的动态代理实现类
/**
* @ClassName: JdkDynamicProxyHandler
* @Description: 这个是JDK的动态代理
*/
public class JdkDynamicProxyHandler implements InvocationHandler {
private static final Logger LOGGER = LogManager.getLogger(JdkDynamicProxyHandler.class);
private Object targetObject;
/**
* @Title: JdkDynamicProxyHandler
* @Description: 构造函数
* @param: targetObject: 这个是被代理的类
*/
public JdkDynamicProxyHandler(Object targetObject) {
this.targetObject = targetObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//执行目标方法前
LOGGER.info("--> JDK的代理开始,before");
byte[] b = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(), proxy.getClass().getInterfaces());
FileOutputStream out = new FileOutputStream("./" + proxy.getClass().getSimpleName() + ".class");
out.write(b);
out.flush();
out.close();
LOGGER.info("--> JDK的代理,{}", method.getName());
//执行目标方法
Object result = method.invoke(targetObject, args);
//执行目标方法后
LOGGER.info("--> JDK的代理结束,after");
return result;
}
}
b、应用
public class Main {
public static void main(String[] args) {
//获取被代理的类加载器
ClassLoader classLoader = ServiceImpl.class.getClassLoader();
//获取被代理的类的接口类型
Class<?>[] interfaces = ServiceImpl.class.getInterfaces();
//指定动态处理器,这样能触发调用invoke()方法
JdkDynamicProxyHandler proxyHandler = new JdkDynamicProxyHandler(new ServiceImpl());
IService service = (IService) Proxy.newProxyInstance(classLoader, interfaces, proxyHandler);
service.say();
}
}
- 总结:实现了java.lang.reflect.InvocationHandler这个JDK中的接口,重写invoke方法,在该方法中通过method.invoke来调用真正被代理的类方法。
- 优点:符合开闭原则、同时也不需要写太多的代理类。
- 缺点:被代理的类必须实现了一个接口(而且只能是接口,抽象类都不行),通过这个接口才能完成代理。
- 在IDEA中debug这块代码时,会出现invoke方法中的内容会重复执行多次,即
LOGGER.info("--> JDK的代理,{}", method.getName());
这行会输出--> JDK的代理,toString
,是因为调试时IDEA会调用被代理类的toString()方法,代理类会代理该类的所有方法(包括toString)。这里是别的人回答。 - 注意,其实在重写invoke方法时,第一个参数是没有用到的,因为第一个参数是JDK动态代理生成的临时类对象$Proxy0.class,在后续的操作过程中,都是通过这个类对象来完成,以下就是生成的代理类
public final class $Proxy0 extends Proxy implements IService {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
//这个是方法是重写了接口中的方法,可见第一个参数为this,第二个参数为接口中的方法
public final void say() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.my.springdemo.proxydemo.service.IService").getMethod("say");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
- 为什么必须要有接口?
- 根据上面生成的class类可以看到,这个临时类继承了Proxy.class,并实现了我们定义的接口,通过实现接口来建立与接口连接
- java中为单继承、多实现
- 从临时生成的代码可知道,运用了java中的多态和反射来生成这个class类
- 这个类与Proxy.class相关,而Proxy又与InvocationHandler是组合关系,增强的方法就是InvocationHandler的实现类中
- 这个类也与我们定义的接口相关,来获取接口中的方法及参数,不需要获取接口的真正实现类方法
Cglib的动态代理
- 导入包cglib
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>
- 代理类
/**
* @ClassName: CgLibDynamicProxyHandler
* @Description: CGLib的动态代理实现类
*/
public class CgLibDynamicProxyHandler implements MethodInterceptor {
private static final Logger LOGGER = LogManager.getLogger(CgLibDynamicProxyHandler.class);
/**
* @Title: getInstance
* @Description: 通过传来的对象获取代理对象,也就是说被代理的对象是生成代理类对象的父类
* @param: targetObject
* @return: java.lang.Object
*/
public Object getInstance(Object targetObject) {
//实例化一个增强器,也就是cglib中的一个class generator
Enhancer enhancer = new Enhancer();
//设置目标类
enhancer.setSuperclass(targetObject.getClass());
//设置代理对象
enhancer.setCallback(this);
//生成代理类并返回一个实例
return enhancer.create();;
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//执行目标方法前
LOGGER.info("--> JDK的代理开始,before");
//执行目标方法,也就是代理类调用父类的方法
Object result = methodProxy.invokeSuper(o, args);
//执行目标方法后
LOGGER.info("--> JDK的代理结束,after");
return result;
}
}
- 应用
public class Main {
public static void main(String[] args) {
//可以把生成的代理类输出出来
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./");
CgLibDynamicProxyHandler cglibProxy = new CgLibDynamicProxyHandler();
IService instance = (IService) cglibProxy.getInstance(new ServiceImpl());
instance.say();
}
}
- 流程:
a、实现MethodInterceptor 这个cglib中的接口中的方法
b、在方法中通过methodProxy.invokeSuper()来调用被代理的类方法
c、通过getInstance()方法来获取生成的代理类对象 - 特点:被代理的是一个类(不是接口),而且这个类不能是final,因为要产成子类,这个子类就是代理对象类
- 工作原理:采用了FastClass的机制来实现对被拦截方法的调用。FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法
- 与JDK代理对比:
a、CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高
b、对于单例的对象,因为无需频繁创建对象,用CGLIB合适
c、CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理 - 实际在Spring中使用CGLib时,不需要导入CGLib的包,因为从spring3.2以后,spring框架本身不在需要cglib这个jar包了,cjlib.jar已经被spring项目的jar包集成进去。为了防止项目中其他对cglib版本依赖不一样的冲突。
代理形式分为两种:
静态代理(手动通过实现接口)和动态代理(通过接口生成类文件)
动态代理的实现分为两种:
JDK代理(被代理的类必须实现了接口)和Cglib代理(只要类不是final的就可以)