zoukankan      html  css  js  c++  java
  • 十四、【AOP】基本使用

    • AOP(Aspect Oriented Programming):面向切面编程。AOP是在我们原来写的代码的基础上,进行一定的包装,比如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者增强处理。我们需要实现一个代理来创建实例,实际运行的实例其实是生成的代理类的实例。
    • Spring的AOP和AspectJ?Spring AOP的底层实现有两种,一种是JDK的动态代理,另一种是CGLIB,Spring AOP没有用到AspectJ ,只是借鉴了它并添加了AspectJ 风格的注解,使用Aspectj必须用到它自己特殊的编译器和运行环境的插件。Spring AOP只是用到了AspectJ 的配置而已,没有用它的编译器、也没有用它的类加载器。Spring AOP对于没有实现接口的类,会用CGLIB来动态生成子类,否则使用JDK自带的动态代理。
    • Spring AOP:首先要说明的是,这里的Spring AOP 是纯的 Spring 代码,和 AspectJ 没什么关系,但是 Spring 延用了 AspectJ 中的概念,包括使用了 AspectJ 提供的 jar 包中的注解,但是不依赖于其实现功能。@Aspect、@Pointcut、@Before、@After 等注解都是来自于 AspectJ,但是功能的实现是纯 Spring AOP 自己实现的。

    基于@AspectJ注解的AOP配置,本文只介绍关于注解的方式,配置文件的方式来实现AOP不作分析

    1. 首先引入AOP所依赖的jar包
    <!-- AOP -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.0.8.RELEASE</version>
    </dependency>
    

    其中包含2个必须的包

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.0.8.RELEASE</version>
        <scope>compile</scope>
        <optional>true</optional>
    </dependency>
    

    如果使用了SpringBoot的话,直接添加如下依赖:

    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    
    1. 在 @AspectJ 的配置方式中,之所以要引入 aspectjweaver 并不是因为需要使用 AspectJ 的处理功能,而是因为 Spring 使用了 AspectJ 提供的一些注解,实际上还是纯的 Spring AOP 代码。明确一点,@AspectJ 采用注解的方式来配置使用 Spring AOP。

    开启 @AspectJ 的注解配置方式,有两种方式:

    一、使用xml方式:

    <aop:aspectj-autoproxy/>
    

    二、使用@EnableAspectJAutoProxy

    /**
     * @author zhangjianbing
     * @From www.zhangjianbing.com
     */
    @Configuration
    // 手动开启Aspect注解
    @EnableAspectJAutoProxy
    public class Config {
        
    }
    

    采用@Configuration配置文件的方式来模拟spring上下文环境

    /**
     * @author zhangjianbing
     * @From www.zhangjianbing.com
     */
    public class MainTest {
    
        public static void main(String[] args) {
            // 加载配置文件
            AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
            BusinessCalculate businessCalculate = ac.getBean(BusinessCalculate.class);
            int result1 = businessCalculate.calculate(10, 5);
            System.out.println("===================================================");
            RandomCalculate randomCalculate = ac.getBean(RandomCalculate.class);
            int result2 = randomCalculate.calculate(10, 5);
        }
    
    }
    

    一旦开启了上面的配置,那么所有使用 @Aspect 注解的 bean 都会被 Spring 当做用来实现 AOP 的配置类,称之为一个 Aspect(切面)。

    /**
     * @author zhangjianbing
     * @From www.zhangjianbing.com
     */
    @Aspect
    public class BeforeAdvice {
    
    }
    

    Demo介绍:

    1. 编写两个业务类(目的是为了测试不同的切入点)
    /**
     * @author zhangjianbing
     * @From www.zhangjianbing.com
     */
    public class BusinessCalculate {
    
        public int calculate(int i, int j) {
            int result = i / j;
            System.out.println("BusinessCalculate-业务方法执行。。。。。。");
            return result;
        }
    
    }
    
    /**
     * @author zhangjianbing
     * @From www.zhangjianbing.com
     */
    public class RandomCalculate {
    
        public int calculate(int i, int j) {
            int result = i / j;
            System.out.println("RandomCalculate-业务方法执行。。。。。。");
            return result;
        }
    
    }
    
    1. 编写切入点SystemArchitecture类(Spring建议是这个名字) (此类上面不需要加@Aspect)
    /**
     * @author zhangjianbing
     * @From www.zhangjianbing.com
     */
    public class SystemArchitecture {
    
        @Pointcut("bean(*domCalculate)")
        public void definitionPointCut(){}
    
        // execution(* *(..)) 所有方法
        // 抽取公共表达式
        // @Pointcut("execution(public int com.nmys.story.springCore.aopdemo.BusinessCalculate.*(..))")
        @Pointcut("execution(* com.nmys.story.springCore.aopdemo.BusinessCalculate.*(..))")
    	// @Pointcut("bean(*Calculate)")
        public void pointCut() {}
    
    }
    

    介绍一下切入点的表达式:

    • within:指定所在类或所在包下面的方法(Spring AOP 独有)如 @Pointcut("within(com.nmys.story.service..*)")。
    • @annotation:方法上具有特定的注解,如 @Subscribe 用于订阅特定的事件。如 @Pointcut("execution( .*(..)) && @annotation(com.nmys.story.Subscribe)")。
    • bean(idOrNameOfBean):匹配 bean 的名字(Spring AOP 独有)如 @Pointcut("bean(*Service)")。
    • execution 来正则匹配方法签名(根据业务需求来查阅一下表达式的具体写法)。

    上面匹配中,通常 "." 代表一个包名,".." 代表包及其子包,方法参数任意匹配使用两个点 ".."。

    1. 定义切面(切面最好不要都揉在一个类中,显得杂乱无章,建议分开写,如下)
    /**
     * @author zhangjianbing
     * @From www.zhangjianbing.com
     */
    @Aspect
    public class BeforeAdvice {
    
        // 前置通知
        @Before("com.zhangjianbing.story.springCore.aopdemo.SystemArchitecture.pointCut()")
        public void logBefore(JoinPoint joinPoint) {
            // 获取传入的参数
            Object[] args = joinPoint.getArgs();
            for (int i = 0; i < args.length; i++) {
                System.out.println(args[i]);
            }
            System.out.println("调用方法之前执行logBefore。。。。。。");
        }
    
        // 前置通知
        @Before("com.zhangjianbing.story.springCore.aopdemo.SystemArchitecture.definitionPointCut()")
        public void randomBefore() {
            System.out.println("调用方法之前执行randomBefore。。。。。。");
        }
    
    }
    

    其中如果需要拿到方法的入参,则用JoinPoint类来获得

    /**
     * @author zhangjianbing
     * @From www.zhangjianbing.com
     */
    @Aspect
    public class AfterAdvice {
    
        // 后置通知
        @After("com.nmys.story.springCore.aopdemo.SystemArchitecture.pointCut()")
        public void logAfter() {
            System.out.println("调用方法之后执行logAfter。。。。。。");
        }
    
        // 后置通知
        @After("com.nmys.story.springCore.aopdemo.SystemArchitecture.definitionPointCut()")
        public void randomAfter() {
            System.out.println("调用方法之后执行randomAfter。。。。。。");
        }
    
    }
    

    上面加上了@Aspect注解表明spring会将此bean当作切面来管理

    介绍一下其他通知方法的写法:

    /**
     * @author zhangjianbing
     * @From www.zhangjianbing.com
     */
    @Aspect
    public class LogAspect {
    
    //    // 前置通知
    //    @Before("com.nmys.story.springCore.aopdemo.SystemArchitecture.pointCut()")
    //    public void logBefore() {
    //        System.out.println("调用方法之前执行logBefore。。。。。。");
    //    }
    //
    //    // 前置通知
    //    @Before("com.nmys.story.springCore.aopdemo.SystemArchitecture.definitionPointCut()")
    //    public void randomBefore() {
    //        System.out.println("调用方法之前执行randomBefore。。。。。。");
    //    }
    
    //    // 后置通知
    //    @After("pointCut()")
    //    public void logAfter() {
    //        System.out.println("调用方法之后执行logAfter。。。。。。");
    //    }
    //
    //    // 正常返回通知(抛出异常则不会执行)
    //    @AfterReturning("pointCut()")
    //    public void logReturn() {
    //        System.out.println("方法正常返回结果后执行logReturn。。。。。。");
    //    }
    //
    //    // 异常通知
    //    @AfterThrowing("pointCut()")
    //    public void logException() {
    //        System.out.println("logException。。。。。。");
    //    }
    //
    //    @Around("pointCut()")
    //    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    //        // 比前置通知提前执行
    //        System.out.println("环绕通知 - around 执行目标方法之前。。。。。。");
    //        // 利用反射机制来调用目标方法
    //        Object proceed = proceedingJoinPoint.proceed();
    //        System.out.println("环绕通知 - around 执行目标方法之后。。。。。。");
    //        return proceed;
    //    }
    }
    

    @Around():环绕通知在实际中不常用,proceed()方法实际是用了反射来调用目标方法,上面的具体注释很清楚。

    1. 编写config类
    /**
     * @author zhangjianbing
     * @From www.zhangjianbing.com
     */
    @Configuration
    @EnableAspectJAutoProxy
    public class Config {
    
        // 将@Aspect修饰的类和业务类都交给spring来管理
        @Bean
        public BeforeAdvice beforeAdvice() {
            return new BeforeAdvice();
        }
    
        @Bean
        public AfterAdvice afterAdvice() {
            return new AfterAdvice();
        }
    
        @Bean
        public BusinessCalculate businessCalculate() {
            return new BusinessCalculate();
        }
    
        @Bean
        public RandomCalculate randomCalculate() {
            return new RandomCalculate();
        }
    
    }
    

    需要将业务类和切面类都交给spring来管理,同时需要开启Aspect(@EnableAspectJAutoProxy)

    1. 编写测试类
    /**
     * @author zhangjianbing
     * @From www.zhangjianbing.com
     */
    public class MainTest {
    
        public static void main(String[] args) {
            // 加载配置文件
            AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
            BusinessCalculate businessCalculate = ac.getBean(BusinessCalculate.class);
            int result1 = businessCalculate.calculate(10, 5);
            System.out.println("===================================================");
            RandomCalculate randomCalculate = ac.getBean(RandomCalculate.class);
            int result2 = randomCalculate.calculate(10, 5);
        }
    
    }
    
    1. 运行结果:
    10
    5
    调用方法之前执行logBefore。。。。。。
    BusinessCalculate-业务方法执行。。。。。。
    调用方法之后执行logAfter。。。。。。
    ===================================================
    调用方法之前执行randomBefore。。。。。。
    RandomCalculate-业务方法执行。。。。。。
    调用方法之后执行randomAfter。。。。。。
    

    以上结果表明对目标方法进行了增强。

    最后再说一下,以上使用的是Spring的AOP,和AspectJ基本没什么关系。

  • 相关阅读:
    [题解] [NOIP2008] 双栈排序——关系的冲突至图论解法
    [搬运] [贪心]NOIP2011 观光公交
    [总结] 最短路径数问题
    [持续更新]一些zyys的题的集合
    [教程]Ubuntu下完整配置自动壁纸切换
    在NOILINUX下的简易VIM配置
    [模板]ST表浅析
    21、Android--RecyclerView
    20、Android--GridView
    19、Android--ListView
  • 原文地址:https://www.cnblogs.com/zhangjianbing/p/13726723.html
Copyright © 2011-2022 走看看