欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

8. Spring之AOP前奏

程序员文章站 2022-04-11 12:59:17
...

Spring之AOP前奏

1. 提出问题

情景:数学计算器
要求:

  1. 执行加减乘除运算
  2. 日志:在程序执行期间追踪正在发生的活动
  3. 验证:希望计算器只能处理正数的运算
常规实现

Calculator

 /**
 * @Date 2020/5/23 23:23
 * @Version 10.21
 * @Author DuanChaojie
 */
public interface Calculator {
    int add(int m,int n);
    
    int sub(int m,int n);
    
    // ...
}

CalculatorImpl

/**
 * @Date 2020/5/23 23:24
 * @Version 10.21
 * @Author DuanChaojie
 */
public class CalculatorImpl implements Calculator {

    public int add(int m, int n) {
        System.out.println("日志:执行了add方法参数1="+m+",参数2="+n);
        int result = m + n;
        System.out.println("日志:执行add方法结束,结果为"+result);
        return result;
    }
    
    public int sub(int m, int n) {
        System.out.println("日志:执行了sub方法参数1="+m+",参数2="+n);
        int result = m - n;
        System.out.println("日志:执行sub方法结束,结果为"+result);
        return result;
    }
    
    // ...
    
}

2. 问题分析

  1. 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
  2. 代码分散: 以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。

3. 动态代理

原理:
  1. 代理设计模式的原理:使用一个代理将原本对象包装起来,然后用该代理对象”取代”原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
    8. Spring之AOP前奏
方式:
  1. 基于接口实现动态代理: JDK动态代理
  2. 基于继承实现动态代理: CglibJavassist动态代理

4. 动态代理改进后

注释,之前在CalculatorImpl中的日志信息。

4.1 创建代理对象
  1. newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
  2. 内部类中使用Calculator,需要是final定义。
/**
 * @Date 2020/5/24 9:50
 * @Version 10.21
 * @Author DuanChaojie
 *     // java.lang.reflect.Proxy
 *     // newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)
 *     // 返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
 */
public class CalculatorProxy {

    /**
     * 帮calculator对象创建代理对象
     * @param calculator 被代理的对象
     * @return
     */
    public static Calculator getProxy(final Calculator calculator){

        InvocationHandler h = new InvocationHandler() {
            /**
             * 方法执行器,帮我们被代理对象执行目标方法
             * @param proxy 代理对象给jdk使用的,我们任何时候都不要使用这个对象
             * @param method 当前将要执行的目标对象
             * @param args 这个方法调用时外界传入的参数值
             * @return
             * @throws Throwable
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                //System.out.println("这是动态代理帮你执行的方法");
                // 利用反射执行目标方法
                // 内部类中使用,需要是final定义的
                Object result = null;
                try {
                    LogUtils.logStart(method,args);

                    // int a = 1/0; 异常的测试
                    
                    result = method.invoke(calculator, args);

                    LogUtils.logEnd(method,result);
                    
                } catch (Exception e) {
                    
                    LogUtils.logError(method,e);
                    
                    e.printStackTrace();
                }finally {
                    LogUtils.logFinally(method,result);
                }
                // 返回值必须返回出去外界才能拿到真正执行后的返回值
                return result;
            }
        };

        // 拿到接口
        Class<?>[] interfaces = calculator.getClass().getInterfaces();
        // 拿到类加载器
        ClassLoader loader = calculator.getClass().getClassLoader();

        // Proxy为目标对象创建代理对象
        Calculator cal  = (Calculator) Proxy.newProxyInstance(loader, interfaces, h);
        return cal;
    }
}
4.2 创建日志工具类
/**
 * @Date 2020/5/24 10:51
 * @Version 10.21
 * @Author DuanChaojie
 */
public class LogUtils {

    public static void logStart(Method method,Object args){
        System.out.println("["+method.getName()+"]:方法执行了,其参数列表为"+ Arrays.asList(args));

    }
    
    public static void logEnd(Method method,Object result){
        System.out.println("["+method.getName()+"]:方法正常执行完成了,其结果为"+ result);
    }
    
    public static void logError(Method method,Exception e){
        System.out.println("["+method.getName()+"]:方法执行出现了异常,原因为:"+ e.getCause());
    }
    
    public static void logFinally(Method method,Object result){
        System.out.println("["+method.getName()+"]:方法最终执行完成了,其结果为"+ result);
    }

}
4.3 测试动态代理
/**
 * @Date 2020/5/24 10:39
 * @Version 10.21
 * @Author DuanChaojie
 */
public class TestCalculator {

    @Test
    public void testCalculator() {
        Calculator calculator = new CalculatorImpl();
        // 没使用动态代理之前
        int addRes = calculator.add(2, 3);

        // 使用动态代理,如果拿到这个对象的代理对象,代理对象执行加减乘除
        Calculator proxy = CalculatorProxy.getProxy(calculator);
        
        // proxy.getClass() =  class com.sun.proxy.$Proxy4
        // 代理对象和被代理对象唯一能产生的关系就是实现了同一个接口
        System.out.println("proxy.getClass() = " + proxy.getClass());
        int add = proxy.add(5,6);
        System.out.println("测试结果 result = " + add);
    }
}

5. 使用动态代理实现存在的问题

  1. 写起来很复杂,实现复杂
  2. jdk默认的动态代理,如果目标对象没有实现任何接口,是无法为他创建代理对象的。