目录
如上图,代理模式可分为动态代理和静态代理,我们比较常用的有动态代理中的jdk动态代理和Cglib代理,像spring框架、hibernate框架中都采用了JDK动态代理,下面将结合代码阐述两种代理模式的使用与区别。
1 静态代理
静态代理的代理对象和被代理对象在代理之前就已经确定,它们都实现相同的接口或继承相同的抽象类。静态代理模式一般由业务实现类和业务代理类组成,业务实现类里面实现主要的业务逻辑,业务代理类负责在业务方法调用的前后作一些你需要的处理,如日志记录、权限拦截等功能…实现业务逻辑与业务方法外的功能解耦,减少了对业务方法的入侵。静态代理又可细分为:基于继承的方式和基于聚合的方式实现。
场景:假设一个预减库存的操作,需要在预减的前后加日志记录(我这里是springboot项目)
1.1 基于继承的方式实现静态代理
/**
* 业务实现类接口
*/
public interface OrderService {
//减库存操作
void reduceStock();
}
/**
* 业务实现类
*/
@Slf4j
public class OrderServiceImpl implements OrderService {
@Override
public void reduceStock() {
try {
log.info("预减库存中……");
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
}
}
/**
* 代理类
*/
@Slf4j
public class OrderServiceLogProxy extends OrderServiceImpl{
@Override
public void reduceStock() {
log.info("预减库存开始……");
super.reduceStock();
log.info("预减库存结束……");
}
}
/**
* 测试继承方式实现的静态代理
*/
@Test
public void testOrderServiceProxy(){
OrderServiceLogProxy proxy = new OrderServiceLogProxy();
proxy.reduceStock();
}
输出结果
14:53:53.769 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceLogProxy - 预减库存开始……
14:53:53.771 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceImpl - 预减库存中……
14:53:54.771 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceLogProxy - 预减库存结束……
可以看到,OrderServiceLogProxy已经实现了为OrderServiceImpl的代理,通过代理类的同名方法来增强了业务方法前后逻辑。
1.2 基于聚合的方式实现静态代理
聚合的意思就是把业务类引入到了代理类中,接口和业务实现类还是之前的OrderService、OrderServiceImpl,代理类改为如下:
/**
* 聚合方式实现静态代理:代理类中引入业务类
*/
@Slf4j
public class OrderServiceLogProxy2 implements OrderService {
private OrderServiceImpl orderService;
public OrderServiceLogProxy2(OrderServiceImpl orderService) {
this.orderService = orderService;
}
@Override
public void reduceStock() {
log.info("预减库存开始……");
orderService.reduceStock();
log.info("预减库存结束……");
}
}
/**
* 测试聚合方式实现的静态代理
*/
@Test
public void testOrderServiceProxy2() {
OrderServiceImpl orderService = new OrderServiceImpl();
OrderServiceLogProxy2 proxy2 = new OrderServiceLogProxy2(orderService);
proxy2.reduceStock();
}
测试输出结果和上面的结果是一致的。
1.3 继承与聚合方式实现的静态代理对比
结合上面的代码来看,如果此时需要叠加代理功能,我不仅要记录预减日志,还要增加权限拦截功能,这个时候如果采用继承的方式的话,就得新建一个代理类,里面包含日志和权限逻辑;那要是再增加一个代理功能,又要新增代理类;如果要改变下代理功能的执行顺序,还是得增加代理类,结合上面分析来看,这样做肯定是不妥的。但是如果使用聚合方式的方式呢?我们稍微改造下上面使用的聚合方式实现的静态代理代码:
首先是日志代理类代码
/**
* 聚合方式实现静态代理--日志记录功能叠加改造
*/
@Slf4j
public class OrderServiceLogProxy3 implements OrderService {
//注意,这里换成了接口
private OrderService orderService;
public OrderServiceLogProxy3(OrderService orderService) {
this.orderService = orderService;
}
@Override
public void reduceStock() {
log.info("预减库存开始……");
orderService.reduceStock();
log.info("预减库存结束……");
}
}
然后是新增的权限验证代理类代码
/**
* 聚合方式实现静态代理--日志记录功能叠加改造
*/
@Slf4j
public class OrderServicePermissionProxy implements OrderService {
//注意,这里换成了接口
private OrderService orderService;
public OrderServicePermissionProxy(OrderService orderService) {
this.orderService = orderService;
}
@Override
public void reduceStock() {
log.info("权限验证开始……");
orderService.reduceStock();
log.info("权限验证结束……");
}
}
测试用例
/**
* 测试聚合方式实现的静态代理-功能叠加
*/
@Test
public void testOrderServiceProxy3() {
OrderServiceImpl orderService = new OrderServiceImpl();
OrderServiceLogProxy2 logProxy2 = new OrderServiceLogProxy2(orderService);
OrderServicePermissionProxy permissionProxy = new OrderServicePermissionProxy(logProxy2);
permissionProxy.reduceStock();
}
测试结果
16:00:28.348 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServicePermissionProxy - 权限验证开始……
16:00:28.365 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceLogProxy2 - 预减库存开始……
16:00:28.365 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceImpl - 预减库存中……
16:00:29.365 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceLogProxy2 - 预减库存结束……
16:00:29.365 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServicePermissionProxy - 权限验证结束……
接下来,如果你需要调换一下代理类逻辑执行顺序问题,你只需要在使用(像测试一样)时调换一下实例化顺序即可实现日志功能和权限验证的先后执行顺序了,而不需要像继承方式一样去不断的新建代理类。
2 动态代理
看完上面的静态代理,我们发现,静态代理模式的代理类,只是实现了特定类的代理,比如上面OrderServiceLogProxy实现的OrderServiceimpl的代理,如果我还有个UserService也许要日志记录、权限校验功能,又得写双份的UserServiceLogProxy、UserServicePermissionProxy代理类,里面的逻辑很多都是相同的,也就是说你代理类对象的方法越多,你就得写越多的重复的代码,那么有了动态代理就可以比较好的解决这个问题,动态代理就可以动态的生成代理类,实现对不同类下的不同方法的代理。
2.1 JDK动态代理
jdk动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用业务方法前调用InvocationHandler处理。代理类必须实现InvocationHandler接口,并且,JDK动态代理只能代理实现了接口的类,没有实现接口的类是不能实现JDK动态代理。结合下面代码来看就比较清晰了。
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* JDK动态代理实现,必须实现InvocationHandler接口
* InvocationHandler可以理解为事务处理器,所有切面级别的逻辑都在此完成
*/
@Slf4j
public class DynamicLogProxy implements InvocationHandler {
//需要代理的对象类
private Object target;
public DynamicLogProxy(Object target) {
this.target = target;
}
/**
* @param obj 被代理对象
* @param method 对象方法
* @param args 方法参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
log.info("这里是日志记录切面,日志开始……");
//使用方法的反射
Object invoke = method.invoke(target, args);
log.info("这里是日志记录切面,日志结束……");
return invoke;
}
}
使用时代码
/**
* 测试JDK动态代理实现的日志代理类
*/
@Test
public void testDynamicLogProxy() {
OrderServiceImpl orderService = new OrderServiceImpl();
Class<?> clazz = orderService.getClass();
DynamicLogProxy logProxyHandler = new DynamicLogProxy(orderService);
//通过Proxy.newProxyInstance(类加载器, 接口s, 事务处理器Handler) 加载动态代理
OrderService os = (OrderService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), logProxyHandler);
os.reduceStock();
}
输出结果
16:35:54.584 [main] INFO com.simons.cn.springbootdemo.proxy.DynamicLogProxy - 这里是日志记录切面,日志开始……
16:35:54.587 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceImpl - 预减库存中……
16:35:55.587 [main] INFO com.simons.cn.springbootdemo.proxy.DynamicLogProxy - 这里是日志记录切面,日志结束……
使用JDK动态代理类基本步骤:
1、编写需要被代理的类和接口(我这里就是OrderServiceImpl、OrderService);
2、编写代理类(例如我这里的DynamicLogProxy),需要实现InvocationHandler接口,重写invoke方法;
3、使用Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)动态创建代理类对象,通过代理类对象调用业务方法。
那么这个时候,如果我需要在代理类中叠加功能,该如何是好?比如不仅要日志,还新增权限认证,思路还是上面的聚合方式实现静态代理里的那样,贴下代码,先新增权限认证代理类
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 基于JDK动态代理实现的权限认证代理类
*/
@Slf4j
public class DynamicPermissionProxy implements InvocationHandler{
private Object target;
public DynamicPermissionProxy(Object target) {
this.target = target;
}
/**
* @param obj 被代理对象
* @param method 对象方法
* @param args 方法参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
log.info("这里是权限认证切面,开始验证……");
Object invoke = method.invoke(target,args);
log.info("这里是权限认证切面,结束验证……");
return invoke;
}
}
然后使用时候,需要稍微改动下
/**
* 测试JDK动态代理实现的日志、权限功能代理类
*/
@Test
public void testDynamicLogAndPermissProxy() {
OrderServiceImpl orderService = new OrderServiceImpl();
Class<?> clazz = orderService.getClass();
DynamicLogProxy logProxyHandler = new DynamicLogProxy(orderService);
OrderService os = (OrderService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), logProxyHandler);
//注:这里把日志代理类实例对象传入权限认证代理类中
DynamicPermissionProxy dynamicPermissionProxy = new DynamicPermissionProxy(os);
OrderService os2 = (OrderService)Proxy.newProxyInstance(os.getClass().getClassLoader(),os.getClass().getInterfaces(),dynamicPermissionProxy);
os2.reduceStock();
}
如上即可,后面还需要叠加功能代理类的话,按照上面的思路依次传入代理对象实例即可。
如何实现一个HashMap的动态代理类?
public class HashMapProxyTest {
public static void main(String[] args) {
final HashMap<String, Object> hashMap = new HashMap<>();
Map<String, Object> mapProxy = (Map<String, Object>) Proxy.newProxyInstance(HashMap.class.getClassLoader(), HashMap.class.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(hashMap, args);
}
});
mapProxy.put("key1", "value1");
System.out.println(mapProxy);
}
}
2.2 Cglib动态代理
cglib是针对类来实现代理的,它会对目标类产生一个代理子类,通过方法拦截技术对过滤父类的方法调用。代理子类需要实现MethodInterceptor接口。另外,如果你是基于Spring配置文件形式开发,那你需要显示声明:
<aop:aspectj-autoproxy proxy-target-class="true"/>
如果你是基于SpringBoot开发,则一般在启动类头部显示的添加注解
@EnableAspectJAutoProxy(proxyTargetClass = true)
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 基于Cglib方式实现动态代理-日志功能
* 它是针对类实现代理的,类不用实现接口,CGlib对目标类产生一个子类,通过方法拦截技术拦截所有的方法调用
*/
@Slf4j
public class DynamicCglibLogProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxyObj(Class clazz) {
//设置父类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
enhancer.setUseCache(false);
return enhancer.create();
}
/**
* 拦截所有目标类的方法调用
*
* @param o 目标对象
* @param method 目标方法
* @param args 方法参数
* @param methodProxy 代理类实例
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
log.info("这里是日志记录切面,日志开始……");
//代理类对象实例调用父类方法
methodProxy.invokeSuper(o, args);
log.info("这里是日志记录切面,日志结束……");
return null;
}
}
测试用例
/**
* 测试Cglib实现的动态代理-日志功能
*/
@Test
public void testGclibDynamicLogProxy(){
DynamicCglibLogProxy dynamicCglibLogProxy = new DynamicCglibLogProxy();
OrderServiceImpl orderService = (OrderServiceImpl)dynamicCglibLogProxy.getProxyObj(OrderServiceImpl.class);
orderService.reduceStock();
}
输出结果
17:41:07.007 [main] INFO com.simons.cn.springbootdemo.proxy.DynamicCglibLogProxy - 这里是日志记录切面,日志开始……
17:41:07.038 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceImpl - 预减库存中……
17:41:08.038 [main] INFO com.simons.cn.springbootdemo.proxy.DynamicCglibLogProxy - 这里是日志记录切面,日志结束……
cglib的动态代理动态代理步骤
1.创建目标类,即需要代理的类
2 在dynamic.cglib包中,创建代理类CglibDynamicProxy,该类实现MethodInterceptor接口
3 创建代理的方法,生成CGLIB代理对象,设置增强父类
4.实现intercept方法会在程序执行目标方法时被调用,
5。中间也可以创建切面类,进行使用,最后进行测试
2.3 总结
总结
类型 | 机制 | 回调方式 | 适用场景 | 效率 |
---|---|---|---|---|
JDK动态代理 | 委托机制,代理类和目标类都实现了同样的接口,InvocationHandler持有目标类,代理类委托InvocationHandler去调用目标类的原始方法 | 反射 | 目标类是接口类 | 效率瓶颈在反射调用稍慢 |
CGLIB动态代理 | 继承机制,代理类继承了目标类并重写了目标方法,通过回调函数MethodInterceptor调用父类方法执行原始逻辑 | 通过FastClass方法索引调用 | 非接口类,非final类,非final方法 | 第一次调用因为要生成多个Class对象较JDK方式慢,多次调用因为有方法索引较反射方式快,如果方法过多switch case过多其效率还需测试 |
jdk的动态代理和cglib的动态代理,都是通过运行时动态生成字节码
的方式来实现代理的。
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
如何强制使用CGLIB实现AOP?
(1)添加CGLIB库,SPRING_HOME/cglib/*.jar
(2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
JDK动态代理和CGLIB字节码生成的区别?
(1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
因为是继承,所以该类或方法最好不要声明成final
JDK与Cglib动态代理对比?
1、JDK动态代理只能代理实现了接口的类,没有实现接口的类不能实现JDK的动态代理;
2、Cglib动态代理是针对类实现代理的,运行时动态生成被代理类的子类拦截父类方法调用,因此不能代理声明为final类型的类和方法;
动态代理和静态代理的区别?
1、静态代理在代理前就知道要代理的是哪个对象,而动态代理是运行时才知道;
2、静态代理一般只能代理一个类,而动态代理能代理实现了接口的多个类;
Spring如何选择两种代理模式的?
1、如果目标对象实现了接口,则默认采用JDK动态代理;
2、如果目标对象没有实现接口,则使用Cglib代理;
3、如果目标对象实现了接口,但强制使用了Cglib,则使用Cglib进行代理