代理模式简述
目录
一、主要角色
二、类型划分
三、静态代理
示例
缺点
四、动态代理
JDK动态代理
示例
缺点
CGLib动态代理
导入依赖
示例
五、Spring AOP
代理模式是一种结构型设计模式,通过代理对象控制对目标对象的访问,可在不改变目标对象情况下增强其功能,隐藏实现细节。
一、主要角色
- Subject(业务接口类):可以是抽象类或接口
- RealSubject(业务实现类):具体的业务执行,即被代理的对象
- Proxy(代理类):RealSubject的代理
二、类型划分
根据代理的创建时期,可分为静态代理和动态代理
- 静态代理:由程序员创建或工具自动生成代理类源代码并编译,程序运行前代理类的字节码文件已经存在;
- 动态代理:在程序运行时运用反射机制动态创建而成
为方便下面的讲解,我们先提前创建业务接口类和业务实现类
//业务接口类
public interface HouseSubject {
void rentHouse();
}
//业务实现类(被代理类)
public class RealHouseSubject implements HouseSubject {
@Override
public void rentHouse() {
System.out.println("我是房东,我出租房子");
}
}
三、静态代理
示例
//代理类
public class HouseProxy implements HouseSubject{
//中介出售房子前要先有房东的授权
private HouseSubject houseSubject;
public HouseProxy(HouseSubject houseSubject) {
this.houseSubject = houseSubject;
}
@Override
public void rentHouse() {
System.out.println("我是中介,我开始代理房东出租房子");
houseSubject.rentHouse();
System.out.println("我是中介,代理结束");
}
}
使用代理类执行
public class Main {
public static void main(String[] args) {
//创建代理对象
HouseSubject subject=new HouseProxy(new RealHouseSubject());
subject.rentHouse();
}
}
缺点
- 为每个被代理类编写代理类,代码冗余,随着业务的发展,类数量膨胀,项目管理难度增大
- 被代理类接口变化时,所有相关代理类都需修改,易出错,可维护性差
- 只能服务特定被代理类,难以应对新类和多变的额外逻辑需求,缺乏灵活性
四、动态代理
JDK动态代理
示例
自定义JDKInvocationHandler 并重写invoke(),在invoke()中会调用目标方法,并自定义一些处理逻辑
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//实现 InvocationHandler接口可以被代理对象的方法进行功能增强
@Slf4j
public class JDKInvocationHandler implements InvocationHandler {
//目标类
private Object target;
public JDKInvocationHandler(Object target) {
this.target = target;
}
/**
* 调用目标方法,并对方法进行增强
* @param proxy 代理对象
* @param method 代理对象需要实现的方法
* @param args method方法所对应的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("JDK动态代理开始");
//调用目标函数
Object result=method.invoke(target,args);
log.info("JDK动态代理结束");
return null;
}
}
创建代理对象,执行逻辑
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
RealHouseSubject target=new RealHouseSubject();
//通过被代理类、被代理类实现的接口、方法调用处理器来创建一个代理类
/**
* ClassLoader loader 类加载器,用于加载代理对象
* Class<?>[] interfaces 被代理类实现的一些接口(jdk只能代理实现了接口的类)
* java.lang.reflect.InvocationHandler h 实现了InvocationHandler接口的对象
*/
HouseSubject proxy=(HouseSubject) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{HouseSubject.class},
new JDKInvocationHandler(target)
);
//使用代理类
proxy.rentHouse();
}
}
缺点
只能代理实现了接口的类,不能代理普通类
CGLib动态代理
导入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
示例
import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
@Slf4j
public class CGLibMethodInterceptor implements MethodInterceptor {
private Object target;
public CGLibMethodInterceptor(Object target) {
this.target = target;
}
/**
*
* @param o 被代理的对象
* @param method 目标方法
* @param objects 方法入参
* @param methodProxy 用于调用原始方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//代理增强内容
System.out.println("CGLib代理开始");
//通过反射调用被代理的方法
Object retVal=methodProxy.invoke(target,objects);
System.out.println("CGLib代理结束");
return retVal;
}
}
import net.sf.cglib.proxy.Enhancer;
public class Main {
public static void main(String[] args) {
HouseSubject target=new RealHouseSubject();
HouseSubject proxy= (HouseSubject)Enhancer.create(target.getClass(),new CGLibMethodInterceptor(target));
proxy.rentHouse();
}
}
五、Spring AOP
Spring AOP是基于动态代理实现的,动态代理有JDK和CGLIB两种方式,运行时使用哪种方式与项目配置和代理的对象有关。
proxy-target-class="false"情况下,若代理对象实现接口,默认使用JDK动态代理;若未实现接口,则会用CGLIB动态代理。当然,即便对象实现接口,也能通过xml配置proxy-target-class="true"或在配置类上使用注解@EnableAspectJAutoProxy(proxyTargetClass = true) 强制使用CGLIB动态代理。
Spring Boot 2.X开始默认使用CGLIB代理,也就是proxy-target-class="true"。
二者各有优劣,需依项目实际情况抉择。JDK动态代理优势在于基于Java原生支持,无需额外依赖,性能较好,适用于对性能要求高且目标对象有接口的场景;但是只能代理实现了接口的对象。
CGLIB动态代理优势在于能代理无接口的普通类,功能更强大、灵活性更高;但是需额外注入依赖,且通过继承创建代理,若类被final修饰则无法代理,生成代理对象的性能开销相对较大。