最近用到了spring的aop,对aop(面向切面编程)记忆有了新的加深,鉴于方便别人也方便自己找资料,特地做了小demo来展示一下xml配置和注解实现的aop,展示了各种通知,并且在各种通知里面获得各种值,这里要提一下,用xml配置做前置通知获得参数的时候,出现过一个问题::0 formal unbound in pointcut ,这里很搞了一会儿,网上也找了各种资料,实在感叹,盲目转帖,各种不做自己判断真是坑爹,基本十有八九都是同样的答案,关键是烂答案,后来不断尝试,终于解决问题,下面会奉上解决方案
spring-aop-xml.xml配置
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:context="http://www.springframework.org/schema/context" 6 xmlns:aop="http://www.springframework.org/schema/aop" 7 xsi:schemaLocation="http://www.springframework.org/schema/beans 8 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 9 http://www.springframework.org/schema/context 10 http://www.springframework.org/schema/context/spring-context-2.5.xsd 11 http://www.springframework.org/schema/aop 12 http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> 13 14 15 16 <!-- 要被拦截的类定义 --> 17 <bean id="aopService" class="com.pis.aop.AopService"/> 18 <!-- 被拦截类aopService的拦截类 --> 19 <bean id="serviceInterceptor" class="com.pis.aop.ServiceInterceptor"/> 20 21 <aop:config> 22 <!-- 声明proxyService为一个切面 也就是拦截类--> 23 24 <aop:aspect id="asp" ref="serviceInterceptor"> 25 26 <!-- pointcut切入点 即对目标的哪些方法进行拦截 expression是拦截表达式 27 最常用的是 execution(* com.pis.aop.*.*(..)) 28 对所有方法进行拦截 第一个"*"号代表返回类型 第二个"*"代表com.pis.aop下面所有类 29 第三个"*"代表所有方法 "(..)代表所有参数"--> 30 31 <aop:pointcut id="target" expression="execution(String com.pis.aop.AopService.*(String)) "/> 32 33 <aop:pointcut id="target_1" expression="execution(* com.pis.aop.*.*(..))"/> 34 35 36 37 <!-- 设定前置通知 并且获得参数值 实践证明pointcut-ref注入target获得参数是会报:0 formal unbound in pointcut的,而直接写切入点表达式是可以的 38 在这里纠结半天,事实也证明arg-names="str"是没有用的 还是直接写切入点并指定参数最有效 这里的str要对应切面和被拦截类的方法的参数名 39 --> 40 <aop:before method="before" pointcut="execution(String com.pis.aop.AopService.*(String)) and args(str)" /> 41 42 43 44 <!-- 设定后置通知 --> 45 <aop:after-returning method="afterReturn" pointcut-ref="target" returning="result"/> 46 47 <!-- 设定环绕通知 --> 48 <aop:around method="around" pointcut-ref="target"/> 49 50 51 <!-- 设定最终通知 --> 52 <aop:after method="after" pointcut-ref="target"/> 53 54 55 <!-- 设定异常通知 --> 56 <aop:after-throwing method="afterThrowing" pointcut-ref="target_1" throwing="e"/> 57 58 59 </aop:aspect> 60 </aop:config> 61 62 </beans>
AopService.java,即要被拦截的类
1 package com.pis.aop; 2 3 import org.springframework.stereotype.Component; 4 5 6 @Component("aopService") 7 public class AopService { 8 9 10 11 //用来验证前置通知获取参数 12 public String execute(String str){ 13 System.out.println("终于轮到我execute方法执行了"); 14 try { 15 throw new Exception("可爱的异常"); 16 } catch (Exception e) { 17 // System.out.println(e.getMessage()); 18 } finally { 19 // System.out.println("finally执行"); 20 } 21 return "result"; 22 } 23 24 25 public void excp() throws Exception{ 26 throw new Exception("可爱的异常"); 27 } 28 29 }
ServiceInterceptor.java 切面类 也就是拦截类(官方名词:代理类)
1 package com.pis.aop; 2 3 import org.aspectj.lang.ProceedingJoinPoint; 4 import org.aspectj.lang.annotation.After; 5 import org.aspectj.lang.annotation.AfterReturning; 6 import org.aspectj.lang.annotation.AfterThrowing; 7 import org.aspectj.lang.annotation.Around; 8 import org.aspectj.lang.annotation.Aspect; 9 import org.aspectj.lang.annotation.Before; 10 import org.aspectj.lang.annotation.Pointcut; 11 import org.springframework.stereotype.Component; 12 13 14 //定义切面 也就是拦截类 15 @Component("serviceInterceptor") 16 @Aspect 17 public class ServiceInterceptor { 18 19 //定义一个切入点 拦截的是execute方法 20 @Pointcut("execution(String com.pis.aop.AopService.*(String))") 21 public void pointcut(){} 22 23 //定义前置通知 24 @Before("pointcut() && args(str)") 25 public void before(String str){ 26 System.out.println("***进入前置通知,获取参数:***"); 27 } 28 29 //这里的returning的值result必须和afterReturn的参数一致 pointcut引入写法较其他方法有所不同 30 @AfterReturning(pointcut="pointcut()", returning="result") 31 public void afterReturn(String result){ 32 System.out.println("***进入后置通知,获取返回结果:"+result+"***"); 33 } 34 35 //定义最终通知 36 @After("pointcut()") 37 public void after(){ 38 System.out.println("***进入最终通知***"); 39 } 40 41 //自定义了一个切入点,并获得异常信息 42 @AfterThrowing(pointcut="execution(* com.pis.aop.*.*(..))", throwing = "e") 43 public void afterThrowing(Exception e){ 44 System.out.println("***进入例外通知,获得异常信息:"+e+"***"); 45 } 46 47 //定义环绕通知 48 @Around("pointcut()") 49 public Object around(ProceedingJoinPoint pjp){ 50 Object obj=null; 51 System.out.println("***进入环绕通知***"); 52 try { 53 obj = pjp.proceed();//此处返回的是拦截的方法的返回值,如果不执行此方法,则不会执行被拦截的方法,利用环绕通知可以很好的做权限管理 54 } catch (Throwable e) { 55 e.printStackTrace(); 56 } 57 System.out.println("***退出环绕通知***"); 58 return obj; 59 } 60 }
AopTest.java 专门来测试aop的类,junit来写的,直接改成main方法亦可以
1 package com.pis.aop; 2 3 import org.junit.Before; 4 import org.junit.Test; 5 import org.springframework.context.ApplicationContext; 6 import org.springframework.context.support.ClassPathXmlApplicationContext; 7 8 9 10 public class AopTest { 11 ApplicationContext atx = null; 12 AopService aopService = null; 13 @Before 14 public void before(){ 15 atx = new ClassPathXmlApplicationContext("/spring/spring-aop-xml.xml"); 16 // atx = new ClassPathXmlApplicationContext("/spring/spring-aop-annotation.xml"); 17 aopService = (AopService)atx.getBean("aopService"); 18 } 19 20 21 22 23 //前置通知 24 @Test 25 public void testExecute(){ 26 aopService.execute("param"); 27 } 28 29 //例外通知 30 @Test 31 public void testExcp() throws Exception{ 32 aopService.excp(); 33 } 34 35 36 }
注解版:
spring-aop-annotation.xml 对,零配置 ,就是这么简单
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:context="http://www.springframework.org/schema/context" 6 xmlns:aop="http://www.springframework.org/schema/aop" 7 xsi:schemaLocation="http://www.springframework.org/schema/beans 8 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 9 http://www.springframework.org/schema/context 10 http://www.springframework.org/schema/context/spring-context-2.5.xsd 11 http://www.springframework.org/schema/aop 12 http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> 13 14 15 <aop:aspectj-autoproxy/> <!-- 开启自动扫描注解 --> 16 17 <context:component-scan base-package="com.pis.aop"/> <!-- 指定在哪个包范围扫描 --> 18 19 20 21 </beans>
被拦截类和切面与上面的一样,我是xml配置测试完了之后,直接在原有代码上面加的注解而已
AopTest.java 注解版的就是切换了一个xml文件而已,从spring-aop-xml.xml配置换成了spring-aop-annotation.xml配置
1 package com.pis.aop; 2 3 import org.junit.Before; 4 import org.junit.Test; 5 import org.springframework.context.ApplicationContext; 6 import org.springframework.context.support.ClassPathXmlApplicationContext; 7 8 9 10 public class AopTest { 11 ApplicationContext atx = null; 12 AopService aopService = null; 13 @Before 14 public void before(){ 15 // atx = new ClassPathXmlApplicationContext("/spring/spring-aop-xml.xml"); 16 atx = new ClassPathXmlApplicationContext("/spring/spring-aop-annotation.xml"); 17 aopService = (AopService)atx.getBean("aopService"); 18 } 19 20 21 22 23 //前置通知 24 @Test 25 public void testExecute(){ 26 aopService.execute("param"); 27 } 28 29 //例外通知 30 @Test 31 public void testExcp() throws Exception{ 32 aopService.excp(); 33 } 34 35 36 }
ok!上面的两种配置都已完成,测试不同版本的时候,只需切换读取的配置文件即可,做就做全,这里给出了前置通知获取参数,后置通知获取返回值,例外通知获得异常的例子,特别是前置通知获得参数闹了好久,两种版本的获值均已实现,下面给出截图,如有问题,欢迎拍砖,
这里是执行testExecute的结果,从这里可以很清晰的看到各个通知的执行顺序 前置通知-环绕通知-方法-后置通知-环绕通知-最终通知(对应的是finally),例外通知只能是连接点出异常的时候才会有,所有我特地写了两个测试方法,testExcp就是专门测试例外通知的
testExecute结果图
testExcp结果图 可以看见异常被拦截了
最后要提下,环绕通知中可以做很多事,比如权限拦截,日志拦截等,可以看切面的拦截方法around就知道,另外附上自己对各个aop名称的理解
aspect:切面 拦截类
joinpoint: 连接点 被拦截的方法
advice: 通知 拦截到方法之后要执行的各种操作,比如各种通知
cutpoint: 切入点 对哪些连接点进行的定义,也就是对那些被拦截方法或方法的集合的定义