代理模式&JDK动态代理&CGLIB动态代理
https://yuanyu.blog.csdn.net/article/details/105607656
1 代理模式
2 静态代理
静态代理的代理对象和被代理对象在代理之前就已经确定,它们都实现相同的接口或继承相同的抽象类。静态代理模式一般由业务实现类和业务代理类组成,业务实现类里面实现主要的业务逻辑,业务代理类负责在业务方法调用的前后作一些你需要的处理,如日志记录、权限拦截等功能,实现业务逻辑与业务方法外的功能解耦,减少了对业务方法的入侵;静态代理又可细分为基于继承的方式和基于聚合的方式实现
场景:假设一个预减库存的操作,需要在预减的前后加日志记录
2.1 基于继承的方式实现静态代理
/**
* 业务实现类接口
*/
public interface OrderService {
// 减库存操作
void reduceStock();
}
/**
* 业务实现类
*/
@Slf4j
public class OrderServiceImpl implements OrderService {
@Override
public void reduceStock() {
try {
log.info("预减库存中...");
TimeUnit.SECONDS.sleep(3);
} 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();
}
输出结果
16:10:18.683 [main] INFO OrderServiceLogProxy - 预减库存开始...
16:10:18.687 [main] INFO OrderServiceImpl - 预减库存中...
16:10:21.688 [main] INFO OrderServiceLogProxy - 预减库存结束...
可以看到,OrderServiceLogProxy已经实现了为OrderServiceImpl的代理,通过代理类的同名方法来增强了业务方法前后逻辑。
2.2 基于聚合的方式实现静态代理
聚合的意思就是把业务类引入到了代理类中,接口和业务实现类还是之前的OrderService、OrderServiceImpl,代理类改为如下
/**
* 业务实现类接口
*/
public interface OrderService {
// 减库存操作
void reduceStock();
}
/**
* 业务实现类
*/
@Slf4j
public class OrderServiceImpl implements OrderService {
@Override
public void reduceStock() {
try {
log.info("预减库存中...");
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 聚合方式实现静态代理:代理类中引入业务类
*/
@Slf4j
public class OrderServiceLogProxy implements OrderService {
// 引入业务类
private final OrderServiceImpl orderService;
public OrderServiceLogProxy(OrderServiceImpl orderService) {
this.orderService = orderService;
}
@Override
public void reduceStock() {
log.info("预减库存开始...");
orderService.reduceStock();
log.info("预减库存结束...");
}
}
/**
* 测试聚合方式实现的静态代理
*/
@Test
public void testOrderServiceProxy() {
OrderServiceImpl orderService = new OrderServiceImpl();
OrderServiceLogProxy proxy = new OrderServiceLogProxy(orderService);
proxy.reduceStock();
}
2.3 继承方式vs聚合方式
结合上面的代码来看,如果此时需要叠加代理功能,我不仅要记录预减日志,还要增加权限拦截功能,这个时候如果采用继承的方式的话,就得新建一个代理类,里面包含日志和权限逻辑;那要是再增加一个代理功能,又要新增代理类,如果要改变下代理功能的执行顺序,还是得增加代理类,结合上面分析来看,这样做肯定是不妥的。但是如果使用聚合方式,我们只需要稍微改造一下上面使用聚合方式实现的静态代理代码
/**
* 业务实现类接口
*/
public interface OrderService {
// 减库存操作
void reduceStock();
}
/**
* 业务实现类
*/
@Slf4j
public class OrderServiceImpl implements OrderService {
@Override
public void reduceStock() {
try {
log.info("预减库存中...");
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
}
}
首先是日志代理类代码
/**
* 聚合方式实现静态代理--日志记录功能叠加改造
*/
@Slf4j
public class OrderServiceLogProxy implements OrderService {
// 注意,这里换成了接口
private final OrderService orderService;
public OrderServiceLogProxy(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();
OrderServiceLogProxy logProxy = new OrderServiceLogProxy(orderService);
OrderServicePermissionProxy permissionProxy = new OrderServicePermissionProxy(logProxy);
permissionProxy.reduceStock();
}
测试结果
16:34:46.603 [main] INFO OrderServicePermissionProxy - 权限验证开始...
16:34:46.607 [main] INFO OrderServiceLogProxy3 - 预减库存开始...
16:34:46.607 [main] INFO OrderServiceImpl - 预减库存中...
16:34:47.607 [main] INFO OrderServiceLogProxy3 - 预减库存结束...
16:34:47.607 [main] INFO OrderServicePermissionProxy - 权限验证结束...
如果你需要调换一下代理类逻辑执行顺序问题,你只需要在使用(像测试一样)时调换一下实例化顺序即可实现日志功能和权限验证的先后执行顺序了,而不需要像继承方式一样去不断的新建代理类
3 动态代理
看完上面的静态代理,我们发现,静态代理模式的代理类,只是实现了特定类的代理,比如上面OrderServiceLogProxy实现的OrderServiceimpl的代理,如果我还有个UserService也许要日志记录、权限校验功能,又得写双份的UserServiceLogProxy、UserServicePermissionProxy代理类,里面的逻辑很多都是相同的,也就是说你代理类对象的方法越多,你就得写越多的重复的代码,那么有了动态代理就可以比较好的解决这个问题,动态代理就可以动态的生成代理类,实现对不同类下的不同方法的代理
3.1 JDK动态代理
jdk动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用业务方法前调用InvocationHandler处理,代理类必须实现InvocationHandler接口,并且jdk动态代理只能代理实现了接口的类,没有实现接口的类是不能实现jdk动态代理
/**
* 业务实现类接口
*/
public interface OrderService {
// 减库存操作
void reduceStock();
}
/**
* 业务实现类
*/
@Slf4j
public class OrderServiceImpl implements OrderService {
@Override
public void reduceStock() {
try {
log.info("预减库存中...");
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* JDK动态代理实现,必须实现InvocationHandler接口,InvocationHandler可以理解为事务处理器,所有切面级别的逻辑都在此完成
*/
@Slf4j
public class DynamicLogProxy implements InvocationHandler {
// 需要代理的对象类
private final Object target;
public DynamicLogProxy(Object target) {
this.target = target;
}
/**
* 获取代理对象
*/
public Object getProxyInstance(){
Class<?> clazz = target.getClass();
// 通过Proxy.newProxyInstance(代理类的类加载器, 代理类实现的所有接口, Handler) 加载动态代理
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
/**
* @param obj 被代理对象
* @param method 对象方法
* @param args 方法参数
*/
@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
// 不处理
if (Object.class.equals(method.getDeclaringClass())){
return method.invoke(this, args);
}
log.info("这里是日志记录切面,日志开始...");
// 使用方法的反射
Object invoke = method.invoke(target, args);
log.info("这里是日志记录切面,日志结束...");
return invoke;
}
}
使用时代码
/**
* 测试JDK动态代理实现的日志代理类
*/
@Test
public void testDynamicLogProxy() {
OrderServiceImpl orderService = new OrderServiceImpl();
DynamicLogProxy proxy = new DynamicLogProxy(orderService);
OrderService os = (OrderService)proxy.getProxyInstance();
os.reduceStock();
}
输出结果
16:42:59.158 [main] INFO DynamicLogProxy - 这里是日志记录切面,日志开始...
16:42:59.163 [main] INFO OrderServiceImpl - 预减库存中...
16:43:00.164 [main] INFO DynamicLogProxy - 这里是日志记录切面,日志结束...
使用JDK动态代理类基本步骤:
- 编写需要被代理的类和接口(我这里就是OrderServiceImpl、OrderService)
- 编写代理类(例如我这里的OrderServiceProxy),需要实现InvocationHandler接口,重写invoke方法
- 使用 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 动态创建代理类对象,通过代理类对象调用业务方法
这个时候,如果我需要在代理类中叠加功能,比如不仅要日志,还新增权限认证,思路还是上面的聚合方式实现静态代理里的那样,贴下代码,先新增权限认证代理类
/**
* 基于JDK动态代理实现的权限认证代理类
*/
@Slf4j
public class DynamicPermissionProxy implements InvocationHandler {
private Object target;
public DynamicPermissionProxy(Object target) {
this.target = target;
}
/**
* 获取代理对象
*/
public Object getProxyInstance(){
Class<?> clazz = target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
/**
* @param obj 被代理对象
* @param method 对象方法
* @param args 方法参数
*/
@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
// 不处理
if (Object.class.equals(method.getDeclaringClass())){
return method.invoke(this, args);
}
log.info("这里是权限认证切面,开始验证...");
Object invoke = method.invoke(target, args);
log.info("这里是权限认证切面,结束验证...");
return invoke;
}
}
然后使用时候,需要稍微改动下
/**
* 测试JDK动态代理实现的日志、权限功能代理类
*/
@Test
public void testDynamicLogAndPermissProxy() {
OrderServiceImpl orderService = new OrderServiceImpl();
DynamicLogProxy logProxyHandler = new DynamicLogProxy(orderService);
OrderService os = (OrderService) logProxyHandler.getProxyInstance();
//注:这里把日志代理类实例对象传入权限认证代理类中
DynamicPermissionProxy dynamicPermissionProxy = new DynamicPermissionProxy(os);
OrderService os2 = (OrderService)dynamicPermissionProxy.getProxyInstance();
os2.reduceStock();
}
如上即可,后面还需要叠加功能代理类的话,按照上面的思路依次传入代理对象实例即可
3.2 Cglib动态代理
cglib是针对类来实现代理的,它会对目标类产生一个代理子类,通过方法拦截技术对过滤父类的方法调用,代理子类需要实现MethodInterceptor接口
如果你是基于Spring配置文件形式开发,那你需要显示声明:
<aop:aspectj-autoproxy proxy-target-class="true"/>
如果你是基于Spring注解形式或者是SpringBoot开发,那你需要显示声明:
@EnableAspectJAutoProxy(proxyTargetClass = true)
<!--
org.springframework.cglib.proxy.Enhancer
org.springframework.cglib.proxy.MethodInterceptor
org.springframework.cglib.proxy.MethodProxy
-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
都不是引入下面依赖
<!--
net.sf.cglib.proxy.Enhancer
net.sf.cglib.proxy.MethodInterceptor
net.sf.cglib.proxy.MethodProxy
-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
/**
* 业务实现类,注意这里没用实现接口
*/
@Slf4j
public class OrderService {
// 减库存操作
public void reduceStock() {
try {
log.info("预减库存中...");
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 基于Cglib方式实现动态代理-日志功能,它是针对类实现代理的,类不用实现接口,CGlib对目标类产生一个子类,通过方法拦截技术拦截所有的方法调用
*/
@Slf4j
public class DynamicCglibLogProxy implements MethodInterceptor {
// 目标对象
private final Object target;
public DynamicCglibLogProxy(Object target) {
this.target = target;
}
/**
* 返回一个代理对象
*/
public Object getProxyObj() {
// 1.创建一个工具类
Enhancer enhancer = new Enhancer();
// 2.设置父类
enhancer.setSuperclass(target.getClass());
// 3.设置回调函数
enhancer.setCallback(this);
// 4.
enhancer.setUseCache(false);
// 5.传教子类对象,即代理对象
return enhancer.create();
}
/**
* 拦截目标类的方法调用
*
* @param obj 目标对象
* @param method 目标方法
* @param args 方法参数
* @param methodProxy 代理类实例
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 不做处理
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(target, args);
}
log.info("这里是日志记录切面,日志开始...");
Object res = method.invoke(target, args);
log.info("这里是日志记录切面,日志结束...");
return res;
}
}
/**
* 测试Cglib实现的动态代理-日志功能
*/
@Test
public void testGclibDynamicLogProxy(){
// 创建目标对象
OrderService orderService = new OrderService();
// 获取代理对象
DynamicCglibLogProxy dynamicCglibLogProxy = new DynamicCglibLogProxy(orderService);
// 执行代理对象的方法,触发intercept方法,从而实现对目标对象的调用
OrderService proxy = (OrderService)dynamicCglibLogProxy.getProxyObj();
proxy.reduceStock();
System.out.println(proxy);
}
JDK与Cglib动态代理对比
- JDK动态代理只能代理实现了接口的类,没有实现接口的类不能实现JDK的动态代理
- Cglib动态代理是针对类实现代理的,运行时动态生成被代理类的子类拦截父类方法调用,因此不能代理声明为final类型的类和方法
动态代理和静态代理的区别
- 静态代理在代理前就知道要代理的是哪个对象,而动态代理是运行时才知道
- 静态代理一般只能代理一个类,而动态代理能代理实现了接口的多个类
Spring如何选择两种代理模式的
- 如果目标对象实现了接口,则默认采用JDK动态代理;
- 如果目标对象没有实现接口,则使用Cglib代理;
- 如果目标对象实现了接口,但强制使用了Cglib,则使用Cglib进行代理
//<dependency>
// <groupId>org.springframework</groupId>
// <artifactId>spring-context</artifactId>
// <version>5.2.6.RELEASE</version>
//</dependency>
// org.springframework.aop.framework.DefaultAopProxyFactory
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
return new JdkDynamicAopProxy(config);
} else {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
} else {
return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
}
}
}
}
3.3 在Spirng框架中使用JKD动态代理
/**
* 业务实现类接口
*/
public interface OrderService {
// 减库存操作
void reduceStock();
}
/**
* 业务实现类
*/
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Override
public void reduceStock() {
try {
log.info("预减库存中...");
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//<dependency>
// <groupId>org.springframework</groupId>
// <artifactId>spring-context</artifactId>
// <version>5.2.6.RELEASE</version>
//</dependency>
/**
* 配置类
*/
@Configuration
@ComponentScan("cn.yuanyu.app")
public class AppConfig {
}
@Component
public class LogPostProcessor implements BeanPostProcessor {
/**
* @param bean 刚创建的实例
* @param beanName 实例在容器中的名字
* @return 返回bean实例,可以返回原来的对象,也可以返回包装后对象
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof OrderService) {
// 传入bean
DynamicLogProxy dynamicLogProxy = new DynamicLogProxy(bean);
Object proxyBean = Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), dynamicLogProxy);
// 返回的不是原来的bean,而是代理bean
return proxyBean;
}
return bean;
}
}
@Slf4j
public class DynamicLogProxy implements InvocationHandler {
// 需要代理的对象类
private final Object target;
public DynamicLogProxy(Object target) {
this.target = target;
}
/**
* @param obj 代表动态代理对象
* @param method 代表正在执行的方法
* @param args 代表调用目标方法时传入的实参
*/
@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
// 不做处理
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 这里还要根据实际情况判断,不是所有的都需要记录日志
log.info("方法执行前添加日志记录等...");
// 使用方法的反射
Object invoke = method.invoke(target, args);
log.info("方法执行后添加日志记录等...");
// 其他操作...
return invoke;
}
}
@Test
public void test() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
OrderService orderService = applicationContext.getBean(OrderService.class);
orderService.reduceStock();
}
[main] INFO DynamicLogProxy - 方法执行前添加日志记录等...
[main] INFO OrderServiceImpl - 预减库存中...
[main] INFO DynamicLogProxy - 方法执行后添加日志记录等...
/**
* ConcurrentModificationException 并发修改异常
*/
public class ArrayListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
List<String> proxy = (List<String>) Proxy.newProxyInstance(ArrayList.class.getClassLoader(), ArrayList.class.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
synchronized (ArrayListDemo.class) {
return method.invoke(list, args);
}
}
});
for (int i = 0; i < 100; i++) {
new Thread(() -> {
proxy.add(UUID.randomUUID().toString().substring(0, 3));
proxy.add(UUID.randomUUID().toString().substring(0, 3));
proxy.add(UUID.randomUUID().toString().substring(0, 3));
System.out.println(proxy);
}, String.valueOf(i)).start();
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" monitorInterval="5">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="console"/>
</Root>
</Loggers>
</Configuration>
<!--
log4j2.xml
https://yuanyu.blog.csdn.net/article/details/105607656
-->
- https://blog.csdn.net/fanrenxiang/article/details/81939357
- https://blog.csdn.net/jiankunking/article/details/52143504
- https://blog.csdn.net/maoyeqiu/article/details/76546468
- https://blog.csdn.net/csdn_20150804/article/details/102734317
- https://blog.csdn.net/caoxiaohong1005/article/details/80039656
还没有评论,来说两句吧...