zoukankan      html  css  js  c++  java
  • Java -- Spring学习笔记5、AOP面向切面编程

    1、AspectJ对AOP的实现

    AspectJ 是一个优秀面向切面的框架,它扩展了Java语言,提供了强大的切面实现、实现了AOP的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以、在Spring中使用AOP开发时,一般使用AspectJ的实现方式。

    2、AspectJ的通知类型

    • 常用的有五种类型:
      • 前置通知
      • 后置通知
      • 环绕通知
      • 异常通知
      • 最终通知

    3、AspectJ的切入点表达式

    • AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:
    execution(modifiers-pattern? ret-type-pattern  declaring-type-pattern?name-pattern(param-pattern)  throws-pattern?)
    
    • 解释:
      • modifiers-pattern 访问权限类型
      • ret-type-pattern 返回值类型
      • declaring-type-pattern 包名类名
      • name-pattern(param-pattern) 方法名(参数类型和参数个数)
      • throws-pattern 抛出异常类型
      • ?表示可选的部分
    • 以上表达式共 4 个部分。
    execution(访问权限 方法返回值 方法声明(参数) 异常类型)
    

    切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:

    • *:0至多个任意字符
    • ..:用在方法参数中、表示任意参数。用在包名后表示当前包及其子包路径。
    • +:用在类名后、表示当前类及其子类。用在接口后、表示当前接口以及实现类。
    • 举例:
    execution(public * *(..))
    指定切入点为:任意公共方法。
    execution(* set*(..))
    指定切入点为:任何一个以“set”开始的方法。
    execution(* com.rg.service.*.*(..))
    指定切入点为:定义在 service 包里的任意类的任意方法。
    execution(* com.rg.service..*.*(..))
    指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。
    execution(* *..service.*.*(..))
    指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
    ........................
    

    4、AspectJ的开发环境

    • 添加maven依赖:
    <!-- 依赖 -->
    <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.11</version>
          <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>5.2.5.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aspects</artifactId>
          <version>5.2.5.RELEASE</version>
        </dependency>
    </dependencies>
    <!-- 插件 -->
    <build>
        <plugins>
          <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
              <source>1.8</source>
              <target>1.8</target>
            </configuration>
          </plugin>
        </plugins>
    </build>
    

    5、AspectJ基于注解的AOP实现

    5.1、前置通知@Before -- 目标方法执行之前执行

    • 在dao层定义接口和实现类、如下:
    //定义接口
    public interface SomeService
    {
        void doSome();
    }
    //实现类
    public class SomeServiceImpl implements SomeService
    {
        @Override
        public void doSome()
        {
            System.out.println("...........执行了doSome()方法............");
        }
    }
    
    • 定义切面类、添加前置通知增强方法、如下:
    /**
     * @Aspect:AspectJ框架的注解、表示当前类是切面类
     */
    @Aspect
    public class MyAspect
    {
        /**
         * @Before:前置通知、也就是在被切入方法之前执行
         * value:表示切入位置、这里的表达式指:任何返回值类型和任何包下的doSome()方法,参数类型和个数也是任意。
         * 位置:方法上边
         */
        @Before(value = "execution(* *..doSome(..))")
        public void before()
        {
            System.out.println("前置通知、在方法前执行........");
        }
    }
    
    • 声明目标类对象和切面类对象、如下:
    <!--目标类对象、接口实现类、因为在测试方法中需要调用该类中的业务方法-->
    <bean id="someService" class="com.rg.dao.impl.SomeServiceImpl"/>
    <!--切面类对象-->
    <bean id="myAspect" class="com.rg.aspect.MyAspect"/>
    
    • 注册 AspectJ的自动代理、如下:
    <!--
        定义好切面Aspect后,需要通知Spring容器,让容器生成“目标类+切面”的代理
        对象。代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj 的
        自动代理生成器,其就会自动扫描到@Aspect注解,并按通知类型与切入点,将其织入,并
        生成代理。
    
        其工作原理是,<aop:aspectj-autoproxy/>通过扫描找到@Aspect 定义的切面类,再由切
        面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
    -->
    <aop:aspectj-autoproxy/>
    
    • 测试方法:
    public class MyTest
    {
        @Test
        public void test01()
        {
            //指定spring配置文件
            String config = "applicationContext.xml";
            //创建spring容器对象
            ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
            //从spring容器中获取对象、使用id
            SomeService someService = (SomeService) ctx.getBean("someService");
            //执行对象的业务方法
            someService.doSome();
        }
    }
    
    • 控制台输出:
    前置通知、在方法前执行........
    ...........执行了doSome()方法............
    

    5.2、JoinPoint参数

    所有的通知方法均可包含一个JoinPoint类型的参数、该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象等。

    • 对上边接口和测试方法稍作修改:
    //添加两个参数
    void doSome(String name,int age);
    
    • 切面方法中通过JoinPoint获取参数信息:
    /**
      * @Before:前置通知、也就是在被切入方法之前执行 value:表示切入位置、这里的表达式指:任何返回值类型和任何包下的doSome()方法,参数类型和个数也是任意。
      * 位置:方法上边
    */
    @Before(value = "execution(* *..doSome(..))")
    public void before(JoinPoint jp)
    {
          System.out.println("被织入方法签名:" + jp.getSignature());
          System.out.println("被织入方法中的参数数量:" + jp.getArgs().length);
    
          Object argList[] = jp.getArgs();
          for (Object arg : argList)
          {
                System.out.println(arg);
          }
    
          System.out.println("前置通知、在方法前执行........");
    }
    
    • 测试方法调用业务方法:
    @Test
        public void test01()
        {
            //指定spring配置文件
            String config = "applicationContext.xml";
            //创建spring容器对象
            ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
            //从spring容器中获取对象、使用id
            SomeService someService = (SomeService) ctx.getBean("someService");
            //执行对象的业务方法
            someService.doSome("Jack",18);
        }
    
    • 控制台输出:
    被织入方法签名:void com.rg.dao.SomeService.doSome(String,int)
    被织入方法中的参数数量:2
    Jack
    18
    前置通知、在方法前执行........
    ...........执行了doSome()方法............
    

    5.3、后置通知@AfterReturning -- 目标方法执行之后执行

    因为在方法后执行,所以可以获取方法的返回值信息、该注解有一个returning属性、就是用于指定接收方法返回值的变量名的、也就是说、在增强方法的参数列表中、除了可以包含JoinPoint参数外,还可以包含用于接收返回值的变量。该变量最好为Object类型,因为目标方法的返回值可能是任何类型。

    • 在接口中添加方法、如下:
    public interface SomeService
    {
        void doSome(String name,int age);
        String getName();
    }
    //实现类中实现该方法
    @Override
        public String getName()
        {
            System.out.println("...........执行了getName()方法............");
            return "大卫";
        }
    
    • 在切面类中添加后置通知增强方法、如下:
    /**
         * 后置通知
         * @param name:目标方法返回值
         */
        @AfterReturning(value = "execution(* *..getName(..))", returning = "name")
        public void after(Object name)
        {
            System.out.println("后置通知、在目标方法执行后执行、目标方法返回值是:" + name);
        }
    
    • 测试方法:
    @Test
        public void test01()
        {
            //指定spring配置文件
            String config = "applicationContext.xml";
            //创建spring容器对象
            ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
            //从spring容器中获取对象、使用id
            SomeService someService = (SomeService) ctx.getBean("someService");
            //执行对象的业务方法
            someService.getName();
        }
    
    • 控制台输出:
    ...........执行了getName()方法............
    后置通知、在目标方法执行后执行、目标方法返回值是:大卫
    

    5.4、环绕通知@Around -- 目标方法执行之前之后执行

    环绕通知、就是将目标方法拿到切面方法中、在增强方法中获取到的目标方法上边和下边加上需要的内容。方法有一个 ProceedingJoinPoint类型的参数、调用proceed()方法,也就是执行目标方法、若目标方法有返回值,则该方法的返回值就是目标方法的返回值。

    • 在接口中添加方法、如下:
    public interface SomeService
    {
        void doSome(String name,int age);
        String getName();
        String getAddress();
    }
    @Override
    public String getAddress()
    {
        System.out.println("...........执行了getAddress()方法............");
        return "中国上海";
    }
    
    • 在切面类中添加环绕通知增强方法:
    @Around(value = "execution(* *..getAddress(..))")
        public Object around(ProceedingJoinPoint pj) throws Throwable
        {
            Object res = null;
            System.out.println("前置功能.....");
            //proceed():目标方法、并接收目标方法的返回值
            res = pj.proceed();
            System.out.println("后置功能.....");
            return res;
        }
    
    • 测试方法:
    @Test
        public void test01()
        {
            //指定spring配置文件
            String config = "applicationContext.xml";
            //创建spring容器对象
            ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
            //从spring容器中获取对象、使用id
            SomeService someService = (SomeService) ctx.getBean("someService");
            //执行对象的业务方法
            Object res = someService.getAddress();
            System.out.println(res);
        }
    
    • 控制台输出:
    前置功能.....
    ...........执行了getAddress()方法............
    后置功能.....
    中国上海
    

    5.5、@Pointcut定义切入点

    如果一个execution切入点表达式可以被多个通知增强方法使用、就可以使用AspectJ提供@Pointcut注解,用于定义execution 切入点表达式、实现代码复用。

    其用法是、将@Pointcut注解在一个方法之上,那么所有的 execution的value属性值均可使用该方法名作为切入点。这个使用@Pointcut注解的方法一般使用private,里边也不写代码、没用实际作用。

    • 对环绕通知的切面类中代码稍作修改、如下:
    /**
         * myCut():下边的方法名就是myCut()
         * @param pj
         * @return
         * @throws Throwable
         */
        @Around(value = "myCut()")
        public Object around(ProceedingJoinPoint pj) throws Throwable
        {
            Object res = null;
            System.out.println("前置功能.....");
            //proceed():目标方法、并接收目标方法的返回值
            res = pj.proceed();
            System.out.println("后置功能.....");
            return res;
        }
    
        @Pointcut(value = "execution(* *..getAddress(..))")
        private void myCut()
        {
          //不写代码
        }
    
    • 测试方法同上、输出结果、如下:
    前置功能.....
    ...........执行了getAddress()方法............
    后置功能.....
    中国上海
    

    当然、不光有前置、后置、环绕、还有异常通知、最终通知,但是都不常用,了解就行。

  • 相关阅读:
    个人兴趣与公司业务的关系
    分析能力的8个等级(转)
    DSL应用的优点
    架构师应具备的概要技能
    Cheetah
    不能运行VS2005的DSL Tool例子
    推荐:原型工具GUI Design Studio
    Viewpoints 1.0 for Visual Studio .NET 2008
    原创故事 - 不死鸡和不死牛的故事
    Antlr构建表达式引擎
  • 原文地址:https://www.cnblogs.com/dcy521/p/14770869.html
Copyright © 2011-2022 走看看