代理模式:
代理模式还是比较好理解,就是委托别人做事情,比如我们要租房子,委托中介去找房子,这就是代理模式。代理模式分为静态代理模式和动态代理模式。
静态代理:
package com.example.designmode.proxy;
/**
* <h3>design-mode</h3>
* <p>定义消费者接口,查找指定区域,指定价格以下的房子</p>
*
* @author : ZhangYuJie
* @date : 2022-03-27 14:38
**/
public interface ICustomer {
/**
* 查找房子,指定区域,指定价格
*/
String findHouse(String location, int price);
}
/**
* 真正的找房消费者,所以只关注找到这个目标
**/
class ZhangCustomer implements ICustomer {
@Override
public String findHouse(String location, int price) {
System.out.println("找到了位于[" + location + "],价格" + price + "以下的房子");
return "找到了位于[" + location + "],价格" + price + "以下的房子";
}
}
/**
* 找房子中介类,找到合适房子再反馈给指消费者
**/
class AgencyProxy implements ICustomer {
private ICustomer coustomer;
public AgencyProxy(ICustomer coustomer) {
this.coustomer = coustomer;
}
@Override
public String findHouse(String location, int price) {
String result = "没有符合条件的房子";
before();
if (null != coustomer) {
result = coustomer.findHouse(location, price);
}
after();
return result;
}
private void before() {
System.out.println("开始找房子");
}
private void after() {
System.out.println("结束找房子");
}
}
/***场景类*/
class Client {
public static void main(String[] args) {
ICustomer customer = new ZhangCustomer();
ICustomer proxy = new AgencyProxy(customer);
String result = proxy.findHouse("钓鱼岛附近", 2000);
}
}
输出结果:
开始找房子
找到了位于[钓鱼岛附近],价格2000以下的房子
结束找房子
JDK动态代理:
动态代理比较复杂一点,相对于静态代理的指定代理对象,动态代理是在运行时候才知道实际代理对象。动态代理应用比较广泛,我们用的最多的框架Spring中 AOP就采用了动态代理。我们来把上面的代码改造成动态代理。
package com.example.designmode.proxy;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;
import java.lang.reflect.Method;
/**
* <h3>design-mode</h3>
* <p>JDK动态代理</p>
*
* @author : ZhangYuJie
* @date : 2022-03-27 14:45
**/
public class MyInvocationHandler implements InvocationHandler {
private Object object;
public MyInvocationHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result= method.invoke(object,args);
after();
return result;
}
private void before(){
System.out.println("开始找房子");
}
private void after(){
System.out.println("结束找房子");
}
}
/**
* 真正的找房消费者,所以只关注找到这个目标
**/
class Zhang1Customer implements ICustomer {
@Override
public String findHouse(String location, int price) {
System.out.println("找到了位于[" + location + "],价格" + price + "以下的房子");
return "找到了位于[" + location + "],价格" + price + "以下的房子";
}
}
class Client1 {
public static void main(String[] args) {
// 获取动态代理对象。
ICustomer customer = (ICustomer) Proxy.newProxyInstance(Client.class.getClassLoader()
, new Class[]{ICustomer.class}
, new MyInvocationHandler(new ZhangCustomer()));
customer.findHouse("钓鱼岛附近", 2000);
}
}
输出结果:
开始找房子
找到了位于[钓鱼岛附近],价格2000以下的房子
结束找房子
动态代理要求代理的类必须要有接口,同时实现InvocationHandler的接口,实现接口的invoke方法即可,在invoke方法内可以在真正执行方法的前后添加想做的事情,比如说记录日志,通知消息等等,这就是AOP的思想,在不改变原有业务代码的情况下,添加功能。但是jdk动态代理的缺点就是代理的类必须要有接口才行,为了弥补这个缺点,出现了一款不需要接口就能被代理的第三方库,CGLIB库。
CGLIB动态代理:
CGLIB是一个强大的、高性能的代码生成库。它被广泛使用在基于代理的AOP框架(例如Spring AOP和dynaop)提供方法拦截。在实现内部,CGLIB库使用了ASM这一个轻量但高性能的字节码操作框架来转化字节码,产生新类。
CGLIB的使用:
既然是第三方库,我们需要添加maven依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.9</version>
</dependency>
CGLIB也和jdk动态代理一样,需要设置回调方法,在JDK动态代理中我们要实现 InvocationHandler,而在CGLIB中我们需要实现net.sf.cglib.proxy.MethodInterceptor接口作为回调接口。我们先看代码
package com.example.designmode.proxy;
/**
* <h3>design-mode</h3>
* <p></p>
*
* @author : ZhangYuJie
* @date : 2022-03-27 14:51
**/
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* cglib动态代理
*
* @author ZhangYuJie
**/
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(o, objects);
after();
return result;
}
private void before() {
System.out.println("开始找房子");
}
private void after() {
System.out.println("结束找房子");
}
}
/**
* 真正的找房消费者,所以只关注找到这个目标
**/
class Zhang2Customer implements ICustomer {
@Override
public String findHouse(String location, int price) {
System.out.println("找到了位于[" + location + "],价格" + price + "以下的房子");
return "找到了位于[" + location + "],价格" + price + "以下的房子";
}
}
class Client2 {
public static void main(String[] args) {
//创建回调实例
MyMethodInterceptor interceptor = new MyMethodInterceptor();
//CGLIB创建实例
Enhancer enhancer = new Enhancer();
//设置需要代理的类
enhancer.setSuperclass(Zhang2Customer.class);
//设置回调类
enhancer.setCallback(interceptor);
//获取代理对象
Zhang2Customer customer = (Zhang2Customer) enhancer.create();
//执行方法
customer.findHouse("钓鱼岛", 1000);
}
}
输出结果:
开始找房子
找到了位于[钓鱼岛],价格1000以下的房子
结束找房子
这里CGLIB实现结果和JDK动态代理完全一样。上面实现回调方法的时候需要注意,推荐使用methodProxy.invokeSuper(o,objects);,这里调用的是CJLIB库的方法,如果使用method.invoke(o,args);需要注意的是,这里使用的就是JDK的动态代理了,同时invoke的object必须是传入的代理实例,而不是方法中形参object,否则会导致死循环调用。同时考虑到性能,还是建议使用第一种调用方式。
CGLIB库还提供了很多其他的特性,比如回调方法过滤等等。
代理模式的优点与缺点
优点
1.职责清晰
真实的角色实现实际的业务逻辑,不用关心其他非核心的业务逻辑。业务是业务,辅助功能是辅助功能,职责非常清晰。比如要实现日志功能,不用耦合在实际的业务代码中,只要做一个代理即可。
2.良好的扩展性
由于核心的业务逻辑已经封装好了,后面要增强业务功能,也可以使用代理模式代理增加功能即可。
缺点
1.类的臃肿
对于静态代理来说,如果过多的使用静态代理会带来类臃肿。一般会在接口协议转换中使用比较多代理模式。
2.复杂性
对于动态代理来说,设计到回调方法的实现,特别是CGLIB中的使用,还是带来了一定的复杂性。
3.性能
对于动态代理来说,都是运行期进行字节码操作,所以还是带来了一些性能损耗,但是这不能作为不使用动态代理的理由,任何东西都是有两面性的。
代理模式的使用场景
1.方法增强;比如增加日志,事务等功能。
2.远程RPC调用;现在很多分布式系统RPC调用都是采用了代理模式。
3.协议等的转换;比如需要适用另外一套协议接口,会使用代理模式,先转换为老协议,然后再调用实际的类去执行。
4.懒加载;有些框架会在开始的时候使用代理类来替换实际类,等到真正要使用该类的时候才进行加载,从而达到了懒加载的效果。