zoukankan      html  css  js  c++  java
  • Spring(三)--AOP【面向切面编程】、通知类型及使用、切入点表达式

    1.概念:Aspect Oriented Programming 面向切面编程
    在方法的前后添加方法
     
    2.作用:本质上来说是一种简化代码的方式
         继承机制
         封装方法
         动态代理
         ……
        
    3.情景举例
      ①数学计算器接口[MathCalculator]
              int add(int i,int j);
              int sub(int i,int j);
              int mul(int i, int j);
              int div(int i,int j);
     
    因为后面的通知方法需要返回值,所以在这里类型声明为 int 类型
    public interface MathCaculator {
          public int add(int i,int j);
          public int sub(int i,int j);
          public int mul(int i,int j);
          public int div(int i,int j);
    }
    
     
       ②提供简单实现[EasyImpl]
          一定要在类上写注解,否则在 IOC容器中找不到
     
    @Component
    public class CacultorEasyImpl implements MathCaculator{
          @Override
          public void add(int i, int j) {
                System.out.println("[日志],【参数:】"+i+","+j);
                int result = i + j;
                System.out.println("[日志],【参数:】"+i+","+j+"--"+result);
          }
    
          @Override
          public void sub(int i, int j) {
                System.out.println("[日志],【参数:】"+i+","+j);
                int result = i - j;
                System.out.println("[日志],【参数:】"+i+","+j+"--"+result);
          }
    
          @Override
          public void mul(int i, int j) {
                System.out.println("[日志],【参数:】"+i+","+j);
                int result = i * j;
                System.out.println("[日志],【参数:】"+i+","+j+"--"+result);
          }
    
          @Override
          public void div(int i, int j) {
                System.out.println("[日志],【参数:】"+i+","+j);
                int result = i / j;
                System.out.println("[日志],【参数:】"+i+","+j+"--"+result);
          }
    }
    
    <context:component-scan base-package="com.neuedu.aop"></context:component-scan>
    

      ③在简单实现的基础上让每一个计算方法都能够打印日志[LoginImpl]

    private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    @Test
    public void test() {
          CacultorEasyImpl bean = ioc.getBean(CacultorEasyImpl.class);
          bean.add(10, 2);
          bean.sub(10, 2);
          bean.mul(10, 2);
          bean.div(10, 2);
    }
    

      ④缺陷
              [1]手动添加日志繁琐,重复
              [2]统一修改不便
              [3]对目标方法本来要实现的核心功能有干扰,使程序代码很臃肿,不易于开发维护
             
          ⑤使用动态代理实现
              [1]创建一个类,让这个类能够提供一个目标对象的代理对象
              [2]在代理对象中打印日志

     
    4.AOP术语![参见图例和doc文档]
          AOP概述
              ●AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论
                是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充。
              ●参见图例和doc文档解释AOP的各个术语!
              ●Spring的AOP既可以使用xml配置的方式实现,也可以使用注解的方式来实现!
     
     

    5.在Spring中使用AOP实现日志功能
      ①Spring中可以使用注解或XML文件配置的方式实现AOP。
      ②导入jar包
                com.springsource.net.sf.cglib -2.2.0.jar
                com.springsource.org.aopalliance-1.0.0 .jar
                com.springsource.org.aspectj.weaver-1.6.8 .RELEASE.jar
                commons-logging-1.1.3. jar
                spring-aop-4.0.0.RELEASE.jar
                spring-aspects-4.0.0.RELEASE.jar
                spring-beans-4.0.0.RELEASE.jar
                spring-context-4.0.0.RELEASE.jar
                spring-core-4.0.0.RELEASE.jar
                spring-expression-4.0.0.RELEASE. jar
               
      ③开启基于注解的AOP功能 < aop:aspectj-autoproxy />

    <context:component-scan base-package="com.neuedu.aop"></context:component-scan>
    <aop:aspectj-autoproxy/>
    

      ④声明一个切面类,并把这个切面类加入到 IOC容器中

       在类上加以下两个注解
              @Aspect  :表示这是一个切面类
              @Component  :加入IOC容器
             
      ⑤在切面类中声明通知方法
              [1] 前置通知:@Before
              [2] 返回通知:@AfterReturning
              [3] 异常通知:@AfterThrowing
              [4] 后置通知:@After
              [5] 环绕通知:@Around :环绕通知是前面四个通知的集合体!

     
    @Component
    @Aspect
    public class CaculatorAspect {
    
          @Before(value="execution(public void com.neuedu.aop.RawCaculatorImpl.add(int, int))")
          public void showBeginLog(){
                System.out.println("日志开始");
          }
    
          @After(value="execution(public void com.neuedu.aop.RawCaculatorImpl.add(int, int))")
          public void showReturnLog(){
                System.out.println("日志正常返回");
          }
    
          @AfterThrowing(value="execution(public void com.neuedu.aop.RawCaculatorImpl.add(int, int))")
          public void showExceptionLog(){
                System.out.println("日志有错");
          }
    
          @AfterReturning(value="execution(public void com.neuedu.aop.RawCaculatorImpl.add(int, int))")
          public void showAfterLog(){
                System.out.println("日志最终结束");
          }
    }
    

      ⑥被代理的对象也需要加入IOC容器

    @Component
    public class RawCaculatorImpl implements MathCaculator{
    
          @Override
          public void add(int i, int j) {
                int result = i + j;
                System.out.println(i+"+"+j+"="+result);
          }
    
          @Override
          public void sub(int i, int j) {
                int result = i - j;
                System.out.println(i+"-"+j+"="+result);
          }
    
          @Override
          public void mul(int i, int j) {
                int result = i * j;
                System.out.println(i+"*"+j+"="+result);
          }
    
          @Override
          public void div(int i, int j) {
                int result = i / j;
                System.out.println(i+"/"+j+"="+result);
          }
    }
    

      Test 中 用 id 查找,通过强转,调用加减乘除四个方法

    private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    @Test
    public void test() {
          MathCaculator bean = (MathCaculator) ioc.getBean("rawCaculatorImpl");
          bean.add(10, 2);
          bean.sub(10, 2);
          bean.mul(10, 2);
          bean.div(10, 2);
    }
    
    6.切入点表达式:
    (1)上述案例通过junit测试,会发现,我们调用目标类的四个方法只有add方法被加入了4个通知
            如果想所有的方法都加上这些通知,可以在切入点表达式处:
            将execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int)) 换成: execution(public int com.neuedu.aop.target.MathCalculatorImpl.*(int, int))
            这样只要是有两个参数,且参数类型为int的方法在执行的时候都会执行其相应的通知方法!
      
    @Component
    @Aspect
    public class CaculatorAspect {
    
          @Before(value="execution(public void com.neuedu.aop.RawCaculatorImpl.*(int, int))")
          public void showBeginLog(){
                System.out.println("日志开始");
          }
    
          @After(value="execution(public void com.neuedu.aop.RawCaculatorImpl.*(int, int))")
          public void showReturnLog(){
                System.out.println("日志正常返回");
          }
    
          @AfterThrowing(value="execution(public void com.neuedu.aop.RawCaculatorImpl.*(int, int))")
          public void showExceptionLog(){
                System.out.println("日志有错");
          }
    
          @AfterReturning(value="execution(public void com.neuedu.aop.RawCaculatorImpl.*(int, int))")
          public void showAfterLog(){
                System.out.println("日志最终结束");
          }
    
    }
    

      如果方法中的参数类型不一致,可以用 (..) 代替 (int,int)

    @Component
    @Aspect
    public class CaculatorAspect {
    
         @Pointcut(value="execution(* com.neuedu.aop.RawCaculatorImpl.*(..))")
         public void showLog(){}
    
          @Before(value="showLog()")
          public void showBeginLog(JoinPoint point){
                Object[] args = point.getArgs();//获取参数
                List<Object> asList = Arrays.asList(args);//转为list类型
                Signature signature = point.getSignature();//获取签名
                String name = signature.getName();//获取方法名字
                System.out.println("【前置通知】目标方法名:"+name+",参数为:"+asList);
          }
    
          @After(value="showLog()")
          public void showReturnLog(){
                System.out.println("【后置通知】日志最终返回");
          }
    
          @AfterThrowing(value="showLog()",throwing="ex")
          public void showExceptionLog(JoinPoint point,Exception ex){
                System.out.println("【异常通知】异常信息为:"+ex.getMessage());
          }
    
          @AfterReturning(value="showLog()",returning="result")
          public void showAfterLog(JoinPoint point,Object result){
                System.out.println("【返回通知】方法的返回值:"+result);
                System.out.println();
          }
    }
    
    (2)①切入点表达式的语法格式[参见第5章AOP细节]
              execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))
        
           参见第5章AOP细节:演示验证
           1.任意参数,任意类型
           2.任意返回值
           3.用@PointCut注解统一声明,然后在其它通知中引用该统一声明即可!
              需要注意的是:权限是不支持写通配符的,当然你可以写一个*表示所有权限所有返回值!


         最详细的切入点表达式:
              execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))
         最模糊的切入点表达式:
              execution (* *.*(..))
              第一个 * 代表   权限和返回类型
         但是不可以 无权限却有返回类型   例如: execution(* int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))
     
    7.统一声明切入点表达式
         @Pointcut(value= "execution(public int com.atguigu.aop.target.EazyImpl.add(int,int))")
         public void myPointCut(){}
        
        
    8.通知方法的细节
       ①在通知中获取目标方法的方法名和参数列表
              [1]在通知方法中声明一个JoinPoint类型的形参
                   JointPoint 是切入点,
              [2]调用JoinPoint对象的getSignature()方法获取目标方法的签名
              [3]调用JoinPoint对象的getArgs()方法获取目标方法的实际参数列表
     
     
    @Before(value="showLog()")
    public void showBeginLog(JoinPoint point){
          Object[] args = point.getArgs();//获取参数
          List<Object> asList = Arrays.asList(args);//转为list类型
          Signature signature = point.getSignature();//获取签名
          String name = signature.getName();//获取方法名字
          System.out.println("目标方法名:"+name+",参数为:"+asList);
          System.out.println("日志开始");
    }
    
      
        ②在返回通知中获取方法的返回值
              [1]在 @AfterReturning   注解中添加returning属性
              [2]在返回通知的通知方法中声明一个形参,形参名和returning属性的值一致     
                  不知道返回什么类型,所以用 Object 类型
                  
    @AfterReturning(value="showLog()",returning="result")
    public void showAfterLog(JoinPoint point,Object result){
          System.out.println("方法的返回值:"+result);
          System.out.println("日志正常结束");
          System.out.println();
    }
    
      
       ③在异常通知中获取异常对象
              [1]在 @AfterThrowing 注解中添加 throwing 属性
              [2]在异常通知的通知方法中声明一个形参,形参名和throwing属性值一致
    @AfterThrowing(value="showLog()",throwing="ex")
    public void showExceptionLog(JoinPoint point,Exception ex){
          System.out.println("异常信息为:"+ex.getMessage());
          System.out.println("日志有错");
    }
    
     
    9.根据接口类型获取target对象时,实际上真正放在IOC容器中的对象是代理对象,而并不是目标对象本身!


    10.环绕通知:@Around
        1.环绕通知需要在方法的参数中指定JoinPoint的子接口类型ProceedingJoinPoint为参数
              @Around(value="pointCut()")
              public void around(ProceedingJoinPoint joinPoint){
              }
         2.环绕通知会将其他4个通知能干的,自己都给干了!
              注意:@Around修饰的方法一定要将方法的返回值返回!本身相当于代理!
                      但是不知道返回值的类型 ,所以用 Object 
     
    @Around(value="execution(public * com.neuedu.aop.RawCaculatorImpl.*(..))")
    public Object showLog(ProceedingJoinPoint point){
          Object[] args = point.getArgs();
          List<Object> asList = Arrays.asList(args);
          Signature signature = point.getSignature();//获取签名
          String name = signature.getName();
          Object result=null;
          try {     
                try{
                      System.out.println("【前置通知】目标方法名:"+name+",参数为:"+asList);
                      result = point.proceed(args);
                 }finally{
                      System.out.println("【后置通知】日志最终返回");
                 }
                 System.out.println("【返回通知】方法的返回值:"+result);
           } catch (Throwable e) {
                 System.out.println("【异常通知】异常信息为:"+e.getMessage());
           }
           System.out.println();
           return result;
    }
    
     
    11.切面的优先级
         对于同一个代理对象,可以同时有多个切面共同对它进行代理。
         可以在切面类上通过@Order (value=50)注解来进行设置,值越小优先级越高!
         没有 @Order 注解默认最大
     
    @Component
    @Aspect
    @Order(value=20)
    public class BAspect {
    
          @Around(value="execution(* com.neuedu.aop.RawCaculatorImpl.*(..))")
          public Object showLog(ProceedingJoinPoint point){
                Object[] args = point.getArgs();
                List<Object> asList = Arrays.asList(args);
                Signature signature = point.getSignature();//获取签名
                String name = signature.getName();
                Object result=null;
                try {
                      try{
                            System.out.println("【前置】目标方法名:"+name+",参数为:"+asList);
                            result = point.proceed(args);
                      }finally{
                            System.out.println("【后置】日志最终返回");
                      }
                      System.out.println("【返回】方法的返回值:"+result);
                } catch (Throwable e) {
                      System.out.println("【异常】异常信息为:"+e.getMessage());
                }
                System.out.println();
                return result;
          }
    }
    
    12.注意:上面的AOP都是通过注解实现的,AOP实际上也可以通过xml配置的方式实现!
         将注解全删掉
     
    <!-- 将需要加载到IOC容器中的bean配置好 -->
    <bean id="caculatorAspect" class="com.neuedu.aop.CaculatorAspect"></bean>
    <bean id="bAspect" class="com.neuedu.aop.BAspect"></bean>
    <bean id="rawCaculatorImpl" class="com.neuedu.aop.RawCaculatorImpl"></bean>
    
    <!-- 配置AOP,需要导入AOP名称空间 -->
    <aop:config>
          <!-- 声明切入点表达式 -->
          <aop:pointcut expression="execution(* com.neuedu.aop.RawCaculatorImpl.*(..))" id="myPointCut"/>
          <!-- 配置日志切面类,引用前面的类 ,通过order属性控制优先级-->
          <aop:aspect ref="caculatorAspect" order="20">
          <!-- 通过method属性指定切面类的切面方法,通过pointcut-ref指定切入点表达式 -->
                 <aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
                 <aop:after method="showAfterLog" pointcut-ref="myPointCut"/>
                 <aop:after-returning method="showReturnLog" pointcut-ref="myPointCut" returning="result"/>
                 <aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="ex"/>
          </aop:aspect>
          <!-- 配置事务切面类,引用前面的类 -->
          <aop:aspect ref="bAspect" order="25">
                <aop:around method="showLog" pointcut-ref="myPointCut"/>
          </aop:aspect>
    
    </aop:config>
    

    需要知道的是:事务的管理是和AOP是有很大关系的,即声明式事务的底层是用AOP实现的!

     
  • 相关阅读:
    R语言 ggplot2包
    C++实现景区信息管理系统
    linux系统目录介绍
    Python中的赋值、深拷贝与浅拷贝(内存地址)
    三大相关系数: pearson, spearman, kendall(python示例实现)
    Xshell删除键不好使:删除显示退格^H
    Spark SQL中出现 CROSS JOIN 问题解决
    Python apply函数
    Python Dataframe 分组排序和 Modin
    Python 中的时间处理包datetime和arrow
  • 原文地址:https://www.cnblogs.com/lwj-0923/p/7449800.html
Copyright © 2011-2022 走看看