Spring - AOP(10)

编写一个简单的需求:要求在程序执行期间追踪正在发生的活动

// 接口
public interface AtithmeticCalculator {
    int add(int i,int j);
    int sub(int i,int j);
    int mul(int i,int j);
    int div(int i,int j);
}
// 实现
public class AtithmeticCalculatorImpl implements AtithmeticCalculator {
    @Override
    public int add(int i, int j) {
        System.out.println("The method add begins with [" + i + "," + j + "]");
        int result = i + j;
        System.out.println("The method add ends with " + result);
        return result;
    }
    @Override
    public int sub(int i, int j) {
        System.out.println("The method add begins with [" + i + "," + j + "]");
        int result = i - j;
        System.out.println("The method add ends with " + result);
        return result;
    }
    @Override
    public int mul(int i, int j) {
        System.out.println("The method add begins with [" + i + "," + j + "]");
        int result = i * j;
        System.out.println("The method add ends with " + result);
        return result;
    }
    @Override
    public int div(int i, int j) {
        System.out.println("The method add begins with [" + i + "," + j + "]");
        int result = i / j;
        System.out.println("The method add ends with " + result);
        return result;
    }
}
// Main
public class Main {
    public static void main(String[] args) {
        AtithmeticCalculator atithmeticCalculator = new AtithmeticCalculatorImpl();
        int result = atithmeticCalculator.add(1,2);
        System.out.println(result);
        result = atithmeticCalculator.div(4,2);
        System.out.println(result);
    }
}
// Output
The method add begins with [1,2]
The method add ends with 3
3
The method add begins with [4,2]
The method add ends with 2
2

上面例子中的实现代码存在的问题:

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

使用动态代理解决上述问题 代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理,代理对象决定是否以及合适将方法调用转到原始对象上

// 接口
public interface ArithmeticCalculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}
// 实现类
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }
    @Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }
    @Override
    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }
    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}
// 代理
public class ArithmeticCalculatorLoggingProxy {
    // 要代理的对象
    private ArithmeticCalculator target;

