一、概念
SpringAOP的底层实现就是对动态代理进行了封装,封装后我们只需要对关注的部分进行编码,并通过配置的方式完成指定目标方法的增强。
相关概念:
- Target(目标对象):要被增强的对象,一般指业务逻辑类的对象。
- Proxy(代理):一个类被AOP织入增强以后,就产生了一个结果代理类。
- Aspect(切面):表示增强的功能,就是一些代码完成的某些功能,即非业务功能。是切入点和通知的结合。
- Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在Spring中,这些点是指方法(一般是类中业务方法),Spring只支持方法类型的连接点。
- Pointcut(切入点):指的是声明的一个或多个连接点的集合,通过切入点指定一组方法。被标记为final的方法是不能作为连接点或切入点的,因为它不能被修改,也不能增强。
- Advice(通知/增强):拦截到Joinpoint之后需要做的事情就是通知。通知定义了增强代码切入到目标代码的时间点,即目标执行方法之前还是之后。通知类型不同,切入时间不同。 通知类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
切入点定义切入位置,通知定义切入时间。 - Weaving(织入):指把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理方式织入,而AspectJ采用编译期织入和类装载期织入。
切面的三个关键因素:
- 切面的功能
- 切面的执行位置(pointCut)
- 切面的执行时间(advice)
二、AspectJ对AOP的实现
对于AOP的编程思想,很多框架都进行了实现,Spring就是其中之一,可以完成面向切面编程。AspectJ也实现了AOP功能,且其实现方式更为简洁还支持注解式开发。所以,Spring又将AspectJ对AOP的实现引入到自己的框架中。
AspectJ是一个优秀的面向切面的框架,它扩展了Java语言,提供了强大的切面实现。
2.1、AspectJ的通知类型
AspectJ支持5种通知类型:
- 前置通知
- 后置通知
- 异常通知
- 最终通知
- 环绕通知
2.2、AspectJ切入点表达式
AspectJ定义了专门的表达式用于指定切入点。
表达式原型:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
说明:
表达式 | 描述 |
---|---|
modifiers-pattern | 访问权限类型 |
ret-type-pattern | 返回值类型 |
declaring-type-pattern | 包名类名 |
name-pattern(param-pattern) | 方法名(参数类型和参数个数) |
throws-pattern | 抛出异常类型 |
? | 可选部分 |
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution表达式中就是方法的签名。表达式各部分间用空格分隔,在其中可以使用以下符号:
符号 | 含义 |
---|---|
* | 0-多个任意字符 |
… | 用在方法参数中,表示任意个参数;用在包后,表示当前及其子包路径 |
+ | 用在类名后,表示当前及其子类;用在接口后,表示当前接口及其实现 |
示例:
// 指定切点为:定义在service包中任意类 任意方法
execution(* com.kkb.service.*.*(..))
// 指定切点为:定义在service包或者子包中任意类 任意方法。 ..出现在类名中,后面必须跟*,表示包、子包下所有类。
execution(* com.kkb.service..*.*(..))
// 指定切点为:IUserService若为接口,则表示接口中任意方法及其实现类中任意方法;若为类,则表示该类及其子类中任意方法
execution(* com.kkb.service.IUserService+.*(..))
三、注解方式实现AOP
开发阶段:关心核心代码和AOP代码
运行阶段:spring框架会在运行时将核心业务和AOP代码通过动态代理方式编织在一起
代理方式选择:如果实现了接口就选择JDK动态代理;没有就选择CGLIB动态代理。
3.1、新建maven项目添加pom依赖
<dependencies>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- spring依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
3.2、添加配置文件application.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 包扫描 -->
<context:component-scan base-package="com.jsonliu.test.service,com.jsonliu.test.config" />
<!-- 开启aop注解使用 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!--
aop:aspectj-autoproxy 底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的,是基于AspectJ的注解适配自动代理生成器。
工作原理:aop:aspectj-autoproxy通过扫描找到@Aspect定义的切面,再由切面根据切点找到目标类的目标方法,再由通知类型找到切入的时间点。
-->
</beans>
3.3、添加接口
public interface IService {
void add(int id,String name);
boolean update(int num);
}
3.4、添加实现类
@Service
public class TeamService implements IService{
@Override
public void add(int id, String name) {
System.out.println("TeamService-------------add------------------");
}
@Override
public boolean update(int num) {
System.out.println("TeamService-------------update------------------");
// 模拟异常
int k=num/0;
if(num>999) return true;
return false;
}
}
@Service("userService")
public class UserService implements IService{
@Override
public void add(int id, String name) {
System.out.println("UserService-------------add------------------");
}
@Override
public boolean update(int num) {
System.out.println("UserService-------------update------------------");
if(num<999) return true;
return false;
}
}
3.5、添加切面
@Aspect
@Component
public class MyAspect {
/**
* 当多个通知增强方法使用相同的execution切入点表达式,编写维护较为麻烦。
* AspectJ提供了@Pointcut注解,用于定义execution切入点表达式
* 用法:将@Pointcut注解在一个方法上,以后所有的execution的value值都可以使用该方法名作为切入点。
* 代表的就是@Pointcut定义的切入点
* 这个使用@Pointcut的注解方法一般使用private标识的方法,即没有实际作用的方法
*/
@Pointcut("execution(* com.jsonliu.test.service..*.*(..))")
private void pointCut(){
}
/**
* 前置通知
* @param jp
*/
@Before("pointCut()")
public void before(JoinPoint jp) {
System.out.println("前置通知before:目标方法执行之前调用的通知");
String name = jp.getSignature().getName();
System.out.println("拦截的方法名称:" + name);
Object[] args = jp.getArgs();
System.out.println("拦截的方法个数:" + args.length);
for (Object arg : args) {
System.out.println("拦截的参数:" + arg);
}
}
/**
* 后置通知
* value:切入点表达式,returning:返回的结果,可以在后置通知中修改
* @param result
* @return
*/
@AfterReturning(value = "pointCut()",returning = "result")
public Object afterReturn(Object result){
System.out.println("后置通知afterReturn:目标方法执行之后被调用的通知,result="+result);
return result==null?"":result.toString();
}
/**
* 环绕通知
* ProceedingJoinPoint中的proceed()方法表示目标方法被执行
* @param pjp
* @return
* @throws Throwable
*/
@Around(value = "pointCut()")
public Object around(ProceedingJoinPoint pjp)throws Throwable {
System.out.println("环绕通知--目标方法执行之前");
Object proceed = pjp.proceed();
System.out.println("环绕通知--目标方法执行之后");
return proceed;
}
/**
* 异常通知
* value:切入点表达式,ex:异常信息
* @param jp
* @param ex
*/
@AfterThrowing(value = "pointCut()",throwing = "ex")
public void exception(JoinPoint jp,Throwable ex){
//一般用于记录异常发生的时间、位置、原因
System.out.println("异常通知exception:目标方法执行出现异常时才会调用的通知,否则不会执行");
System.out.println(jp.getSignature().getName()+"方法出现异常,异常的信息:"+ex.getMessage());
}
/**
* 最终通知
*/
@After("pointCut()")
public void myFinally(){
System.out.println("最终通知after:无论是否出现异常都是最后执行的通知");
}
}
3.6、添加测试类
@Test
public void test1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
TeamService teamService = (TeamService) applicationContext.getBean("teamService");
System.out.println("-----------------------------add---------------------------------");
teamService.add(1,"阿森纳");
System.out.println("-----------------------------update---------------------------------");
teamService.update(9999);
System.out.println("-----------------------------UserService---------------------------------");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.add(22,"曼联");
}
运行结果:
四、XML方式实现AOP
4.1、修改Aspect
@Aspect //aspectj 框架的注解 标识当前类是一个切面
@Component //切面对象的创建权限依然交给spring容器
public class XmlAspect {
/**
* 前置通知
* @param jp
*/
public void before(JoinPoint jp) {
System.out.println("前置通知before:目标方法执行之前调用的通知");
String name = jp.getSignature().getName();
System.out.println("拦截的方法名称:" + name);
Object[] args = jp.getArgs();
System.out.println("拦截的方法个数:" + args.length);
for (Object arg : args) {
System.out.println("拦截的参数:" + arg);
}
}
/**
* 后置通知
* @param result
* @return
*/
public Object afterReturn(Object result){
System.out.println("后置通知afterReturn:目标方法执行之后被调用的通知,result="+result);
return result==null?"":result.toString();
}
/**
* 环绕通知
* @param pjp
* @return
* @throws Throwable
*/
public Object around(ProceedingJoinPoint pjp)throws Throwable {
System.out.println("环绕通知--目标方法执行之前");
Object proceed = pjp.proceed();
System.out.println("环绕通知--目标方法执行之后");
return proceed;
}
/**
* 异常通知
* value:切入点表达式,ex:异常信息
* @param jp
* @param ex
*/
public void exception(JoinPoint jp,Throwable ex){
//一般用于记录异常发生的时间、位置、原因
System.out.println("异常通知exception:目标方法执行出现异常时才会调用的通知,否则不会执行");
System.out.println(jp.getSignature().getName()+"方法出现异常,异常的信息:"+ex.getMessage());
}
/**
* 最终通知
*/
public void myFinally(){
System.out.println("最终通知after:无论是否出现异常都是最后执行的通知");
}
}
4.2、添加XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 包扫描 -->
<context:component-scan base-package="com.jsonliu.test.service,com.jsonliu.test.config" />
<!-- 开启aop注解使用 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!--
aop:aspectj-autoproxy 底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的,是基于AspectJ的注解适配自动代理生成器。
工作原理:aop:aspectj-autoproxy通过扫描找到@Aspect定义的切面,再由切面根据切点找到目标类的目标方法,再由通知类型找到切入的时间点。
-->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.jsonliu.test.service..*.update*(..))"/>
<aop:pointcut id="pt2" expression="execution(* com.jsonliu.test.service..*.add*(..))"/>
<aop:aspect ref="xmlAspect">
<aop:before method="before" pointcut="execution(* com.jsonliu.test.service..*.*(..))"/>
<aop:after-returning method="afterReturn" pointcut-ref="pt1" returning="result"/>
<aop:after-throwing method="exception" pointcut-ref="pt1" throwing="ex"/>
<aop:after method="myFinally" pointcut-ref="pt2" />
<aop:around method="around" pointcut-ref="pt1" />
</aop:aspect>
</aop:config>
</beans>
4.3、添加测试类
@Test
public void test2(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("xmlAop.xml");
TeamService teamService = (TeamService) applicationContext.getBean("teamService");
System.out.println("-----------------------------add---------------------------------");
teamService.add(1,"阿森纳");
System.out.println("-----------------------------update---------------------------------");
teamService.update(9999);
}