一.Spring AOP
1.1 Spring AOP
底层还是用的动态代理。如果目标对象所对应的类有接口,spring就用jdk生成代理对象;
如果目标对象所对应的类没有接口,spring就用CGLIB生成代理对象。
优点:动态代理,如果一个类被它代理,则接口或者类的所有方法都被强迫执行。而spring AOP可以按业务划分,有些方法需要事务则扩展功能,有些方法不需要则不进行扩展。
1.2 相关概念名词
① 切面aspect:切面是一个类(功能类),类里有若干方法,每个方法代表一个功能。把这些功能方法切到指定的位置,切到到业务方法的前后等
对应:spring中<aop:aspect id="myAspect" ref="tm"></aop:aspect>
② 连接点joinpoint:用代理对象调用方法,这句话的位置就是连接点,用连接点启动横切。
③ 通知advice:切面类中的那些方法就是通知。
前置通知<aop:before 在执行目标方法前面
后置通知<aop:after-returning 在执行完目标方法后执行
异常通知<aop:after-throwing 在目标方法抛异常执行,不会执行后置通知
最终通知<aop:after 前置,后置或异常通知,都执行完毕后会执行最终通知
环绕通知<aop:around 特点:可以控制目标方法的执行
④ 切入点pointcut:把切面中的通知,切到哪个类中的哪些方法上,切入点是指切到哪些方法上,spring给提供一些规则表达式,用于指定切在哪些方法上。
对应:spring中<aop:pointcut id="myPointCut" expression="execution(* com.hdu.service..*.*(..))"/>
⑤ 目标对象targetObject:实际的业务类的对象
⑥ aop代理aopproxy:由spring框架帮程序员用jdk或cglib生成代理对象,这个生成的过程被spring封装了。spring的独有配置,用于指定生成代理对象。
对应: spring中<aop:config></aop:config>
⑦ 织入weaving:也叫横切,把额外的功能用配置文件和注解的方式织入到业务中;取消配置文件和注解,就取消了织入,不影响原有业务流程
在spring中加入<aop:config节点,<aop:pointcut expression="",从spring容器取出expression属性指定的对象都是代理对象。
在spring中没有加入<aop:config节点,从spring容器中取出的所有对象是真实对象。
1.3 参考手册
1.3.1 声明一个切面
1.3.2 声明一个切点
其中<aop:pointcut/>标签写在<aop:aspect></aop:aspect>标签内,为局部切点,只起作用在对应切面。
其中<aop:pointcut/>标签写在<aop:aspect></aop:aspect>标签外,为全局切点,所有切面对其有效。
关于切点的expression 表达式:
参考手册中的示例:
① the execution of any public method:所有的public方法
execution(public * *(..))
② the execution of any method with a name beginning with "set": 所有以set开头的方法
execution(* set*(..))
③ the execution of any method defined by the AccountService interface: com.xyz.service.AccountService类或接口中任意方法,任意参数,任意返回值
execution(* com.xyz.service.AccountService.*(..))
④ the execution of any method defined in the service package: com.xyz.service包下任意类,任意方法,任意参数,任意返回值
execution(* com.xyz.service.*.*(..))
⑤ the execution of any method defined in the service package or a sub-package: com.xyz.service包下以及子包下任意类.任意的方法,任意参数,任意返回值
execution(* com.xyz.service..*.*(..))
关于切点的within表达式:
① any join point (method execution only in Spring AOP) within the service package: com.xyz.service的任意类
within(com.xyz.service.*)
② any join point (method execution only in Spring AOP) within the service package or a sub-package: com.xyz.service及其子包下的任意类
within(com.xyz.service..*)
③ any join point (method execution only in Spring AOP) where the proxy implements the AccountService interface: com.xyz.service的AccountService类
this(com.xyz.service.AccountService)
1.3.3 声明通知
通常 前置通知、后置通知、异常通知、最终通知 与 环绕通知分开使用,否则执行顺序不符合预期逻辑。
若环绕通知叠加使用,按配置顺序先A后B,则执行时,先执行A在目标对象调用方法之前的内容,再执行B在目标对象调用方法之前的内容,调用目标对象的原有方法,执行A在目标对象调用方法之后的内容,执行B在目标对象调用方法之后的内容。
1.3.3.1 前置通知
1.3.3.2 后置通知
1.3.3.3 异常通知
1.3.3.4最终通知
1.3.3.5环绕通知
二.Spring AOP使用示例:使用xml配置文件的方式
2.1 新增jar包,支持对AOP的处理
2.2 接口及业务实现类
1 package com.hdu.dao; 2 3 public interface IUserDao { 4 public int addrUser(); 5 public int updateUser(); 6 }
1 package com.hdu.dao.impl; 2 3 import com.hdu.dao.IUserDao; 4 5 public class UserDaoImpl implements IUserDao { 6 7 @Override 8 public int addrUser() { 9 System.out.println("UserDaoImpl.addUser()"); 10 return 1; 11 } 12 13 @Override 14 public int updateUser() { 15 System.out.println("UserDaoImpl.updateUser()"); 16 return 1; 17 } 18 } 19 20 Class UserDaoImpl
1 package com.hdu.service; 2 3 public interface IUserService { 4 public boolean addUser(); 5 public boolean updateUser(); 6 7 } 8 9 Interface IUserService
1 package com.hdu.service.impl; 2 3 import com.hdu.dao.IUserDao; 4 import com.hdu.dao.impl.UserDaoImpl; 5 import com.hdu.service.IUserService; 6 7 public class UserServiceImpl implements IUserService { 8 //应该用spring进行解耦,本次重点在于代理,故简化操作 9 private IUserDao userDao = new UserDaoImpl(); 10 11 @Override 12 public boolean addUser() { 13 System.out.println("UserServiceImpl.addUser()"); 14 boolean flag = false; 15 int rowAffect = userDao.addrUser(); 16 if(rowAffect==1) { 17 flag = true; 18 } 19 return flag; 20 } 21 22 @Override 23 public boolean updateUser() { 24 System.out.println("UserServiceImpl.updateUser()"); 25 boolean flag = false; 26 int rowAffect = userDao.updateUser(); 27 if(rowAffect==1) { 28 flag = true; 29 } 30 return flag; 31 } 32 33 } 34 35 Class UserServiceImpl
2.3 切面类
其中JoinPoint 可以获取目标对象,目标方法及目标方法的参数,环绕通知则使用ProceedingJoinPoint 获取。
1 package com.hdu.aspect; 2 3 import org.aspectj.lang.JoinPoint; 4 import org.aspectj.lang.ProceedingJoinPoint; 5 6 /** 7 * 切面 8 * @author Administrator 9 * 10 */ 11 public class TransactionManager { 12 //前置通知方法 13 public void begin(JoinPoint jp) { 14 System.out.println("begin transaction"); 15 System.out.println("目标对象-->"+jp.getTarget()); 16 System.out.println("目标方法-->"+jp.getSignature().getName()); 17 System.out.println("目标方法参数-->"+jp.getArgs()[0]); 18 } 19 20 //后置通知方法 21 public void commit(JoinPoint jp,Object flag) { 22 System.out.println("commit transaction"); 23 System.out.println("目标对象-->"+jp.getTarget()); 24 System.out.println("目标方法-->"+jp.getSignature().getName()); 25 System.out.println("目标方法参数-->"+jp.getArgs()[0]); 26 System.out.println("目标方法的返回值-->"+flag); 27 } 28 //异常通知方法 29 public void rollback(JoinPoint jp,Exception ex) { 30 System.out.println("rollback transaction"); 31 System.out.println("目标对象-->"+jp.getTarget()); 32 System.out.println("目标方法-->"+jp.getSignature().getName()); 33 System.out.println("目标方法参数-->"+jp.getArgs()[0]); 34 System.out.println("目标方法抛异常-->"+ex.getMessage()); 35 } 36 //最终通知 37 public void finallyClose(JoinPoint jp) { 38 System.out.println("finallyClose"); 39 System.out.println("目标对象-->"+jp.getTarget()); 40 System.out.println("目标方法-->"+jp.getSignature().getName()); 41 System.out.println("目标方法参数-->"+jp.getArgs()[0]); 42 } 43 //环绕通知 能控制目标方法的执行 44 public Object around(ProceedingJoinPoint pjp)throws Throwable { 45 Object retVal=null; 46 try { 47 System.out.println("around begin transaction"); 48 System.out.println("目标对象-->"+pjp.getTarget()); 49 System.out.println("目标方法-->"+pjp.getSignature().getName()); 50 System.out.println("目标方法参数-->"+pjp.getArgs()[0]); 51 //if() { 52 //就是直接指定目标方法 53 retVal = pjp.proceed(); 54 //} 55 System.out.println("around commit transaction"); 56 }catch(Exception e) { 57 e.printStackTrace(); 58 System.out.println("around rollback transaction"); 59 }finally { 60 System.out.println("around finallyClose"); 61 } 62 63 return retVal; 64 } 65 }
2.4 配置文件
<?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" 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.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 实例化业务模型对象 --> <bean id="userDao" class="com.hdu.dao.impl.UserDaoImpl"></bean> <bean id="userService" class="com.hdu.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"></property> </bean> <!-- 实例化切面的对象 --> <bean id="tm" class="com.hdu.aspect.TransactionManager"></bean> <!-- spring 独有的配置,创建代理对象和确定如何横切切面 --> <!-- <aop:config>这个标签就告知spring要做代理对象了 原则:如果实际对象有接口就jdk动态代理生成代理对象;如果实际对象没有接口就用cglib动态代理生成代理对象 可以人为改动proxy-target-class="true",强制用cglib --> <aop:config proxy-target-class="false"> <!-- <aop:aspect 这个节点标签,告知spring使用哪个切面 可以写多个<aop:aspect节点,可以使用多个横切 --> <aop:aspect id="myAspect" ref="tm"> <!-- 告知spring 把切面 切在哪些类的哪些方法上,切点可以写多个--> <aop:pointcut id="myPointCut" expression="execution(* com.hdu.service..*.*(..))"/> <!-- 前置通知 --> <aop:before pointcut-ref="myPointCut" method="begin"/> <!-- 后置通知 属性returning="flag" flag对应的通知方法中的参数 --> <aop:after-returning pointcut-ref="myPointCut" method="commit" returning="flag"/> <!-- 异常通知 属性throwing="ex" ex对应的异常通知的方法的参数 --> <aop:after-throwing pointcut-ref="myPointCut" method="rollback" throwing="ex"/> <!-- 最终通知 --> <aop:after pointcut-ref="myPointCut" method="finallyClose"/> <!-- 环绕通知 --> <!-- <aop:around --> <!-- pointcut-ref="myPointCut" --> <!-- method="around"/> --> </aop:aspect> </aop:config> </beans>
2.5 测试
1 package com.hdu.test; 2 3 import org.junit.Test; 4 import org.springframework.context.ApplicationContext; 5 import org.springframework.context.support.ClassPathXmlApplicationContext; 6 7 import com.hdu.dao.UserDao; 8 import com.hdu.entity.User; 9 import com.hdu.service.UserService; 10 11 12 public class TestSpringAOP { 13 @Test 14 public void testMethod1() { 15 //初始化spring容器 16 ApplicationContext context = new ClassPathXmlApplicationContext("resources/spring_aop.xml"); 17 UserService userService=context.getBean("userService",UserService.class); 18 System.out.println(userService.getClass()); 19 userService.addUser(new User()); 20 21 //userDao不会被代理 22 UserDao userDao=context.getBean("userDao",UserDao.class); 23 System.out.print("userDao不会被代理"+userDao.getClass()); 24 } 25 }
结果:
class com.sun.proxy.$Proxy6 begin transaction 目标对象-->com.hdu.service.impl.UserServiceImpl@769f71a9 目标方法-->addUser 目标方法参数-->com.hdu.entity.User@4b5d6a01 UserServiceImpl.addUser() UserDaoImpl.addUser() commit transaction 目标对象-->com.hdu.service.impl.UserServiceImpl@769f71a9 目标方法-->addUser 目标方法参数-->com.hdu.entity.User@4b5d6a01 目标方法的返回值-->true finallyClose 目标对象-->com.hdu.service.impl.UserServiceImpl@769f71a9 目标方法-->addUser 目标方法参数-->com.hdu.entity.User@4b5d6a01 userDao不会被代理class com.hdu.dao.impl.UserDaoImpl
2.6调用过程
我们获取对象,要么是对象地址,要么是代理:System.out.println(userService.getClass());
从打印结果 class com.sun.proxy.$Proxy6 可知,目前使用的是jdk动态代理。
其中UserService userService = (UserService) context.getBean("userService");执行时spring已经把UserService变成代理对象。
在执行userService.addUser();业务方法时就会被invoke方法拦截。就被切点表达式expression="execution(* com.hdu.service..*.*(..))"拦截。
切点表达式expression="execution(* com.hdu.service..*.*(..))"匹配上了实现类,就会执行切面中通知。
<aop:aspect id="myAspect" ref="tm">
<aop:before pointcut-ref="myPointCut" method="begin"/>
</aop:aspect>
就找到TransactionManager类,找到before方法,并调用。
也就是说只要程序满足切点表达式,就会调用切面的方法。最终我们就看到它不是真实的对象,而是一个代理对象。由代理去执行就会响应执行通知的方法。
三.Spring AOP使用示例:使用注解的方式
3.1 切面类
参考手册:
切点可以是局部的也可以是全局的。
1 package com.hdu.aspect; 2 3 import org.aspectj.lang.JoinPoint; 4 import org.aspectj.lang.ProceedingJoinPoint; 5 import org.aspectj.lang.annotation.After; 6 import org.aspectj.lang.annotation.AfterReturning; 7 import org.aspectj.lang.annotation.AfterThrowing; 8 import org.aspectj.lang.annotation.Around; 9 import org.aspectj.lang.annotation.Aspect; 10 import org.aspectj.lang.annotation.Before; 11 import org.aspectj.lang.annotation.Pointcut; 12 import org.springframework.stereotype.Component; 13 14 /** 15 * 切面 16 * @author Administrator 17 * 18 */ 19 @Component("tm") 20 @Aspect 21 public class TransactionManager { 22 //the pointcut expression 23 @Pointcut("execution(* com.hdu.service..*.*(..))") 24 private void myPointCut() {}// the pointcut signature 25 26 //前置通知方法 27 //@Before("myPointCut()") 28 public void begin(JoinPoint jp) { 29 System.out.println("begin transaction"); 30 System.out.println("目标对象-->"+jp.getTarget()); 31 System.out.println("目标方法-->"+jp.getSignature().getName()); 32 System.out.println("目标方法参数-->"+jp.getArgs()[0]); 33 } 34 35 //后置通知方法 36 //@AfterReturning(pointcut="myPointCut()",returning="flag") 37 public void commit(JoinPoint jp,Object flag) { 38 System.out.println("commit transaction"); 39 System.out.println("目标对象-->"+jp.getTarget()); 40 System.out.println("目标方法-->"+jp.getSignature().getName()); 41 System.out.println("目标方法参数-->"+jp.getArgs()[0]); 42 System.out.println("目标方法的返回值-->"+flag); 43 } 44 //异常通知方法 45 //@AfterThrowing(pointcut="myPointCut()",throwing="ex") 46 public void rollback(JoinPoint jp,Exception ex) { 47 System.out.println("rollback transaction"); 48 System.out.println("目标对象-->"+jp.getTarget()); 49 System.out.println("目标方法-->"+jp.getSignature().getName()); 50 System.out.println("目标方法参数-->"+jp.getArgs()[0]); 51 System.out.println("目标方法抛异常-->"+ex.getMessage()); 52 } 53 //最终通知 54 //@After("myPointCut()") 55 public void finallyClose(JoinPoint jp) { 56 System.out.println("finallyClose"); 57 System.out.println("目标对象-->"+jp.getTarget()); 58 System.out.println("目标方法-->"+jp.getSignature().getName()); 59 System.out.println("目标方法参数-->"+jp.getArgs()[0]); 60 } 61 62 //环绕通知 能控制目标方法的执行 63 @Around("myPointCut()") 64 public Object around(ProceedingJoinPoint pjp)throws Throwable { 65 Object retVal=null; 66 try { 67 System.out.println("around begin transaction"); 68 System.out.println("目标对象-->"+pjp.getTarget()); 69 System.out.println("目标方法-->"+pjp.getSignature().getName()); 70 System.out.println("目标方法参数-->"+pjp.getArgs()[0]); 71 //if() { 72 //就是直接指定目标方法 73 retVal = pjp.proceed(); 74 75 //} 76 System.out.println("around commit transaction"); 77 }catch(Exception e) { 78 e.printStackTrace(); 79 System.out.println("around rollback transaction"); 80 }finally { 81 System.out.println("around finallyClose"); 82 } 83 84 return retVal; 85 } 86 }
3.2 配置清单
<?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" 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.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- @Controller @Service @Repository @Component @Resource @Autowired @Qualifier --> <!-- 实例化业务模型对象 --> <context:component-scan base-package="com.hdu.dao.impl"></context:component-scan> <context:component-scan base-package="com.hdu.service.impl"></context:component-scan> <!-- 实例化切面的对象 --> <context:component-scan base-package="com.hdu.aspect"></context:component-scan> <!-- spring 独有的配置,创建代理对象和确定如何横切切面 @Aspect @Pointcut @Before @AfterReturning @AfterThrowing @After @Around --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
3.3 测试
1 package com.hdu.test; 2 3 import org.junit.Test; 4 import org.springframework.context.ApplicationContext; 5 import org.springframework.context.support.ClassPathXmlApplicationContext; 6 7 import com.hdu.entity.User; 8 import com.hdu.service.UserService; 9 10 11 public class TestSpringAOP { 12 @Test 13 public void testMethod1() { 14 //初始化spring容器 15 ApplicationContext context= new ClassPathXmlApplicationContext("resources/spring_aop.xml"); 16 UserService userService=context.getBean("userService",UserService.class); 17 System.out.println(userService.getClass()); 18 userService.addUser(new User());//连接点 启动横切 19 } 20 21 22 }
结果
class com.sun.proxy.$Proxy13 around begin transaction 目标对象-->com.hdu.service.impl.UserServiceImpl@217ed35e 目标方法-->addUser 目标方法参数-->com.hdu.entity.User@f5958c9 UserServiceImpl.addUser() UserDaoImpl.addUser() around commit transaction around finallyClose