• Spring--AOP 例子


    先用代码讲一下什么是传统的AOP(面向切面编程)编程

    需求:实现一个简单的计算器,在每一步的运算前添加日志。最传统的方式如下:

    Calculator.Java

    [java] view plain copy
     
     print?
    1. package cn.limbo.spring.aop.calculator;  
    2.   
    3. /** 
    4.  * Created by Limbo on 16/7/14. 
    5.  */  
    6. public interface Calculator {  
    7.     int add(int i , int j);  
    8.     int sub(int i , int j);  
    9.     int mul(int i , int j);  
    10.     int div(int i , int j);  
    11. }  

    CalculatorImpl.java

    [java] view plain copy
     
     print?
    1. package cn.limbo.spring.aop.calculator;  
    2.   
    3. /** 
    4.  * Created by Limbo on 16/7/14. 
    5.  */  
    6. public class CalculatorImpl implements Calculator {  
    7.     @Override  
    8.     public int add(int i, int j) {  
    9.         System.out.println("The method add begin with [ "+ i +"," + j+" ]");  
    10.         System.out.println("The method add end with [ "+ i +"," + j+"]");  
    11.         return i + j;  
    12.     }  
    13.   
    14.     @Override  
    15.     public int sub(int i, int j) {  
    16.         System.out.println("The method sub begin with [ "+ i +"," + j+" ]");  
    17.         System.out.println("The method sub end with [ "+ i +"," + j+" ]");  
    18.         return i - j;  
    19.     }  
    20.   
    21.     @Override  
    22.     public int mul(int i, int j) {  
    23.         System.out.println("The method mul begin with [ "+ i +"," + j+" ]");  
    24.         System.out.println("The method mul end with [ "+ i +"," + j+" ]");  
    25.         return i * j;  
    26.     }  
    27.   
    28.     @Override  
    29.     public int div(int i, int j) {  
    30.         System.out.println("The method div begin with [ "+ i +"," + j+" ]");  
    31.         System.out.println("The method div end with [ "+ i +"," + j+" ]");  
    32.         return i / j;  
    33.     }  
    34. }  

    这样就完成了需求,但是我们发现,倘若是要修改日志的信息,那么就需要在具体方法里面改,这样做很麻烦,而且把原本清爽的方法改的十分混乱,方法应该表现的是核心功能,而不是这些无关紧要的关注点,下面用原生的java的方式实现在执行方法时,动态添加输出日志方法

    CalculatorImpl.java

    [java] view plain copy
     
     print?
    1. package cn.limbo.spring.aop.calculator;  
    2.   
    3. /** 
    4.  * Created by Limbo on 16/7/14. 
    5.  */  
    6. public class CalculatorImpl implements Calculator {  
    7.     @Override  
    8.     public int add(int i, int j) {  
    9.         return i + j;  
    10.     }  
    11.   
    12.     @Override  
    13.     public int sub(int i, int j) {  
    14.         return i - j;  
    15.     }  
    16.   
    17.     @Override  
    18.     public int mul(int i, int j) {  
    19.         return i * j;  
    20.     }  
    21.   
    22.     @Override  
    23.     public int div(int i, int j) {  
    24.         return i / j;  
    25.     }  
    26. }  


    CalculatorLoggingProxy.java

    [java] view plain copy
     
     print?
    1. package cn.limbo.spring.aop.calculator;  
    2.   
    3.   
    4. import java.lang.reflect.InvocationHandler;  
    5. import java.lang.reflect.Method;  
    6. import java.lang.reflect.Proxy;  
    7. import java.util.Arrays;  
    8. import java.util.Objects;  
    9.   
    10. /** 
    11.  * Created by Limbo on 16/7/14. 
    12.  */  
    13. public class CalculatorLoggingProxy {  
    14.   
    15.     //要代理的对象  
    16.     private Calculator target;  
    17.   
    18.     public CalculatorLoggingProxy(Calculator target) {  
    19.         this.target = target;  
    20.     }  
    21.   
    22.     public Calculator getLoggingProxy(){  
    23.   
    24.         //代理对象由哪一个类加载器负责加载  
    25.         ClassLoader loader = target.getClass().getClassLoader();  
    26.         //代理对象的类型,即其中有哪些方法  
    27.         Class[] interfaces = new Class[]{Calculator.class};  
    28.         // 当调用代理对象其中的方法时,执行改代码  
    29.         InvocationHandler handler = new InvocationHandler() {  
    30.             @Override  
    31.             /** 
    32.              * proxy:正在返回的那个对象的代理,一般情况下,在invoke方法中不使用该对象 
    33.              * method:正在被调用的方法 
    34.              * args:调用方法时,传入的参数 
    35.              */  
    36.             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
    37.                 String methodName = method.getName();  
    38.                 System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));  
    39.                 //日志  
    40.                 Object result = method.invoke(target,args);  
    41.                 System.out.println("The method " + methodName + " ends with " + result);  
    42.   
    43.                 return result;  
    44.             }  
    45.         };  
    46.         Calculator proxy = (Calculator) Proxy.newProxyInstance(loader,interfaces,handler);  
    47.   
    48.         return proxy;  
    49.     }  
    50.   
    51. }  


    Main.java

    [java] view plain copy
     
     print?
    1. package cn.limbo.spring.aop.calculator;  
    2.   
    3. /** 
    4.  * Created by Limbo on 16/7/14. 
    5.  */  
    6. public class Main {  
    7.     public static void main(String[] args) {  
    8.         Calculator calculator = new CalculatorImpl();  
    9.         Calculator proxy = new CalculatorLoggingProxy(calculator).getLoggingProxy();  
    10.   
    11.         int result = proxy.add(1,2);  
    12.         System.out.println("--->" + result);  
    13.   
    14.         result = proxy.sub(1,2);  
    15.         System.out.println("--->" + result);  
    16.   
    17.         result = proxy.mul(3,2);  
    18.         System.out.println("--->" + result);  
    19.   
    20.         result = proxy.div(14,2);  
    21.         System.out.println("--->" + result);  
    22.     }  
    23. }  


    这样写虽然已经简化了代码,而且可以任意修改日志信息代码,但是写起来还是很麻烦!!!

    下面我们使用spring自带的aop包实现但是要加入

     com.springsource.org.aopalliance-1.0.0.jar
    com.springsource.org.aspectj.weaver-1.8.5.RELEASE.jar

    这两个额外的包

    下面看代码,要点全部卸载代码里面了

    Calculator.java

    [java] view plain copy
     
     print?
    1. package cn.limbo.spring.aop.impl;  
    2.   
    3. /** 
    4.  * Created by Limbo on 16/7/14. 
    5.  */  
    6. public interface Calculator {  
    7.     int add(int i, int j);  
    8.     int sub(int i, int j);  
    9.     int mul(int i, int j);  
    10.     int div(int i, int j);  
    11. }  

    CalculatorImpl.java

    [java] view plain copy
     
     print?
    1. package cn.limbo.spring.aop.impl;  
    2.   
    3. import org.springframework.stereotype.Component;  
    4.   
    5. /** 
    6.  * Created by Limbo on 16/7/14. 
    7.  */  
    8. @Component("calculatorImpl")  
    9. public class CalculatorImpl implements Calculator {  
    10.     @Override  
    11.     public int add(int i, int j) {  
    12.         return i + j;  
    13.     }  
    14.   
    15.     @Override  
    16.     public int sub(int i, int j) {  
    17.         return i - j;  
    18.     }  
    19.   
    20.     @Override  
    21.     public int mul(int i, int j) {  
    22.         return i * j;  
    23.     }  
    24.   
    25.     @Override  
    26.     public int div(int i, int j) {  
    27.         return i / j;  
    28.     }  
    29. }  

    LoggingAspect.java

    [java] view plain copy
     
     print?
    1. package cn.limbo.spring.aop.impl;  
    2.   
    3. import org.aspectj.lang.JoinPoint;  
    4. import org.aspectj.lang.ProceedingJoinPoint;  
    5. import org.aspectj.lang.annotation.*;  
    6. import org.springframework.core.annotation.Order;  
    7. import org.springframework.stereotype.Component;  
    8.   
    9. import java.util.Arrays;  
    10. import java.util.List;  
    11. import java.util.Objects;  
    12.   
    13. /** 
    14.  * Created by Limbo on 16/7/14. 
    15.  */  
    16. //把这个类声明为切面:需要该类放入IOC容器中,再声明为一个切面  
    17. @Order(0)//指定切面优先级,只越小优先级越高  
    18. @Aspect  
    19. @Component  
    20. public class LoggingAspect {  
    21.   
    22.     /** 
    23.      * 定义一个方法,用于声明切入点表达式,一般的,该方法中再不需要添加其他代码 
    24.      * 主要是为了重用路径,使用@Pointcut来声明切入点表达式 
    25.      * 后面的其他通知直接使用方法名来引用当前的切入点表达式 
    26.      */  
    27.     @Pointcut("execution(* cn.limbo.spring.aop.impl.Calculator.*(..))")  
    28.     public void declareJointPointExpression()  
    29.     {  
    30.   
    31.     }  
    32.   
    33.   
    34.     //声明该方法是一个前置通知:在目标方法之前执行  
    35.     @Before("declareJointPointExpression()")  
    36. //  @Before("execution(* cn.limbo.spring.aop.impl.*.*(..))")  该包下任意返回值,任意类,任意方法,任意参数类型  
    37.     public void beforeMethod(JoinPoint joinPoint)  
    38.     {  
    39.         String methodName = joinPoint.getSignature().getName();  
    40.         List<Object> args = Arrays.asList(joinPoint.getArgs());  
    41.         System.out.println("The Method "+ methodName+" Begins With " + args);  
    42.     }  
    43.     //在目标方法执行之后执行,无论这个方法是否出错  
    44.     //在后置通知中还不能访问目标方法的返回值,只能通过返回通知访问  
    45.     @After("execution(* cn.limbo.spring.aop.impl.Calculator.*(int,int))")  
    46.     public void afterMethod(JoinPoint joinPoint)  
    47.     {  
    48.         String methodName = joinPoint.getSignature().getName();  
    49.         System.out.println("The Method "+ methodName+" Ends ");  
    50.     }  
    51.   
    52.     /** 
    53.      * 在方法正常结束后执行的代码 
    54.      * 返回通知是可以访问到方法的返回值 
    55.      */  
    56.     @AfterReturning(value = "execution(* cn.limbo.spring.aop.impl.Calculator.*(..))",returning = "result")  
    57.     public void afterReturning(JoinPoint joinPoint , Object result)  
    58.     {  
    59.         String methodName = joinPoint.getSignature().getName();  
    60.         System.out.println("The Method " + methodName + " Ends With " + result);  
    61.     }  
    62.   
    63.   
    64.     /** 
    65.      *在目标方法出现异常的时候执行代码 
    66.      * 可以访问异常对象,且可以指定出现特定异常时再执行通知 
    67.      */  
    68.     @AfterThrowing(value = "execution(* cn.limbo.spring.aop.impl.Calculator.*(..))",throwing = "ex")  
    69.     public void afterThrowing(JoinPoint joinPoint,Exception ex)//Exception 可以改成 NullPointerException等特定异常  
    70.     {  
    71.         String methodName = joinPoint.getSignature().getName();  
    72.         System.out.println("The Method " + methodName + " Occurs With " + ex);  
    73.     }  
    74.   
    75.     /** 
    76.      * 环绕通知需要ProceedingJoinPoint 类型参数 功能最强大 
    77.      * 环绕通知类似于动态代理的全过程:ProceedingJoinPoint 类型的参数可以决定是否执行目标方法 
    78.      * 且环绕通知必须有返回值,返回值即为目标方法的返回值 
    79.      */  
    80.     @Around("execution(* cn.limbo.spring.aop.impl.Calculator.*(..))")  
    81.     public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint)  
    82.     {  
    83.         Object result =null;  
    84.         String methodName = proceedingJoinPoint.getSignature().getName();  
    85.         try {  
    86.             //前置通知  
    87.             System.out.println("The Method " + methodName + " Begins With " + Arrays.asList(proceedingJoinPoint.getArgs()));  
    88.             result = proceedingJoinPoint.proceed();  
    89.             // 返回通知  
    90.             System.out.println("The Method " + methodName + " Ends With " + result);  
    91.         } catch (Throwable throwable) {  
    92.             //异常通知  
    93.             throwable.printStackTrace();  
    94.         }  
    95.         System.out.println("The Method " + methodName + " Ends ");  
    96.         return result;  
    97.     }  
    98. }  



    applicationContext.xml

    [html] view plain copy
     
     print?
    1. <?xml version="1.0" encoding="UTF-8"?>  
    2. <beans xmlns="http://www.springframework.org/schema/beans"  
    3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    4.        xmlns:context="http://www.springframework.org/schema/context"  
    5.        xmlns:aop="http://www.springframework.org/schema/aop"  
    6.        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">  
    7.     <!--配置自动扫描的包-->  
    8.     <context:component-scan base-package="cn.limbo.spring.aop.impl"></context:component-scan>  
    9.   
    10.     <!--使Aspect注解起作用,自动为匹配的类生成代理对象-->  
    11.     <aop:aspectj-autoproxy></aop:aspectj-autoproxy>  
    12.   
    13.   
    14. </beans>  

    用xml来配置aop

    application-config.xml

    [html] view plain copy
     
     print?
    1. <?xml version="1.0" encoding="UTF-8"?>  
    2.   
    3. <beans xmlns="http://www.springframework.org/schema/beans"  
    4.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    5.          xmlns:aop="http://www.springframework.org/schema/aop"  
    6.          xmlns:tx="http://www.springframework.org/schema/tx"  
    7.          xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd  
    8.            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd  
    9.            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">  
    10.                
    11.       
    12.     <bean id="userManager" class="com.tgb.aop.UserManagerImpl"/>  
    13.       
    14.     <!--<bean id="aspcejHandler" class="com.tgb.aop.AspceJAdvice"/>-->  
    15.     <bean id="xmlHandler" class="com.tgb.aop.XMLAdvice" />  
    16.     <aop:config>  
    17.         <aop:aspect id="aspect" ref="xmlHandler">  
    18.             <aop:pointcut id="pointUserMgr" expression="execution(* com.tgb.aop.*.find*(..))"/>  
    19.               
    20.             <aop:before method="doBefore"  pointcut-ref="pointUserMgr"/>  
    21.             <aop:after method="doAfter"  pointcut-ref="pointUserMgr"/>  
    22.             <aop:around method="doAround"  pointcut-ref="pointUserMgr"/>  
    23.             <aop:after-returning method="doReturn"  pointcut-ref="pointUserMgr"/>  
    24.             <aop:after-throwing method="doThrowing" throwing="ex" pointcut-ref="pointUserMgr"/>  
    25.               
    26.         </aop:aspect>  
    27.     </aop:config>  
    28. </beans>  



    一共有5类的通知,其中around最为强大,但是不一定最常用,aspect的底层实现都是通过代理来实现的,只能说这个轮子造的不错

    2016.12.09更新

    最近在实际项目中配置aop发现了几个问题,实际的项目配置的的模式是ssh即Spring4+SpringMVC+Hibernate4的模式。

    基于注解的方式配置方式有些坑点:

    1.发现SpringMVC中aop不起任何作用

    经过排查和查找网上的资料,发现问题如下:

    Spring MVC启动时的配置文件,包含组件扫描、url映射以及设置freemarker参数,让spring不扫描带有@Service注解的类。为什么要这样设置?因为springmvc.xml与applicationContext.xml不是同时加载,如果不进行这样的设置,那么,spring就会将所有带@Service注解的类都扫描到容器中,等到加载applicationContext.xml的时候,会因为容器已经存在Service类,使得cglib将不对Service进行代理,直接导致的结果就是在applicationContext 中的事务配置不起作用,发生异常时,无法对数据进行回滚。以上就是原因所在。

    所以改进后的applicationContext.xml如下(只是更改自动扫描包的那个配置):

    [html] view plain copy
     
     print?
    1. <context:component-scan base-package="cn.limbo">  
    2.         <!--不要将Controller扫进来,否则aop无法使用-->  
    3.         <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />  
    4.     </context:component-scan>  


    spring-mvc.xml扫描包配置如下:

    [html] view plain copy
     
     print?
    1. <context:component-scan base-package="cn.limbo">  
    2.         <!--不要将Service扫进来,否则aop无法使用-->  
    3.         <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />  
    4.     </context:component-scan>  

    这样就可以成功解决ssh配置下不能使用aop的情况。看来扫描包的时候不能暴力直接全部扫描进来啊,真是成也扫描,败也扫描,手动哭笑不得。

  • 相关阅读:
    STL逆序迭代器(reverse_iterator)
    STL容器之vector容器API(二)
    STL容器之vector容器巧用swap收缩空间
    STL容器之vector容器API(一)
    STL容器vector概念和注意事项(每次扩充都会重新开辟空间,释放原空间,即首元素地址会变一次)
    STL容器之string内存重定义
    STL容器之string与c_style类型转换
    STL容器之string插入和删除
    STL容器之string字串
    STL容器之string比较
  • 原文地址:https://www.cnblogs.com/KingIceMou/p/7169561.html
Copyright © 2020-2023  润新知