当前位置:   article > 正文

Spring学习笔记----AOP编程_spring学习笔记----aop编程实现计算器日志,验证

spring学习笔记----aop编程实现计算器日志,验证

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

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

Calculator.java

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

  1. package cn.limbo.spring.aop.calculator;
  2. /**
  3. * Created by Limbo on 16/7/14.
  4. */
  5. public class CalculatorImpl implements Calculator {
  6. @Override
  7. public int add(int i, int j) {
  8. System.out.println("The method add begin with [ "+ i +"," + j+" ]");
  9. System.out.println("The method add end with [ "+ i +"," + j+"]");
  10. return i + j;
  11. }
  12. @Override
  13. public int sub(int i, int j) {
  14. System.out.println("The method sub begin with [ "+ i +"," + j+" ]");
  15. System.out.println("The method sub end with [ "+ i +"," + j+" ]");
  16. return i - j;
  17. }
  18. @Override
  19. public int mul(int i, int j) {
  20. System.out.println("The method mul begin with [ "+ i +"," + j+" ]");
  21. System.out.println("The method mul end with [ "+ i +"," + j+" ]");
  22. return i * j;
  23. }
  24. @Override
  25. public int div(int i, int j) {
  26. System.out.println("The method div begin with [ "+ i +"," + j+" ]");
  27. System.out.println("The method div end with [ "+ i +"," + j+" ]");
  28. return i / j;
  29. }
  30. }
这样就完成了需求,但是我们发现,倘若是要修改日志的信息,那么就需要在具体方法里面改,这样做很麻烦,而且把原本清爽的方法改的十分混乱,方法应该表现的是核心功能,而不是这些无关紧要的关注点,下面用原生的java的方式实现在执行方法时,动态添加输出日志方法

CalculatorImpl.java

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

CalculatorLoggingProxy.java

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

Main.java

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

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

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

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

这两个额外的包

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

Calculator.java

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

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

LoggingAspect.java

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


applicationContext.xml

  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. <!--使Aspect注解起作用,自动为匹配的类生成代理对象-->
  10. <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  11. </beans>

用xml来配置aop

application-config.xml

  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:aop="http://www.springframework.org/schema/aop"
  5. xmlns:tx="http://www.springframework.org/schema/tx"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
  7. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
  8. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
  9. <bean id="userManager" class="com.tgb.aop.UserManagerImpl"/>
  10. <!--<bean id="aspcejHandler" class="com.tgb.aop.AspceJAdvice"/>-->
  11. <bean id="xmlHandler" class="com.tgb.aop.XMLAdvice" />
  12. <aop:config>
  13. <aop:aspect id="aspect" ref="xmlHandler">
  14. <aop:pointcut id="pointUserMgr" expression="execution(* com.tgb.aop.*.find*(..))"/>
  15. <aop:before method="doBefore" pointcut-ref="pointUserMgr"/>
  16. <aop:after method="doAfter" pointcut-ref="pointUserMgr"/>
  17. <aop:around method="doAround" pointcut-ref="pointUserMgr"/>
  18. <aop:after-returning method="doReturn" pointcut-ref="pointUserMgr"/>
  19. <aop:after-throwing method="doThrowing" throwing="ex" pointcut-ref="pointUserMgr"/>
  20. </aop:aspect>
  21. </aop:config>
  22. </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如下(只是更改自动扫描包的那个配置):

  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扫描包配置如下:

  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的情况。看来扫描包的时候不能暴力直接全部扫描进来啊,真是成也扫描,败也扫描,手动哭笑不得。





声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/792887
推荐阅读
相关标签
  

闽ICP备14008679号