概念
Spring的AOP底层实现就是对动态代理的代码进行了封装, 封装后我们只需要对需要关注的部分进行代码编写, 并通过配置的方式完成指定目标方法的增强.
1. AOP相关术语
- Target(目标对象): 代理的目标对象.
- Proxy(代理): 一个类被AOP织入增强后, 就产生一个结果代理类.
- JoinPoint(连接点): 所谓连接点是指那些被拦截到的点. 在Spring中, 这些点指的是方法,因为Spring只支持方法类型的连接点.
- PointCut(切入点): 所谓的切入点是指我们要对哪些JoinPoint进行拦截定义.
- Advice(通知/增强): 拦截到JoinPoint之后要做的事情就是通知.
- Aspect(切面): 切入点和通知的结合.
- Weaving(织入): 指把增加应用到目标对象来创建新的代理对象的过程. Spring采用动态代理织入, 而AspectJ采用编译期织入和类装载时织入.
2. 切点表达式写法
表达式语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以省略.
- 返回值类型、包名、类名、方法名可以用*代表任意.
- 包名与类名之间一个点".“代表当前包下的类, 两个点”…"表示当前包及其子包下的类.
- 参数列表可以使用"…"表示任意个数, 任意类型的参数列表.
3. 通知的类型
名称 | 标签 | 说明 |
---|---|---|
前置通知 | aop:before | 指定增强的方法在切入点方法之前执行 |
后置通知 | aop:after-returning | 指定增强的方法在切入点方法之后执行 |
环绕通知 | aop:around | 指定增强的方法在切入点之前和之后都执行 |
异常抛出 | aop:after-throwing | 指定增强的方法在出现异常时执行 |
最终通知 | aop:after | 无论增强方式执行是否有异常都会执行 |
4. 编写代码测试
4.1 基于xml配置
- pom.xml
<!-- Spring集成的JUnit -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!-- aop需要依赖这个 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<!-- Spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
- 目标接口类
public interface Target
{
public void save();
}
- 目标实现类
public class TargetImpl implements Target
{
public void save()
{
// 制造异常, 用来测试异常通知
// int i = 1/0;
System.out.println("save running...");
}
}
- 自定义切面类
public class MyAspect
{
public void before()
{
System.out.println("前置通知...");
}
public void around(ProceedingJoinPoint pjp) throws Throwable
{
System.out.println("环绕通知前...");
pjp.proceed();
System.out.println("环绕通知后...");
}
public void afterReturning()
{
System.out.println("后置通知...");
}
public void throwing()
{
System.out.println("异常通知...");
}
public void after()
{
System.out.println("最终通知...");
}
}
- 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"
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="target" class="site.zhouyun.aop.TargetImpl"></bean>
<!-- 切面对象 -->
<bean id="myAspect" class="site.zhouyun.aop.MyAspect"></bean>
<aop:aspectj-autoproxy />
<!--
AOP配置,要使用aop标签需要先添加命名空间.
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
-->
<aop:config>
<!-- 申明切面 -->
<aop:aspect ref="myAspect">
<!-- 定义公共切入点 -->
<aop:pointcut id="logPoint" expression="execution(* site.zhouyun.aop.*.*(..))"/>
<!-- 切面: 切入点+通知 -->
<aop:before method="before" pointcut="execution(public void site.zhouyun.aop.TargetImpl.save())"></aop:before>
<aop:after-returning method="afterReturning" pointcut="execution(* site.zhouyun.aop.Target.*(..))"></aop:after-returning>
<aop:around method="around" pointcut-ref="logPoint"></aop:around>
<aop:after-throwing method="throwing" pointcut-ref="logPoint"></aop:after-throwing>
<aop:after method="after" pointcut-ref="logPoint"></aop:after>
</aop:aspect>
</aop:config>
</beans>
- 测试代码
//使用Spring集成的junit测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest
{
@Autowired
Target target;
@Test
public void test()
{
target.save();
}
}
- 测试结果
前置通知…
环绕通知前…
save running…
最终通知…
环绕通知后…
后置通知…
4.2 基于全注解方式
- pom.xml不变
- 目标接口类
public interface Target
{
public void save();
}
- 目标接口实现类
@Component("target")
public class TargetImpl implements Target
{
public void save()
{
System.out.println("save running");
}
}
- 自定义切面类
@Component("myAspect")
@Aspect
public class MyAspect
{
@Before("execution(public void site.zhouyun.annotation.TargetImpl.save())")
public void before()
{
System.out.println("前置通知...");
}
@AfterReturning("execution(* site.zhouyun.annotation.Target.*(..))")
public void afterReturning()
{
System.out.println("后置通知...");
}
// 引用公共切入点方式1
@Around("pointcut()")
public void around(ProceedingJoinPoint pjp) throws Throwable
{
System.err.println("环绕通知开始...");
pjp.proceed();
System.err.println("环绕通知结束...");
}
// 引用公共切入点方式2
@AfterThrowing("MyAspect.pointcut()")
public void afterThrowing()
{
System.out.println("异常通知...");
}
@After("pointcut()")
public void after()
{
System.out.println("最终通知...");
}
/**
* 定义公用切入点
*/
@Pointcut("execution(* site.zhouyun.annotation.*.*(..))")
public void pointcut()
{
}
}
- Spring配置类
/**
* Spring配置类
* EnableAspectJAutoProxy: 相当于<aop:aspectj-autoproxy />, 开启自动代理,使用注解方式必须加上这个.
*/
@Configuration
@ComponentScan("site.zhouyun.annotation")
@EnableAspectJAutoProxy
public class SpringConfiguration
{
}
- 测试代码
/**
* ContextConfiguration: 指定classes为自定义的SpringConfiguration
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class AnnotationAopTest
{
@Autowired
Target target;
@Test
public void test()
{
target.save();
}
}
- 测试结果
环绕通知开始…
前置通知…
save running
后置通知…
最终通知…
环绕通知结束…
5. 注解说明
如果对某些注解不明白意思,可以查看Spring 零配置注解开发增删改查Demo这篇博客.