Spring Aop
相关概念
AOP是面向切面编程的一种设计思想,Sring的IOc容器不需要依赖于SpringAOP,主要有一下几个相关术语
- 连接点(Joinpoint):指能够被拦截的点,在spring中,这些点指的都是方法,因为spring只支持方法类型的连接点,相当于目标对象类中的所有方法(可以被切入的点)
- 切入点(Pointcut):指我们要对哪些连接点进行拦截的定义(已经切入的点)
- 通知/增强(Advice):指拦截到连接点之后要做的事情就是通知,可以分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
- 引介(Introduction):引介是一种特殊的通知,在不修改类代码的情况下,Introduction可以在运行期为类动态的添加一些方法和Field
- 切面(Aspect):切入点和通知(引介)的结合
- 目标对象(Target):代理的目标对象
- 代理(Proxy):一个类被AOP织入增强后,就产生一个结果代理类
- 织入(Weaving):把增强的功能添加到目标对象来创建新的代理对象的过程,spring采用动态代理织入
通知主要有一下几个类型
- 前置通知:在目标方法之前调用
- 后置通知:在目标方法之后调用,有两种,一种是如果出现异常就不调用,一种是无论是否出现异常都调用
- 环绕通知:在目标方法之前、之后调用
- 异常通知:出现异常则通知
半自动方式开发
第一步:创建通知类
/**
* 通过继承通知类进行通知类型控制
*/
public class BeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("前置通知");
}
}
public class AfterAdvice implements AfterReturningAdvice {
/**
*
* @param returnValue 目标方法返回的内容
* @param method
* @param args
* @param target
* @throws Throwable
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("后置通知" + returnValue);
}
}
public class ArroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("环绕前");
//手动执行目标方法
Object proceed = invocation.proceed();//唤醒目标方法
System.out.println("环绕后");
return proceed;
}
}
第二步:创建目标接口和实现类
第三步:创建代理类(Spring提供)
<?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">
<!-- 1.创建通知类的对象 -->
<bean id="beforeAdvice" class="com.xm.advice.LogAdvice"/>
<bean id="afterAdvice" class="com.xm.advice.AfterAdvice"/>
<bean id="arroundAdvice" class="com.xm.advice.ArroundAdvice"/>
<!-- 2.创建目标类的对象 -->
<bean id="studentServiceTarget" class="com.xm.service.StudentServiceImpl"/>
<!-- 3.创建代理类的对象 -->
<bean id="studentServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 3.1 整合目标类 -->
<property name="target" ref="studentServiceTarget"></property>
<!-- 3.2 整合通知类 -->
<property name="interceptorNames">
<list>
<!--<value>logAdvice</value> -->
<value>afterAdvice</value>
<!--<value>arroundAdvice</value>-->
</list>
</property>
<!-- 3.3 关联接口 -->
<property name="proxyInterfaces">
<array>
<value>com.xm.service.StudentService</value>
</array>
</property>
</bean>
</beans>
测试:
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取代理类
StudentService studentService = (StudentService) context.getBean("studentServiceProxy");
studentService.study();
//测试后置通知
studentService.afterAdvice();
}
}
除了使用配置代理类的方式,还可以使用配置切入点和切入面的方式
<?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">
<!-- 1.创建通知类的对象 -->
<bean id="beforeAdvice" class="com.xm.advice.LogAdvice"/>
<bean id="afterAdvice" class="com.xm.advice.AfterAdvice"/>
<bean id="arroundAdvice" class="com.xm.advice.ArroundAdvice"/>
<!-- 2.创建目标类的对象 -->
<bean id="studentServiceTarget" class="com.xm.service.StudentServiceImpl"/>
<!-- 3. 使用切面配置 -->
<aop:config>
<aop:pointcut id="myPoint" expression="execution(* com.xm.service.*.*(..))"/>
<aop:advisor advice-ref="beforeAdvice" pointcut-ref="myPoint"/>
</aop:config>
</beans>
使用AspectJ的方式进行编程
xml方式的AOP开发
第一步:准备目标对象
创建接口和实现类
public interface UserService {
void save();
void delete();
void update();
void select();
}
public class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("保存用户");
}
@Override
public void delete() {
System.out.println("删除用户");
}
@Override
public void update() {
System.out.println("更新用户");
}
@Override
public void select() {
System.out.println("查询用户");
}
}
第二步:编写通知
import org.aspectj.lang.ProceedingJoinPoint;
public class TransactionAdvice {
//前置通知
public void before(){
System.out.println("前置通知");
}
//后置通知
public void after(){
System.out.println("后置通知(无论是否出现异常)");
}
//后置通知
public void afterReturning(){
System.out.println("后置通知(出现异常不调用");
}
//异常通知
public void afterException(){
System.out.println("异常通知");
}
//环绕通知
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("环绕:调用目标方法之前");
Object proceed = point.proceed();
System.out.println("环绕:调用目标方法之后");
return proceed;
}
}
第三步:配置织入,将通知织入到目标对象
<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
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 目标对象 -->
<bean name="userService" class="com.xm.service.UserServiceImpl"/>
<!-- 通知对象 -->
<bean name="transactionAdvice" class="com.xm.advice.TransactionAdvice"/>
<!-- 将通知对象织入目标对象 -->
<aop:config>
<!-- 选择切入点 -->
<aop:pointcut id="pointcut"
expression="execution(public void com.xm.service.UserServiceImpl.update())"/>
<!-- 选择切入面 -->
<aop:aspect ref="transactionAdvice">
<!-- 选择切入方法 -->
<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="pointcut"/>
<!-- 后置通知 出现异常不调用 -->
<!-- 如果存在返回参数returnValue,可以进行配置显示 -->
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="returnValue"/>
<!-- 后置通知 无论是否出现异常 -->
<aop:after method="after" pointcut-ref="pointcut"/>
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="pointcut"/>
<!-- 异常通知 -->
<aop:after-throwing method="afterException" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
关于切入点的相关配置:
- 表示服务层中任何业务服务执行的切入点可以定义如下(切入所有方法):
第一个
*
表示返回值可以是任意类型,第二个*
表示在该包下的任意类,第三个*
表示任意方法
若使用service..*.*(..)
作则表示该包及子包的任意方法,..
表示任意参数(有参或无参)
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xm.service.*.*(..))"/>
</aop:config>
第四步:测试
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Resource(name = "userService")
private UserService userService;
@Test
public void testUpdate(){
userService.update();
}
@Test
public void saveTest(){
userService.save();
}
}
注解方式开发
- 开启织入注解
<aop:aspectj-autoproxy/>
- 对通知类进行注解
- @Aspect:声明切入面
- @Pointcut:声明切入点
- @Before:声明前置通知
- @AfterReturning:声明后置通知(无异常时执行)
- @After:声明后置通知(无论是否异常)
- @AfterThrowing:异常通知
- @Around:环绕通知
package com.xm.advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
@Aspect
public class TransactionAdvice {
//声明一个切入点,方便后面的调用
@Pointcut("execution(* com.xm.service.*.*(..))")
public void pointcut(){}
//前置通知;
// @Before("execution(* com.xm.service.UserServiceImpl.*(..))")
@Before("TransactionAdvice.pointcut()")
public void before(){
System.out.println("前置通知");
}
//后置通知
@After("TransactionAdvice.pointcut()")
public void after(){
System.out.println("后置通知(无论是否出现异常)");
}
//后置通知
@AfterReturning("TransactionAdvice.pointcut()")
public void afterReturning(){
System.out.println("后置通知(出现异常不调用)");
}
//异常通知
@AfterThrowing("TransactionAdvice.pointcut()")
public void afterException(){
System.out.println("异常通知");
}
//环绕通知
@Around("TransactionAdvice.pointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("环绕:调用目标方法之前");
Object proceed = point.proceed();
System.out.println("环绕:调用目标方法之后");
return proceed;
}
}