概述
AOP ,即面向切面编程,可以用来管理一些和主业务无关的周边业务,如日志记录,事务管理等;
相关概念
Join point
:连接点,表示程序执行期间的一个点,在 Spring AOP 表示的就是一个方法,即一个方法可以看作是一个 Join point
pointcut
:切点,就是与连接点匹配的谓词,什么意思呢,就是需要执行 Advice 的连接点就是切点
Advice
:增强,在连接点执行的操作,分为前置、后置、异常、最终、环绕增强五种
Aspect
:切面,由 pointcut 和 Advice 组成,可以简单的认为 @Aspect 注解的类就是一个切面
Target object
:目标对象,即 织入 advice 的目标对象
AOP proxy
:代理类,在 Spring AOP 中, 一个 AOP 代理是一个 JDK 动态代理对象或 CGLIB 代理对象
Weaving
:织入,将 Aspect 应用到目标对象中去
demo
要想使用 Spring AOP ,首先先得在 Spring 配置文件中配置如下标签:
1
| <aop:aspectj-autoproxy expose-proxy="true" proxy-target-class="true"/>
|
expose-proxy
: 是否需要将当前的代理对象使用 ThreadLocal 进行保存,默认为false。
例如 Aop 需要对某个接口下的所有方法进行拦截,但是有些方法在内部进行自我调用:
1 2 3 4 5 6 7
| 1 public void test_1() 2 { 3 this.test_2(); 4 } 5 public void test_2() 6 { 7 }
|
调用 test_1,此时 test_2 将不会被拦截进行增强。所以该属性 expose-proxy 就是用来解决这个问题的,即 AOP 代理的获取。
proxy-target-class
:是否使用 CGLIB 进行代理,默认为false,使用JDK代理。当需要代理的类没有实现任何接口的时候才会使用 CGLIB 进行代理。如果想都是用 CGLIB 进行代理,可以把该属性设置为 true 即可。
接口:
1 2 3 4 5 6 7
| 1public interface IUserService { 2 void add(User user); 3 User query(String name); 4 List<User> qyertAll(); 5 void delete(String name); 6 void update(User user); 7}
|
接口实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| 1@Service("userServiceImpl") 2public class UserServiceImpl implements IUserService { 3 4 @Override 5 public void add(User user) { 6 System.out.println("添加用户成功,user=" + user); 7 } 8 9 @Override 10 public User query(String name) { 11 System.out.println("根据name查询用户成功"); 12 User user = new User(name, 20, 1, 1000, "java"); 13 return user; 14 } 15 16 @Override 17 public List<User> qyertAll() { 18 List<User> users = new ArrayList<>(2); 19 users.add(new User("zhangsan", 20, 1, 1000, "java")); 20 users.add(new User("lisi", 25, 0, 2000, "Python")); 21 System.out.println("查询所有用户成功, users = " + users); 22 return users; 23 } 24 25 @Override 26 public void delete(String name) { 27 System.out.println("根据name删除用户成功, name = " + name); 28 } 29 30 @Override 31 public void update(User user) { 32 System.out.println("更新用户成功, user = " + user); 33 } 34}
|
定义AOP切面 前置增强:
1 2 3 4 5 6 7 8 9 10
| 1@Component 2@Aspect 3public class UserAspectj { 4 5 6 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))") 7 public void before_1(){ 8 System.out.println("log: 在 add 方法之前执行...."); 9 } 10}
|
如果想要获取目标方法执行的参数等信息呢,我们可在 切点的方法中添参数 JoinPoint ,通过它了获取目标对象的相关信息:
1 2 3 4 5 6 7 8 9
| 1 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))") 2 public void before_2(JoinPoint joinPoint){ 3 Object[] args = joinPoint.getArgs(); 4 User user = null; 5 if(args[0].getClass() == User.class){ 6 user = (User) args[0]; 7 } 8 System.out.println("log: 在 add 方法之前执行, 方法参数 = " + user); 9 }
|
后置增强:
1 2 3 4 5
| 1 2 @After("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))") 3 public void after_1(){ 4 System.out.println("log: 在 add 方法之后执行...."); 5 }
|
返回增强:
1 2 3 4
| 1@AfterReturning(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", returning="object") 2public void after_return(Object object){ 3 System.out.println("在 query 方法返回后执行, 返回值= " + object); 4}
|
异常增强:
1 2 3 4
| 1@AfterThrowing(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", throwing = "ex") 2public void after_throw(Exception ex){ 3 System.out.println("在 query 方法抛异常时执行, 异常= " + ex); 4}
|
环绕增强:
1 2 3 4 5 6 7
| 1@Around("execution(* main.tsmyk.mybeans.inf.IUserService.delete(..))") 2public void test_around(ProceedingJoinPoint joinPoint) throws Throwable { 3 Object[] args = joinPoint.getArgs(); 4 System.out.println("log : delete 方法执行之前, 参数 = " + args[0].toString()); 5 joinPoint.proceed(); 6 System.out.println("log : delete 方法执行之后"); 7}
|
也可以现在可以使用 @Pointcut 来声明一个可重用的切点表达式:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 1 2@Pointcut("execution(* main.tsmyk.mybeans.inf.IUserService.query(..))") 3public void pointcut(){ 4} 5 6@Before("pointcut()") 7public void before_3(){ 8 System.out.println("log: 在 query 方法之前执行"); 9} 10@After("pointcut()") 11public void after_4(){ 12 System.out.println("log: 在 query 方法之后执行...."); 13}
|
指示符
execution
: 它用来匹配方法执行的连接点,也是 Spring AOP 使用的主要指示符,在切点表达式中使用了通配符 () 和 (.. ),其中,( )可以表示任意方法,任意返回值,(..)表示方法的任意参数。
within
: 匹配特定包下的所有类的所有 Joinpoint(方法),包括子包,注意是所有类,而不是接口,如果写的是接口,则不会生效。
@within
: 匹配所有持有指定注解类型的方法,如 @within(Secure),任何目标对象持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用。
target
: 匹配的是一个目标对象,target(main.tsmyk.mybeans.inf.IUserService)匹配的是该接口下的所有 Join point 。
@target
: 匹配一个目标对象,这个对象必须有特定的注解,如 @target(org.springframework.transaction.annotation.Transactional) 匹配任何 有 @Transactional 注解的方法。
this
: 匹配当前AOP代理对象类型的执行方法,this(service.IPointcutService),当前AOP对象实现了 IPointcutService接口的任何方法。
arg
: 匹配参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(name)") public void test_arg(){
}
@Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(name, ..)") public void test_arg2(){
}
@Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(*, name, ..)") public void test_arg3(){
}
|
@arg
: 匹配参数,参数有特定的注解,@args(Anno)),方法参数标有Anno注解。
bean
: 匹配特定的 bean 名称的方法
1 2 3 4 5 6 7 8 9 10 11
| 1 2 @Before("bean(userServiceImpl)") 3 public void test_bean(){ 4 System.out.println("==================="); 5 } 6 7 8 @Before("bean(*ServiceImpl)") 9 public void test_bean2(){ 10 System.out.println("+++++++++++++++++++"); 11 }
|
@annotation
: 匹配特定注解
@annotation(org.springframework.transaction.annotation.Transactional) 匹配 任何带有 @Transactional 注解的方法。
相关
上文描述了用注解方式实现AOP的方法,一下是xml配置方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?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" 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"> <bean id="arithmeticCalculator" class="com.spring.aop.AtitheticCalculatorImp"></bean> <bean id="loggingAspect" class="com.spring.aop.impl.LoggingAspect"></bean> <bean id="vlidationAspect" class="com.spring.aop.impl.Aspect2"></bean> <aop:config> <aop:pointcut id="pointcut" expression="execution(* com.spring.aop.*.*(int,int))"/> <aop:aspect ref="loggingAspect" order="2"> <aop:before method="beforeMethod" pointcut-ref="pointcut"/> <aop:after method="afterMethod" pointcut-ref="pointcut"/> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/> </aop:aspect> <aop:aspect ref="vlidationAspect" order="1"> <aop:before method="validationArgs" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
|
声明式事务配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/wangyiyun"></property> <property name="user" value="root"></property> <property name="password" value="153963"></property> </bean>
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
<tx:advice id="txadvice" transaction-manager="dataSourceTransactionManager"> <tx:attributes>
<tx:method name="account*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" rollback-for="" timeout="-1" /> </tx:attributes> </tx:advice>
<aop:config> <aop:pointcut expression="execution(* cn.itcast.service.OrdersService.*(..))" id="pointcut1" /> <aop:advisor advice-ref="txadvice" pointcut-ref="pointcut1" /> </aop:config>
|