    public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target){
        this.target = target;
    }
    public ArithmeticCalculator getLoggingProxy(){
        ArithmeticCalculator proxy = null;
        // 代理对象由哪一个类加载器负责加载
        ClassLoader loader = target.getClass().getClassLoader();
        // 代理对象的类型,即其中有哪些方法
        Class [] interfaces = new Class[]{ArithmeticCalculator.class};
        // 当调用代理对象其中的方法时,该执行的代码
        InvocationHandler h = new InvocationHandler() {
            /**
             *
             * @param proxy 正在返回的那个代理对象,一般情况下,在invoke方法中都不使用该对象
             * @param method 正在被调用的方法
             * @param args 调用方法时,传入的参数
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();
                // 日志
                System.out.println("The method" + methodName + "begins with " + Arrays.asList(args));
                // 执行方法
                Object result = method.invoke(target,args);
                // 日志
                System.out.println("The method" + methodName + "ends with " + result);
                return result;
            }
        };
        proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader,interfaces,h);
        return proxy;
    }
}
// Main
public class Main {
    public static void main(String[] args) {
        ArithmeticCalculator target = new ArithmeticCalculatorImpl();
        ArithmeticCalculator proty = new ArithmeticCalculatorLoggingProxy(target).getLoggingProxy();
        int result = proty.add(1,2);
        System.out.println(result);
        result = proty.div(8,2);
        System.out.println(result);
    }
}

使用AOP

AOP面向切面编程,是一种新的方法论,并对传统OOP(面向对象编程)的补充

在应用AOP编程时,仍然需求定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类,这样依赖横切关注点就被模块化到特殊的对象(切面)里

AOP术语

切面(Aspect):横切关注点 (跨越应用程序多个模块的功能)被模块化的特殊对象 通知(Advice):切面必须要完成的工作 目标(Target):被通知的对象 代理(Proxy):向目标对象应用通知之后创建的对象 连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前,调用后,方法抛出异常后等。 切点(pointcut):每个类都拥有多个连接点。即连接点是程序类中客观存在的事务。AOP通过切点定位到特定的连接点

Spring中启用AspectJ注解支持

AspectJ:Java社区里最完整最流行的AOP框架

通知是标注有注解的简单的Java方法: @Before 前置通知,在方法执行之前执行 @After 后置通知,在方法执行之后执行 @AfterRunning 返回通知,在方法返回结果之后执行 @AfterThrowing 异常通知,在方法抛出异常之后 @Around环绕通知,环绕着方法执行

// maven注入依赖
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>5.1.12.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.4</version>
</dependency>
// ArithmeticCalculator
public interface ArithmeticCalculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}
// ArithmeticCalculatorImpl
@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }
    @Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }
    @Override
    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }
    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}
// ArithmeticCalculatorLoggingProxy
public class ArithmeticCalculatorLoggingProxy {
    // 要代理的对象
    private ArithmeticCalculator target;

    public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target){
        this.target = target;
    }
    public ArithmeticCalculator getLoggingProxy(){
        ArithmeticCalculator proxy = null;
        // 代理对象由哪一个类加载器负责加载
        ClassLoader loader = target.getClass().getClassLoader();
        // 代理对象的类型,即其中有哪些方法
        Class [] interfaces = new Class[]{ArithmeticCalculator.class};
        // 当调用代理对象其中的方法时,该执行的代码
        InvocationHandler h = new InvocationHandler() {
            /**
             * @param proxy 正在返回的那个代理对象,一般情况下,在invoke方法中都不使用该对象
             * @param method 正在被调用的方法
             * @param args 调用方法时,传入的参数
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();
                // 日志
                System.out.println("The method" + methodName + "begins with " + Arrays.asList(args));
                // 执行方法
                Object result = method.invoke(target,args);
                // 日志
                System.out.println("The method" + methodName + "ends with " + result);
                return result;
            }
        };
        proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader,interfaces,h);
        return proxy;
    }
}
// LoggingAspect
@Component
@Aspect
public class LoggingAspect {

    // 声明该方法是一个前置通知:在目标方法开始之前执行
    // @Before("execution(public int com.sangyu.test10.ArithmeticCalculator.add(int, int))") // 只针对add方法
    @Before("execution(public int com.sangyu.test10.ArithmeticCalculator.*(int, int))") // 所有方法:add、mul、div、sub
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("The method " + methodName + " begins with" + args);
    }
}
// Main
public class Main {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        ArithmeticCalculator arithmeticCalculator = ctx.getBean(ArithmeticCalculator.class);
        int result = arithmeticCalculator.add(3,6);
        System.out.println("result: " + result);
        result = arithmeticCalculator.sub(2,1);
        System.out.println("result: " + result);
        result = arithmeticCalculator.mul(2,1);
        System.out.println("result: " + result);
        result = arithmeticCalculator.div(6,3);
        System.out.println("result: " + result);
    }
}

前置通知

在方法执行之前执行的通知,使用@Before注解,并将切入点表达式的值作为注解值

编写AspectJ切入点表达式

通过方法的签名来匹配各种方法: execution * com.sangyu.test10.ArithmeticCalculator.*(...) 匹配ArithmeticCalculator中声明的所有方法,第一个代表任意修饰符及任意返回值,第二个 代表任意方法,...表示匹配任意数量的参数(若目标类于接口与该切面在同一个包中,可以省略包名)

execution public * com.sangyu.test10.ArithmeticCalculator.*(...) 匹配ArithmeticCalculator接口的所有共有方法

execution public double com.sangyu.test10.ArithmeticCalculator.*(...) 匹配ArithmeticCalculator中返回double类型数值的方法

execution public double com.sangyu.test10.ArithmeticCalculator.*(double,...) 匹配第一个参数为double类型的方法 ...匹配任意数量任意类型的参数

execution public double com.sangyu.test10.ArithmeticCalculator.*(double,double) 匹配参数类型为double,double类型的方法

execution * *,*(...) 执行任意类的任意方法

JoinPoint

通知方法中beforeMethod()中声明一个类型为JoinPoint的参数,可以访问链接细节,如方法名称和参数值

后置通知

在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候,下面的后置通知记录了方法的终止,一个切面可以包括一个或者多个通知

@Component
@Aspect
public class LoggingAspect {
    // 声明该方法是一个前置通知:在目标方法开始之前执行
     @Before("execution(public int com.sangyu.test10.ArithmeticCalculator.add(int, int))") // 只针对add方法
//    @Before("execution(public int com.sangyu.test10.ArithmeticCalculator.*(int, int))") // 所有方法:add、mul、div、sub
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("The method " + methodName + " begins with" + args);
    }
    // 后置通知:在目标方法执行后(无论是否发生异常),执行的通知
    @Before("execution(public int com.sangyu.test10.ArithmeticCalculatorImpl.*(int, int))") // 所有方法:add、mul、div、sub
    public void afterMethod(JoinPoint joinPoint){
         String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " end");
    }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 栈(stack)

    栈是一个先入后出的有序列表,栈中的元素插入和删除只能在线性表的同一端进行。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底。...

    桑鱼
  • Java-复用类

    第一种,只需在新的类中产生现有类的对象。由于新的类是由现有的类所组成,所以这种方式称为组合,该方法只是复用了现有程序代码的功能,而非它的形式;

    桑鱼
  • Java设计模式-抽象工厂模式

    抽象工厂模式,提供了一个创建一些列相关或相互依赖对象的接口,而无需指定它们具体的类

    桑鱼
  • 直方图实现快速中值滤波

    中值滤波能够有效去除图像中的异常点,具有去除图像噪声的作用。传统中值滤波的算法一般都是在图像中建立窗口,然后对窗口内的所有像素值进行排序,选择排序后的中间值作为...

    一棹烟波
  • Java高并发之无锁与Atomic源码分析

    用户1216491
  • 优化 Java 中的多态代码

    Oracle的Java是一个门快速的语言,有时候它可以和C++一样快。编写Java代码时,我们通常使用接口、继承或者包装类(wrapper class)来实现多...

    用户1257393
  • UCF Local Programming Contest 2015 A~~H

    用户7727433
  • 【2019秋PAT乙级真题】7-3 缘分数 (20 分)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    韩旭051
  • 迷宫的最短路径

    题意:给定一个大小为N * M的迷宫,迷宫由通道与墙壁组成,每一步可以向邻接的上下左右四格的通道移动。请求出从起点到终点所需要的最下步数。

    用户7727433
  • OpenCV图像处理专栏十一 | IEEE Xplore 2015的图像白平衡处理之动态阈值法

    这是OpenCV图像处理专栏的第十一篇文章,之前介绍过两种处理白平衡的算法,分别为灰度世界算法和完美反射算法。今天来介绍另外一个自动白平衡的算法,即动态阈值法,...

    BBuf

扫码关注云+社区

领取腾讯云代金券