设计模式之代理模式的应用

本文探讨了代理模式在Java中的应用,通过一个项目背景展示了如何利用cglib实现代理,降低代码耦合度。详细解释了构建cglib代理的四个步骤,并提出两个优化方案:职责拆分的拦截器和自动化注册拦截器。最后提出了针对现有实现的进一步优化思考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

代理模式的应用

​ 前面简单介绍了下代理模式在 Java 中的三种不同使用方式。下面来介绍一个demo项目来加深理解代理模式的原理,方便更好的使用在多变的实际项目中。

项目背景

​ 由于项目中会需要进行入参日志打印、处理结果打印、权限控制等业务逻辑处理,而这些业务逻辑功能又是多变的,如果都将这些业务逻辑放在主要逻辑之中,那么随着时间的增加,代码会越来越杂。

为了降低代码的耦合度,提高代码逻辑辨识。可以使用代理模式去抽离业务逻辑,将业务逻辑独立出来。

但是业务逻辑有时候也不会只有一种,例如某接口即需要入参打印,又需要权限处理,那么这类情况最好将不同的业务逻辑也进行拆分。

构建cglib代理

1. 目标方法

假设目标方法长这样:

public class ToolService {

    public void run(String[] args) {
        System.out.println("执行目标对象方法;args="+ Arrays.toString(args));
        try {
            Thread.sleep(100);	// 模拟调用时长
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
2. 子类代理回调的拦截器

如果使用cglib代理,那么需要先引用cglib的依赖。

<dependency>
 	<groupId>cglib</groupId>
	<artifactId>cglib</artifactId>
	<version>3.2.10</version>
</dependency>

那么创建一回调拦截器,实现 MethodInterceptor接口,用于处理子类代理调用方法时回调的逻辑,也即真正代理的逻辑。

public class ToolProxyCallback implements MethodInterceptor {

   // 目标对象
   private Object target;

   public ToolProxyCallback(Object target) {
      this.target = target;
   }

   @Override
   public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
      // 入参日志打印
      System.out.println("准备执行目标方法;参数信息为="+args);
	  
      // 权限拦截
      if(args.length <= 2) {	// 假设这里的权限是参数不能小于2个
         return null;
      }
       
      // 通过反射执行目标方法 
      Object resultObj = method.invoke(target,args);

      // 处理接口打印
      System.out.println("目标方法请求结果为;"+resultObj);

      return resultObj;
   }
}
3. 创建子类代理的工厂

还需要一个工厂来创建子类代理的实例。

public class ToolProxyFactory {

    /**
     * 获取子类代理实例
     * @param target 目标对象
     * @param callback 回调接口
     * @return
     */
    public static Object getProxyInstance(Object target, Callback callback) {
        // 工具类
        Enhancer enhancer = new Enhancer();
        // 设置父类
        enhancer.setSuperclass(target.getClass());
        // 设置回调函数。
        enhancer.setCallback(callback);
        
        return enhancer.create();   // 创建子类代理对象
    }

}
4. 客户端执行调用

那么调用目标方法就变为这样。

	@Test
    public void testProjectProxy() {

        ToolService service = new ToolService();
        Callback toolProxyCallback = new ToolProxyCallback(service);

        ToolService serviceProxy = (ToolService)ToolProxyFactory.getProxyInstance(service,toolProxyCallback);

        /**
         * 使用 ToolProxyFactory 构建出 ToolCglibService的代理子类,通过代理子类去调用接口方法
         */
        String[] args = new String[]{"-s","test","-n","zhangsan"};
        serviceProxy.run(args);
    }

这样就可以实现将业务逻辑与核心逻辑抽离开,通过cglib提供的接口实例化出目标对象的子类实例,然后用这个子类实例调用父类的方法,也即我们需要代理的目标方法。

优化一:将代理的职责拆分

但是~这样就ok了嘛?

有木有发现 回调拦截器这个方法 ToolProxyCallback#intercept,存在职责不单一的情况。假设这里的权限拦截有着很复杂的逻辑,又或是这个权限拦截在其他接口需要用到的,那么是不是就需要把这个逻辑给抽离出来呢。

业务小的话是没关系的,但是如果考虑到后续前置/后置逻辑会不断累积,那么就需要将回调拦截器中的业务逻辑按照职责拆分单独出来了。

针对代理拦截的逻辑可以知道,有前置逻辑和后置逻辑需要处理。那么抽象为父类

public abstract class AbstractInterceptor {

    /**
     * 前置逻辑
     * @param target 目标对象
     * @param method 目标方法
     * @param args  参数
     * @throws Exception
     */
    public abstract void before(Object target, Method method,Object[] args) throws Exception;

    /***
     * 后置逻辑
     * @param target 目标对象
     * @param method 目标方法
     * @param result 处理结果
     * @throws Exception
     */
    public abstract void after(Object target,Method method,Object result) throws Exception;

}

自定义注解Order,用于拦截器调用的先后顺序;

/**
 * order 值越小 表示优先级越高
 * -1 为默认值,可表示系统拦截
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Order {

    int value() default -1;
}

定义LogInterceptor,继承AbstractInterceptor,用于关键日志打印;

@Order(1)
public class LogInterceptor extends AbstractInterceptor {

    private long startTime;

    private long endTime;

    private static Logger logger = Logger.getLogger(LogInterceptor.class.getName());

    @Override
    public void before(Object target, Method method,Object[] args) {
        startTime = System.currentTimeMillis();
        logger.info("代理对象方法执行。参数args="+args);
    }

    @Override
    public void after(Object target,Method method,Object result) {
        endTime = System.currentTimeMillis();
        logger.info("方法执行结束。耗时:" + (endTime - startTime) + "ms");
    }
}

定义AuthInterceptor拦截器,用于接口权限拦截;

@Order(2)
public class AuthInterceptor extends AbstractInterceptor {
    @Override
    public void before(Object target, Method method, Object[] args) throws Exception {
        System.out.println("run start..."+ this.getClass());
    }

    @Override
    public void after(Object target, Method method, Object result) throws Exception {
        System.out.println("run end..."+this.getClass());
    }
}

这里 LogInterceptor 和 AuthInterceptor 的调用先后顺序就是先 Log 再Auth。

修改下前面的回调拦截器的方法,新增interceptorList列表,用于存储拦截器,注意这里是先后顺序存储,先后调用。

public class ToolProxyCallback implements MethodInterceptor {

   private Object target;

   public ToolProxyCallback(Object target) {
      this.target = target;
   }

   private static List<AbstractInterceptor> interceptorList = new LinkedList<>();

   // 注册拦截器
   public static void registerInterceptor(AbstractInterceptor interceptor) {
      interceptorList.add(interceptor);
   }


   @Override
   public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
	 // 执行拦截器的前置方法
      for(AbstractInterceptor interceptor : interceptorList) {
         interceptor.before(target,method,args);
      }

      System.out.println("准备执行目标方法;interceptorList.size="+interceptorList.size());
      Object resultObj = method.invoke(target,args);

       // 执行拦截器的后置方法
      for(AbstractInterceptor interceptor : interceptorList) {
         interceptor.after(target,method,resultObj);
      }

      return resultObj;
   }
}

添加拦截器,那么调用ToolProxyCallback#registerInterceptor 方法,将拦截器注册到interceptorList即可。

优化二:自动化注册拦截器

后续如果想要新增拦截器,那么只需要继承AbstractInterceptor就行。但是每次添加一个拦截器都要主动注册一下,还要判断下先后顺序,那就有点麻烦。

新增拦截器注解,通过扫描包,将有该注解的拦截器都注册进来,就可以实现自动化注册了。

/**
 * 有该注解表示为自定义拦截器
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ToolAspect {
}

通过扫描包,将拦截器统统注册进来。

public class AnnotationScanner {

    private static final String packageName = "agent.project";

    /**
     * 自动注册工具拦截器
     */
    public static void registerToolInterceptorList() {

        List<Class> list = ClassUtil.getAllClassByAnnotation(ToolAspect.class,packageName);

        // 从小到大排序
        Collections.sort(list, new Comparator<Class>() {
            @Override
            public int compare(Class clazz1, Class clazz2) {
                Order order1 = (Order)clazz1.getAnnotation(Order.class);
                Order order2 = (Order)clazz2.getAnnotation(Order.class);
                return order1.value() - order2.value();
            }
        });

        for(Class clazz : list) {
            AbstractInterceptor interceptor = null;
            try {
                interceptor = (AbstractInterceptor)clazz.newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            // 注册拦截器
            ToolProxyCallback.registerInterceptor(interceptor);
        }
    }
}

还记得前面的代理工厂吗 ToolProxyFactory。由于我们每次获取子类代理实例的时候都会使用这个类,那么将自动注册的调用放在这里就合适了。

public class ToolProxyFactory {

    static {
        AnnotationScanner.registerToolInterceptorList();	// 注册拦截器
    }

    /**
     * 获取子类代理实例
     * @param target 目标对象
     * @param callback 回调接口
     * @return
     */
    public static Object getProxyInstance(Object target, Callback callback) {
        // 工具类
        Enhancer enhancer = new Enhancer();
        // 设置父类
        enhancer.setSuperclass(target.getClass());
        // 设置回调函数。
        enhancer.setCallback(callback);

        return enhancer.create();   // 创建子类代理对象
    }

}

通过上述的优化一、二,实现了将代理职责拆分,同时自动化注册拦截器。在这基础上,客户端调用的方法不变,依旧通过ToolProxyFactory#getProxyInstance方法获取到子类代理,然后执行目标方法。

再扩展优化思考

针对现有的实现架构,留下几个Q:

  • 自动化注册拦截器会为所有目标方法实现了所有拦截器,但并不是每个目标方法所需要的拦截器都是相同的,那么就需要为目标方法实现特定的拦截器;

  • 获取子类代理实例的逻辑略为繁琐,需要认识的类有点多,能不能实现自动代理;(通过注解或者其它方式)

  • 能不能搞成即可使用cglib实现,又可自动切换为接口代理实现;(根据判断目标方法有没目标接口?)

  • 对比下Spring Aop有何区别与相同之处;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值