Spring
中Aop
的配置方式
准备
一个 Spring 的 java 项目。
AOP 术语
几个重要AOP
的术语
- 通知(
Advice
)
- 连接点(
JoinPoint
)
- 切入点(
Pointcut
)
- 切面(
Aspect
)
- 目标(
target
)
通知(Advice
)
需要织入的一段逻辑代码。
比如我们需要检查某个函数的参数是否合法。
就需要在方法体之前织入一段逻辑来判断参数。
这段逻辑一般为一个函数。
这个函数就叫做通知。
连接点(JoinPoint
)
连接点的意思是允许我们在哪些地方可以织入一段逻辑。
在Spring
中,可以织入的方式有5种。
Before
- 前置通知(方法前)
After
- 后置通知(方法后)
Around
- 环绕通知(方法前后)
AfterReturning
- 返回通知(方法返回值之后)
AfterThrowing
- 异常通知(方法抛出异常之后)
切入点(Pointcut
)
每个方法都可以有五个连接点。
但是实际上我们可能只需在某些地方织入一段逻辑。
这个织入的连接点就叫做切入点。
即连接点是告诉你哪些地方可以织入。
而切入点是实际需要织入的地方。
切面(Aspect
)
通常为一个类。
里面包括了若干的通知(方法)和对应的切入点。
目标(Target
)
需要被通知的对象。
真正的业务逻辑不会感知到我们的切入。
一切织入都是透明的,不可见的。
AOP 配置
为了模拟服务。
这里建立三个类来测试。
一个是POJO
的Person
类。
1 2 3 4 5
| public class Person { private int id; private String name; }
|
一个是Service
的PersonService
类来模拟对Person
的服务操作。
1 2 3
| public class PersonService { }
|
一个是切面类(Aspect
)。
XML
配置
这里我们在PersonService
里面增加一个方法。
模拟获取一个Person
对象。
1 2 3 4 5 6 7 8 9 10
| public class PersonService { public Person findPersonById(int id) { Person person = new Person(); person.setId(id); person.setName("lwf"); System.out.println("查询中..."); return person; } }
|
我们需要先把切面(Aop
)类,服务(PersonService
)类。
配置为Bean
,让Spring
管理它们。
1 2 3 4 5 6 7 8
| <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="personService" class="service.PersonService"></bean> <bean id="aop" class="aop.Aop"></bean> </beans>
|
现在我们想在findPersonById
这个服务前打印日志。
输出查询的id
。
1 2 3 4 5 6 7
| public class Aop { public void before(JoinPoint joinPoint) throws Exception { Object[] args = joinPoint.getArgs(); int id = (int) args[0]; System.out.println("logger: " + id + "的Person开始查询"); } }
|
写完通知之后需要在xml
配置这个通知织入findPersonById
这个服务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="personService" class="service.PersonService"></bean> <bean id="aop" class="aop.Aop"></bean>
<aop:config> <aop:aspect ref="aop"> <aop:before method="before" pointcut="execution(public * service.PersonService.findPersonById(int))"></aop:before> </aop:aspect> </aop:config> </beans>
|
写个测试类来测试下:
1 2 3 4 5 6 7 8 9
| public class SpringTest { @Test public void test01() { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); PersonService personService = applicationContext.getBean("personService", PersonService.class); Person person = personService.findPersonById(100); System.out.println(person); } }
|
运行之后,可以看见确实在方法调用之前织入了日志的通知:
上面配置了前置通知。
后置通知和前置通知差不多。
只需要改变下参数就行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="personService" class="service.PersonService"></bean> <bean id="aop" class="aop.Aop"></bean>
<aop:config> <aop:aspect ref="aop"> <aop:before method="after" pointcut="execution(public * service.PersonService.findPersonById(int))"></aop:before> </aop:aspect> </aop:config> </beans>
|
1 2 3 4 5 6 7 8
| public class Aop { public void after(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); int id = (int) args[0]; System.out.println("logger: " + id + "的Person查询完成"); } }
|
运行之前的test01
之后可以看到在服务之后织入了逻辑。
现在来配置下afterReturning
和afterThrowing
。
看名字很容易看出,就是在返回值或者抛出异常之后运行。
先配置afterReturning
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="personService" class="service.PersonService"></bean> <bean id="aop" class="aop.Aop"></bean>
<aop:config> <aop:aspect ref="aop"> <aop:after-returning method="afterReturning" pointcut="execution(public * service.PersonService.findPersonById(int))" returning="person"></aop:after-returning> </aop:aspect> </aop:config> </beans>
|
1 2 3 4 5 6
| public class Aop { public void afterReturning(Person person) { System.out.println("logger: 返回了Person对象: " + person); } }
|
运行test01
,可以看到织入成功:
再来配置afterThrowing
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="personService" class="service.PersonService"></bean> <bean id="aop" class="aop.Aop"></bean>
<aop:config> <aop:aspect ref="aop"> <aop:after-throwing method="afterThrowing" pointcut="execution(public * service.PersonService.findPersonById(int))" throwing="ex"></aop:after-throwing> </aop:aspect> </aop:config> </beans>
|
1 2 3 4 5 6
| public class Aop { public void afterThrowing(Exception ex) { System.out.println("logger: 捕捉到异常: " + ex.getMessage()); } }
|
为了查看异常通知的效果。
我们在findPersonById
模拟。
如果id
为0
就抛出一个异常。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class PersonService { public Person findPersonById(int id) { Person person = new Person(); person.setId(id); person.setName("lwf"); System.out.println("查询中..."); if (id == 0) { throw new Exception("出错了,没查到~~"); } return person; } }
|
然后我们修改下test01
1 2 3 4 5 6 7 8 9 10 11 12
| public class SpringTest { @Test public void test01() { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); PersonService personService = applicationContext.getBean("personService", PersonService.class); try { Person person = personService.findPersonById(0); System.out.println(person); } catch (Exception e) { } } }
|
运行之后可以看到确实捕获到了异常
接下来是环绕通知的配置。
环绕通知的配置和其他的有所不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="personService" class="service.PersonService"></bean> <bean id="aop" class="aop.Aop"></bean>
<aop:config> <aop:aspect ref="aop"> <aop:around method="around" pointcut="execution(public * service.PersonService.findPersonById(int))"></aop:around> </aop:aspect> </aop:config> </beans>
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Aop { public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("logger: 环绕通知的前置"); Object proceed = joinPoint.proceed(); System.out.println("logger: 环绕通知的后置"); return proceed; } }
|
运行test01
发现环绕的后置没有执行。
原因是在目标中抛出了异常,函数就终止运行了。
我们可以把id
改为大于0
的数。
这样子就可以看到效果了:
注解配置
注解的话其实都差不多:
@Aspect
配置切面
@Before
前置通知
@After
后置通知
@AfterReturning
返回通知
@AfterThrowing
异常通知
先在xml
配置开启bean
注解和aop
注解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?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:context="http://www.springframework.org/schema/context" 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"> <context:component-scan base-package="pojo,aop"/> <aop:aspectj-autoproxy/> </beans>
|
在PersonService
上配置@Component
。
然后在Aop
类上标注相应的注解就可以了。
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
| @Aspect @Component public class Aop { @Before("execution(public * service.PersonService.findPersonById(int))") public void before(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); int id = (int) args[0]; System.out.println("logger: " + id + "的Person开始查询"); }
@After("execution(public * service.PersonService.findPersonById(int))") public void after(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); int id = (int) args[0]; System.out.println("logger: " + id + "的Person查询完成"); }
@Around("execution(public * service.PersonService.findPersonById(int))") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("logger: 环绕通知的前置"); Object proceed = joinPoint.proceed(); System.out.println("logger: 环绕通知的后置"); return proceed; }
@AfterReturning( pointcut = "execution(public * service.PersonService.findPersonById(int))", returning = "person" ) public void afterReturning(Person person) { System.out.println("logger: 返回了Person对象: " + person); }
@AfterThrowing( pointcut = "execution(public * service.PersonService.findPersonById(int))", throwing = "ex" ) public void afterThrowing(Exception ex) { System.out.println("logger: 捕捉到异常: " + ex.getMessage()); } }
|
运行test01
的结果和xml
配置的一样。
后记
一个目标也可以织入多个通知。
这时候就可以用@Order
注解来配置先后顺序。