Spring-Aop-应用

  1. 定义:

    1. Aspect Oriented Programming 面向切面编程

    2. 切面本质上是个类,全名:切面类,切面(Aspect)类中可以指定五种类型的通知(advice)方法。

    3. 底层原理:动态代理,

    4. 实现AOP的框架有三个:

      1. spring AOP 、AspectJ、JBoss AOP
      2. spring AOP只支持到方法的连接点,另外两个还可以支持字段和构造器的连接点
      3. 直接在方法上做文章就可以了,在构造器上做什么文章?以后再研究
    5. 通知、切点、连接点术语

      1. 通知(Advice):定义了切面是什么和何时调用,何时调用包含以下几种:需要增加的操作

        1. BeforeAdvice:在目标方法执行之前调用前置通知方法
        2. AfterAdvice:在目标方法执行之后调用后置通知方法,无论目标方法是否执行成功
        3. After-returning-Advice:在目标方法执行成功之后调用返回通知方法
        4. After-throwing-Advice:在目标方法执行抛出异常时调用异常通知方法
        5. AroundAdvice:目标方法在环绕通知方法的方法体中间调用
    6. 切点(PointCut):

      1. 定义:定义了在目标类的目标方法上切入通知方法,切点会匹配要要织入通知的一个或者多个连接点,我们通常使用明确的方法来指定这些切点,

      或者利用execution表达式定义匹配到的类或者方法名来指定切点,指定需要增加操作的类方法,比如Service、Dao(有接口的实现类)

      1. 执行(* com.sample.service.impl … *。*(…))

      2. 符号含义
        执行()表达式的主体;
        第一个” *“符号表示返回值的类型任意;
        com.sample.service.implAOP所切的服务的包名,即,我们的业务部分
        包名后面的” …“表示当前包及子包
        第二个” *“表示类名,*即所有类。此处可以自定义,下文有举例
        。*(…)表示任何方法名,括号表示参数,两个点表示任何参数类型
      3. 连接点(jointPoint)

      4. 在应用程序执行过程中能够插入切面的一个点,这个点可以是调用方法时,也可以是修改一个字段时,切面代码根据这些连接点插入到应用的正常流程中,并添加新的行为,如日志、事务、安全、缓存等,连接通知和切点的标签,指定哪个哪个切点和哪个通知有关联

  2. 问题

    1. 事务操作、权限校验、日志记录、缓存存储等
  3. 如何使用

    1. 比依赖注入多了一种实现方式,xml、接口、注解

    2. 方式一xml:使用自定义通知,然后在xml中声明

      1. 在xml中的配置元素声明切面

        1. AOP配置元素描述
          <aop:advisor>定义AOP通知器
          <aop:after>定义AOP后置通知(不管该方法是否执行成功)
          <aop:after-returning>在方法成功执行后调用通知
          <aop:after-throwing>在方法抛出异常后调用通知
          <aop:around>定义AOP环绕通知
          <aop:aspect>定义切面
          <aop:aspect-autoproxy>定义@AspectJ注解驱动的切面
          <aop:before>定义AOP前置通知
          <aop:config>顶层的AOP配置元素,大多数的aop:*包含在aop:config元素内
          <aop:declare-parent>为被通知的对象引入额外的接口,并透明的实现
          <aop:pointcut>定义切点
        2. 自定义切面

        3. package com.tf.aop.log;
          import org.aspectj.lang.ProceedingJoinPoint;
          
          public class XmlAopDemoUserLog {
              //    前置通知方法
              public void beforeLog() {
                  System.out.println("开始执行前置通知  日志记录");
              }
              //    后置通知方法
              public void afterLog() {
                  System.out.println("开始执行后置通知 日志记录");
              }
              //    返回通知方法
              public void afterReturningLog() {
                  System.out.println("方法成功执行后返回通知方法 日志记录");
              }
              //    异常通知方法
              public void afterThrowingLog() {
                  System.out.println("方法抛出异常后执行异常通知方法 日志记录");
              }
          
              //    环绕通知方法
              public Object aroundLog(ProceedingJoinPoint joinpoint) {
                  Object result = null;
          
                  try {
                      System.out.println("环绕通知方法:前置通知方法");
                      long start = System.currentTimeMillis();
                      result =  joinpoint.proceed();// 目标方法执行
                      long end = System.currentTimeMillis();
                      System.out.println("总共执行时长" + (end - start) + " 毫秒");
                      System.out.println("环绕通知方法:返回通知方法");
          
                  } catch (Throwable t) {
                      System.out.println("环绕通知方法:异常通知方法");
                      System.out.println("出现错误");
                  }
                  System.out.println("环绕通知方法:后置通知方法");
                  return result;
              }
          }
          
        4. 创建需要增加操作的类

          2. 4. package com.tf.aop.dao;
          
          public interface UserDao {
              void getUser();
              void addUser(String name)  ;
          
          }
          
          package com.tf.aop.dao;
          
          import org.springframework.stereotype.Repository;
          
          @Repository
          public class UserDaoImpl implements UserDao {
              public String getUser()  {
                  System.out.println("查询返回了一个用户");
                  return "提莫";
              }
          
              public void addUser(String name) {
                  //        System.out.println(2/0);
                  System.out.println("增加了一个用户:"+name);
              }
          }
          
        5. xml文件

          <?xml version="1.0" encoding="UTF-8"?>
          <beans xmlns="http://www.springframework.org/schema/beans"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns:context="http://www.springframework.org/schema/context"
                 xmlns:aop="http://www.springframework.org/schema/aop"
                 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
                          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
          
              <!-- 配置自动扫描包 -->
              <context:component-scan base-package="com.tf.aop"></context:component-scan>
              <!-- 注册切面类到ioc -->
              <bean id="xmlAopDemoUserLog" class="com.tf.aop.log.XmlAopDemoUserLog"></bean>
              <!-- 使用自定义通知 -->
              <aop:config>
                  <!--声明切点-->
                  <aop:pointcut id="logPoint" expression="execution(* com.tf.aop.dao.UserDaoImpl.*(..))"></aop:pointcut>
                  <!--声明切面 -->
                  <aop:aspect ref="xmlAopDemoUserLog">
                      <!--声明通知-->
                      <aop:before method="beforeLog" pointcut-ref="logPoint"></aop:before>
                      <aop:after method="afterLog" pointcut-ref="logPoint"></aop:after>
                      <aop:after-returning method="afterReturningLog" pointcut-ref="logPoint"></aop:after-returning>
                      <aop:after-throwing method="afterThrowingLog" pointcut-ref="logPoint"></aop:after-throwing>
                      <aop:around method="aroundLog" pointcut-ref="logPoint" ></aop:around>
                  </aop:aspect>
          
              </aop:config>
          
          </beans>
          
        6. pom.xml文件

          <?xml version="1.0" encoding="UTF-8"?>
          <project xmlns="http://maven.apache.org/POM/4.0.0"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
              <modelVersion>4.0.0</modelVersion>
          
              <groupId>Spring.Framerwork.Study</groupId>
              <artifactId>Spring_Framerwork</artifactId>
              <packaging>pom</packaging>
              <version>1.0-SNAPSHOT</version>
              <modules>
                  <module>study_AOP</module>
              </modules>
          
              <dependencies>
                  <dependency>
                      <groupId>org.springframework</groupId>
                      <artifactId>spring-webmvc</artifactId>
                      <version>5.2.7.RELEASE</version>
                  </dependency>
                  <!-- https://mvnrepository.com/artifact/junit/junit -->
                  <dependency>
                      <groupId>junit</groupId>
                      <artifactId>junit</artifactId>
                      <version>4.13</version>
                      <scope>test</scope>
                  </dependency>
                  <!-- 为了使用注解@Aspect -->
                  <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
                  <dependency>
                      <groupId>org.aspectj</groupId>
                      <artifactId>aspectjrt</artifactId>
                      <version>1.9.6</version>
                  </dependency>
                  <!-- 为了使用注解@Aspect -->
                  <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
                  <dependency>
                      <groupId>org.aspectj</groupId>
                      <artifactId>aspectjweaver</artifactId>
                      <version>1.9.5</version>
                  </dependency>
          
              </dependencies>
          
          </project>
          
        7. 测试:

          @Test
          public void test03 (){
              ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
              UserDao userDao = (UserDao) applicationContext.getBean("userDaoImpl");
              userDao.getUser();
          
          }
          
      2. 方式二:使用spring框架中的通知接口

        1. pom.xml文件导入包: aspectjweaver.jar

              <dependency>
                  <groupId>org.aspectj</groupId>
                  <artifactId>aspectjweaver</artifactId>
                  <version>1.9.5</version>
              </dependency>
          
        2. 创建切面:实现不同的Advice接口:(作者从哪获取的信息来通过实现这些接口来做操作?

        3. 接口类类型
          AfterReturningAdvice返回通知方法
          MethodBeforeAdvice前置通知方法
          MethodInterceptor环绕通知方法
        4. 创建通知advice,每一种通知分别实现不同的通知advice接口

           package com.tf.aop.log;
           
           
           import org.springframework.aop.AfterReturningAdvice;
           
           import java.lang.reflect.Method;
           
           public class AfterReturningLog implements AfterReturningAdvice {
           
               public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
                   System.out.println("调用了:"+method+",方法执行成功后调用该通知");
               }
           }
          
           package com.tf.aop.log;
           
           import org.springframework.aop.MethodBeforeAdvice;
           
           import java.lang.reflect.Method;
           
           public class MotherBeforeLog implements MethodBeforeAdvice {
               public void before(Method method, Object[] objects, Object o) throws Throwable {
                   System.out.println(objects);
                   System.out.println("调用了方法:"+method+",在调用方法前,执行该通知");
               }
           }
          
          package com.tf.aop.log;
          
          import org.aopalliance.intercept.MethodInterceptor;
          import org.aopalliance.intercept.MethodInvocation;
          // 环绕通知
          public class AroundAdviceLog implements MethodInterceptor {
              public Object invoke(MethodInvocation methodInvocation) throws Throwable {
                  Object result = null ;
                  System.out.println("环绕通知的前置通知方法:");
                  System.out.println("getMethod:"+methodInvocation.getMethod());
                  System.out.println("getArguments:"+methodInvocation.getArguments());
                  try{
                      result = methodInvocation.proceed();// 执行目标方法
                      System.out.println("环绕通知的返回通知方法:");
                  }catch (Exception e){
                      System.out.println(e.getStackTrace());
                      System.out.println("环绕通知的异常通知方法:");
                  }
                  System.out.println("环绕通知的异常通知方法:");
                  return result;
              }
          }
          
          
        5. 配置xml

          <?xml version="1.0" encoding="UTF-8"?>
           <beans xmlns="http://www.springframework.org/schema/beans"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns:context="http://www.springframework.org/schema/context"
              xmlns:aop="http://www.springframework.org/schema/aop"
              xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
                       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
           
           <!-- 配置自动扫描包 -->
           <context:component-scan base-package="com.tf.aop"></context:component-scan>
           
           <!-- 注册bean到ioc -->
           <!--<bean id="userService" class="com.tf.aop.service.UserServiceImpl"></bean>-->
           <!--<bean id="userDaoImpl" class="com.tf.aop.dao.UserDaoImpl"></bean>-->
           <bean id="xmlAopDemoUserLog" class="com.tf.aop.log.XmlAopDemoUserLog"></bean>
           <bean id="afterReturningLog" class="com.tf.aop.log.AfterReturningLog"></bean>
           <bean id="methorBeforeLog" class="com.tf.aop.log.MethorBeforeLog"></bean>
           <bean id="aroundAdviceLog" class="com.tf.aop.log.AroundAdviceLog"></bean>
          
           <!--aop配置标签中配置切面和切点-->
           <aop:config >
               <aop:pointcut id="logPoint" expression="execution( * com.tf.aop.dao.UserDaoImpl.getUser(..))"></aop:pointcut>
                  <aop:pointcut id="logPointB" expression="execution( * com.tf.aop.dao.UserDaoImpl.addUser(..))"></aop:pointcut>
          
               <!-- 使用顾问连接切面和切点,或者说使用顾问连接通知和切点 -->
               <aop:advisor advice-ref="methorBeforeLog"  pointcut-ref="logPoint"></aop:advisor>
               <aop:advisor advice-ref="afterReturningLog"  pointcut-ref="logPoint"></aop:advisor>
               <aop:advisor advice-ref="aroundAdviceLog"  pointcut-ref="logPointB"></aop:advisor>
          
           </aop:config>
          </beans>
          
          1. Error:发现了以元素 ‘{“http://www.springframework.org/schema/aop”:pointcut}’ 开头的无效内容。应以 ‘{“http://www.springframework.org/schema/aop”:aspect}’ 之一开头。

            原因:标签的顺序错了!<aop:pointcut 和 </aop:config 是有先后顺序的!

          2. Caused by: java.lang.IllegalArgumentException: warning no match for this type name: com.tf.aop.dao [Xlint:invalidAbsoluteTypeName]

            原因:没有指定到具体的类型:

            <aop:pointcut id="logPoint" expression="execution( * com.tf.aop.dao(..))"></aop:pointcut>
            
             <aop:pointcut id="logPoint" expression="execution( * com.tf.aop.dao.UserDaoImpl.getUser(..))"></aop:pointcut>
            
        6. 测试

          @Test
          public void test02 (){
              ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
              // getBean方法的二个参数无论是接口还是实现类都可以
              //Caused by: java.lang.IllegalArgumentException:
              // warning no match for this type name: com.tf.aop.dao [Xlint:invalidAbsoluteTypeName]
              // 因为是动态代理:getBean方法中不要指定class类型,返回值是接口。
              UserDao userDao = (UserDao) applicationContext.getBean("userDaoImpl");
              userDao.getUser();
          
          }
          
    3. 方式三:使用注解实现AOP:

      1. pom.xml引入两个jar包:aspectjrt.jar和aspectjweaver.jar
              <!-- 为了使用注解@Aspect -->
              <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
              <dependency>
                  <groupId>org.aspectj</groupId>
                  <artifactId>aspectjrt</artifactId>
                  <version>1.9.6</version>
              </dependency>
              <!-- 为了使用注解@Aspect -->
              <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
              <dependency>
                  <groupId>org.aspectj</groupId>
                  <artifactId>aspectjweaver</artifactId>
                  <version>1.9.5</version>
              </dependency>
      
        2. 创建切面类
      
      package com.tf.aop.log;
      
      import org.aspectj.lang.JoinPoint;
      import org.aspectj.lang.ProceedingJoinPoint;
      import org.aspectj.lang.annotation.*;
      import org.springframework.stereotype.Component;
      
      @Aspect // 标注为切面类
      @Component // 注册到ioc容器
      public class AopAnnoLog {
      
          // 前置通知方法:目标方法调用前,会被执行的通知方法
          @Before("execution(* com.tf.aop.dao.UserDaoImpl.*(..))")
          public void before(JoinPoint point){
              /**
               * 接口:JoinPoint point 解决了通知方法对目标方法的参数做操作问题
               *
               */
      
      //        System.out.println(point.getArgs());//[Ljava.lang.Object;@2beee7ff 参数数组,也是只有参数是有用信息
      //        System.out.println(point.getKind());//method-execution
      //        System.out.println(point.getSignature());//String com.tf.aop.dao.UserDao.getUser() 被通知前置的方法全路径名
      //        System.out.println(point.getSourceLocation());//org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint$SourceLocationImpl@29647f75
      //        System.out.println(point.getStaticPart());//execution(String com.tf.aop.dao.UserDao.getUser())
      //        System.out.println(point.getTarget());//com.tf.aop.dao.UserDaoImpl@3315d2d7
      //        System.out.println(point.getThis());//com.tf.aop.dao.UserDaoImpl@3315d2d7
      //        System.out.println(point.toLongString());//execution(public abstract java.lang.String com.tf.aop.dao.UserDao.getUser())
      //        System.out.println(point.toShortString());//execution(UserDao.getUser())
      //        System.out.println(point.toString());//execution(String com.tf.aop.dao.UserDao.getUser())
      //        System.out.println("==============================");
              Object[] args = point.getArgs();
              for (Object obj : args){
                  System.out.println("参数:"+obj);
              }
              System.out.println("目标方法的前置通知方法");
          }
      
          // 后置通知方法,即使目标通知方法抛异常了,该通知方法仍然会被执行
          @After("execution(* com.tf.aop.dao.UserDaoImpl.*(..))")
          public void after (){
              System.out.println("目标方法的后置通知方法");
          }
      
          /**
           *  后置方法如何解决对目标方法的结果值做操作的问题?
           *  注解@AfterReturning 使用了 属性:returning="param_name"
           */
      
          // 返回通知方法: 目标方法调用成功后的通知方法
          @AfterReturning(value="execution(* com.tf.aop.dao.UserDaoImpl.*(..))",returning = "result")
          public void afterReturning(JoinPoint joinPoint,Object result){
              System.out.println("调用目标方法名称:"+joinPoint.getSignature().getName());
              System.out.println("返回值对象为:"+result);
              System.out.println("目标方法调用成功后的通知方法");
          }
      
          // 异常通知方法:目标方法调用抛异常后调用的通知方法
          @AfterThrowing(value="execution(* com.tf.aop.dao.UserDaoImpl.*(..))",throwing = "ex")
          public void afterThrowingLog(Exception ex){
              System.out.println("抛出的异常是:"+ex);
              System.out.println("getMessage:"+ex.getMessage());
              System.out.println("目标方法调用抛异常后调用的通知方法");
          }
      
          /**
           * 环绕通知需要携带:ProceedingJoinPoint 类型的参数
           * 环绕通知需要有返回值,返回值即是目标方法的返回值
           * @param joinPoint
           */
          // 环绕通知方法:本质上是前面四个通知方法的综合体
          @Around(value = "execution(* com.tf.aop.dao.UserDaoImpl.*(..))" )
          public Object  aroundLog(ProceedingJoinPoint joinPoint ){
              System.out.println("环绕通知方法:前置通知方法");
              Object result = null ;
              try {
                  result = joinPoint.proceed();// 执行目标方法
                  System.out.println("环绕通知方法:返回通知方法");
      
              } catch (Throwable throwable) {
                  System.out.println("环绕通知方法:异常通知方法");
                  throwable.printStackTrace();
              }
              System.out.println("环绕通知方法:后置通知方法");
              return result ;
          }
          
          
      
      }
      
      
        3. application-context.xml文件配置:
      
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:context="http://www.springframework.org/schema/context"
             xmlns:aop="http://www.springframework.org/schema/aop"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
                      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
      
          <!-- 配置自动扫描包 -->
          <context:component-scan base-package="com.tf.aop"></context:component-scan>
          <!-- 自动为切面方法中匹配的方法所在的类生成代理对象。 -->
          <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
      
      </beans>
      
        4. 测试:
      
          @Test
          public void test04 (){
              ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
              UserDao userDao = (UserDao) applicationContext.getBean("userDaoImpl");
              System.out.println(userDao.getUser());
              System.out.println("=============================");
              userDao.addUser("提莫");
          }
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值