前文 中,已经讲解了Spring传统的AOP开发,但在实际开发中,我们都是使用AspectJ进行AOP开发。
AspectJ 简介
-
AspectJ 是一个基于Java语言的独立的AOP框架。
-
在没有AspectJ 之前,Spring在进行AOP开发的时候,都是使用 前文 中讲过的传统的AOP开发方式,而有了AspectJ 之后,就使得Spring的AOP开发变的简单灵活,大大提高了程序的开发效率 和 后期的维护效率
-
Spring 2.0 后,新增了对AspectJ 切点表达式支持
-
AspectJ 1.5 后,新增了@AspectJ 注解开发功能,通过JDK5 技术,可以直接在Bean类中定义切面
-
新版本Spring框架,建议使用灵活简单的AspectJ 方式 取代 繁杂的Spring传统AOP开发方式
Spring 使用AspectJ进行AOP开发所需jar包:
-
4个spring基本jar包
-
spring-core-4.3.22.RELEASE.jar
-
spring-beans-4.3.22.RELEASE.jar
-
spring-expression-4.3.22.RELEASE.jar
-
spring-context-4.3.22.RELEASE.jar
-
-
spring aop相关jar包
spring-aop-4.3.22.RELEASE.jar
aopalliance-1.0.jar
,aop联盟
-
aspectj 相关jar包
aspectjweaver-1.9.2.jar
-
spring整合aspectj 相关jar包
spring-aspects-4.3.22.RELEASE.jar
当然,为了方便测试,你可以添加 junit-4.12.jar
和 spring-test-4.3.22.RELEASE.jar
AspectJ 定义切点的方式
前文介绍的Spring传统AOP开发中,我们是通过pattern正则表达式 或 默认切点方式来定义切点;而在AspectJ中,则是通过execution函数来定义切入点。
通过execution函数构建的切入点表达式:
例如:
匹配com.cd4356.spring_aspectj.annotation包及其子孙包下,的任意类,的任意方法,参数可以是任意参数
execution(* com.cd4356.spring_aspectj.annotation..*.*(..))
匹配指定包下所有类方法
execution(* com.cd4356.spring_aspectj.annotation.*.*(..))
匹配指定UserService类的方法
execution(* com.cd4356.spring_aspectj.annotation.UserService.*(..))
匹配指定UserService类的save方法
execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))
匹配指定UserService类的以save开头的方法
execution(* com.cd4356.spring_aspectj.annotation.UserService.save*(..))
匹配所有以save开头的方法
execution(* save*(..))
匹配所有不以save开头的方法
!execution(* save*(..))
Spring使用AspectJ实现AOP的两种方式
-
注解方式
-
XML方式
@AspectJ 提供的通知类型
-
@Before(
前置通知
),相当于BeforeAdvice -
@AfterReturning(
后置通知
),相当于AfterReturningAdvice,拦截的目标方法异常,该通知不起作用,对应的方法不会执行 -
@Around(
环绕通知
),相当于MethodIntercepter -
@AfterThrowing(
异常抛出通知
),相当于ThrowAdvice,拦截的目标方法无异常,该通知不起作用 -
@After(
最终通知
),不管是否发生异常,该通知都会执行,相当于 finally 代码块
通知执行的现后次序
需求:对save方法进行前置通知、对delete方法进行后置通知、对update方法进行环绕通知、对find方法进行异常抛出通知、对findAll方法进行最终通知

定义目标类
package com.cd4356.spring_aspectj.annotation;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserService {
public void save(){
System.out.println("保存用户");
}
public String delete(){
System.out.println("删除用户");
return "我是返回值!";
}
public void update(){
System.out.println("修改用户");
}
public void find(){
System.out.println("查找用户");
}
public void findAll(){
System.out.println("查找所有用户");
}
}
定义切面类,别忘了在类上添加@Aspect注解
( 细看,精华都在该切面类中了,内含注释说明 )
package com.cd4356.spring_aspectj.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AspectAnno {
/**
* 前置通知
* @param joinPoint , 通过在方法中传入的JoinPoint对象,用来获取切点(目标方法)信息
*/
@Before(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))")
public void before(JoinPoint joinPoint){
//打印切点所属类名
System.out.println(joinPoint.getTarget().getClass().getSimpleName());
//打印切点对应方法名
System.out.println(joinPoint.getSignature().getName());
//打印切点对应方法的第一个参数
//System.out.println(joinPoint.getArgs()[0]);
System.out.println(". . . 前置通知 . . . ");
}
/**
* 后置通知
* @param returning , 通过returning属性(也可以不定义该属性),可以获取目标方法的返回值,作为参数传入后置方法中
*/
@AfterReturning(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.delete(..))",returning = "returning")
public void afterReturning(Object returning){
System.out.println(". . . 后置通知 . . . "+returning);
}
/**
* 环绕通知
* @param joinPoint , 参数为ProceedingJoinPoint,用来调用拦截的目标方法执行
* @return , around方法的返回值,就是目标代理方法执行返回值
* @throws Throwable , 目标方法有异常,则只执行环绕前通知,不执行环绕后通知
*/
@Around(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.update(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(". . . 环绕前通知 . . . ");
//注意,如果不调用ProceedingJoinPoint的proceed方法,那么目标方法就被拦截了,不会执行该目标方法
Object obj = joinPoint.proceed(); //执行目标方法
System.out.println(". . . 环绕后通知 . . . ");
return obj;
}
/**
* 异常抛出通知
* @param e , 通过throwing属性,获取拦截目标方法的异常
*/
@AfterThrowing(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.find(..))",throwing = "e")
public void afterThrowing(Throwable e){
System.out.println(". . . 异常抛出通知 . . . "+e.getMessage());
}
/**
* 最终通知
*/
@After(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.findAll(..))")
public void after(){
System.out.println(". . . 最终通知 . . . ");
}
}
在resource目录下,新建spring目录,用来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"
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/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">
<!--开启AspectJ的注解开发,自动代理-->
<aop:aspectj-autoproxy/>
<!--开启全局扫描-->
<context:component-scan base-package="com.cd4356.spring_aspectj.annotation"/>
</beans>
定义测试类
package com.cd4356.spring_aspectj.annotation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
//加载spring配置文件
@ContextConfiguration("classpath:spring/applicationContext1.xml")
public class SpringDemo {
@Autowired
private UserService userService;
@Test //使用了@Test注解的方法作用相当于main方法
public void demo(){
userService.save();
userService.update();
userService.delete();
userService.find();
userService.findAll();
}
}
看一下运行结果
通过@Pointcut 为切点命名
前面已经介绍了AspectJ的通知类型,并通过案例进行了演示。
回顾一下,前面案例中切面类的代码,需求较为简单,都是一个通知作用再一个方法(切点)中,这样看起来没有什么不足之处。
但如果有多个类型的通知作用再同一个方法上,再使用前面案例中的这种方式就会显得很麻烦,而且维护量也会很大。
针对这种情况,AspectJ提供了@Pointcut注解,为切点名,进行统一管理
@Pointcut(切入点
)
-
再每个通知内定义切点,会造成工作量大,不易维护,所以对于重复使用的切点,可以使用@Pointcut 进行定义
-
切点方法格式:private void 切点名(){} ,( 因为切点方法仅在本切面类中使用,且不会有返回值,方法体中也不需要任何内容,切点方法名切点名一致方便阅读 )
-
当通知多个切点时,可以使用
||
进行连接
下面通过案例来对@Pointcut进一步了解,取前面案例,改变需求( 对save和delete方法使用,前置通知、后置通知和异常抛出通知 )(提示,只修改切面类的代码)
不使用@Pointcut的做法,直接在每通知内定义切点
package com.cd4356.spring_aspectj.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AspectAnno {
@Before(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))"
+ "||execution(* com.cd4356.spring_aspectj.annotation.UserService.delete(..))")
public void before(){
System.out.println(". . . 前置通知 . . . ");
}
@AfterReturning(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))"
+ "||execution(* com.cd4356.spring_aspectj.annotation.UserService.delete(..))")
public void afterReturning(){
System.out.println(". . . 后置通知 . . . ");
}
@AfterThrowing(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))"
+ "||execution(* com.cd4356.spring_aspectj.annotation.UserService.delete(..))")
public void afterThrowing(){
System.out.println(". . . 异常抛出通知 . . . ");
}
}
使用@Pointcut的做法,将每个切点独立出来统一管理
package com.cd4356.spring_aspectj.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AspectAnno {
@Before(value = "save()||delete()")
public void before(){
System.out.println(". . . 前置通知 . . . ");
}
@AfterReturning(value = "save()||delete()")
public void afterReturning(){
System.out.println(". . . 后置通知 . . . ");
}
@AfterThrowing(value = "save()||delete()")
public void afterThrowing(){
System.out.println(". . . 异常抛出通知 . . . ");
}
@Pointcut(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))")
private void save(){}
@Pointcut(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.delete(..))")
private void delete(){}
}
对比这两种方式,是不是发现使用@Pointcut,代码量降低了,而且看起开显得更加整洁,没有那么辣眼睛。
需求:对save方法和update方法,进行前置通知和后置通知
(该案例以实现了接口的类进行演示)

package com.cd4356.spring_aspectj.xml;
public interface UserService {
void save();
void delete();
void update();
void find();
void findAll();
}
package com.cd4356.spring_aspectj.xml;
public class UserServiceImpl implements UserService {
public void save(){
System.out.println("保存用户");
}
public void delete(){
System.out.println("删除用户");
}
public void update(){
System.out.println("修改用户");
}
public void find(){
System.out.println("查找用户");
}
public void findAll(){
System.out.println("查找所有用户");
}
}
定义切面类,类中定义了5种通知类型的方法
package com.cd4356.spring_aspectj.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class AspectXml {
/**
* 前置通知
* @param joinPoint , 通过在方法中传入的JoinPoint对象,用来获取切点(目标方法)信息
*/
public void before(JoinPoint joinPoint){
//打印切点所属类名
System.out.println(joinPoint.getTarget().getClass().getSimpleName());
//打印切点对应方法名
System.out.println(joinPoint.getSignature().getName());
//打印切点对应方法的第一个参数
//System.out.println(joinPoint.getArgs()[0]);
System.out.println(". . . 前置通知 . . . ");
}
/**
* 后置通知
* @param obj, 通过returning属性(也可以不定义该属性),可以获取目标方法的返回值,作为参数传入后置方法中
*/
public void afterReturning(Object obj){
System.out.println(". . . 后置通知 . . . "+obj);
}
/**
* 环绕通知
* @param joinPoint , 参数为ProceedingJoinPoint,用来调用拦截的目标方法执行
* @return , around方法的返回值,就是目标代理方法执行返回值
* @throws Throwable , 目标方法有异常,则只执行环绕前通知,不执行环绕后通知
*/
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(". . . 环绕前通知 . . . ");
Object obj = joinPoint.proceed();
System.out.println(". . . 环绕后通知 . . . ");
return obj;
}
/**
* 异常抛出通知
* @param e , 通过throwing属性,获取拦截目标方法的异常
*/
public void afterThrowing(Throwable e){
System.out.println(". . . 异常抛出通知 . . . "+e.getMessage());
}
/**
* 最终通知
*/
public void after(){
System.out.println(". . . 最终通知 . . . ");
}
}
AspectJ基于XML的方式重点了解 < aop:config > 标签
< aop:config >标签有三个子标签:
- < aop:pointcut /> ,用来配置切入点
- < aop:advisor /> ,用来配置切面(用来配置一个切入点和一个通知的切面组合)
- < aop:aspect /> , 用来配置切面(可以用来一个切入点和一个通知的切面组合,也可用来配置多个切入点和多个通知的切面组合),开发中一般使用该标签
事大数
< aop:aspect />标签中有与各种类型通知相对应的子标签
- <aop:before /> ,用来配置前置通知
- <aop:after-returning /> ,用来配置后置通知
- <aop:around /> ,用来配置环绕置通知
- <aop:after-throwing /> ,用来配置异常抛出通知
- <aop:after /> ,用来配置最终通知
<?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="userService" class="com.cd4356.spring_aspectj.xml.UserServiceImpl"/>
<!--配置通知类-->
<bean id="aspectXml" class="com.cd4356.spring_aspectj.xml.AspectXml"/>
<!--配置aop完成增强-->
<aop:config>
<!--配置切入点: 哪些类的哪些方法需要增强-->
<aop:pointcut id="save" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.save(..))"/>
<aop:pointcut id="update" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.update(..))"/>
<!--配置切面-->
<aop:aspect ref="aspectXml">
<!--配置前置通知-->
<aop:before method="before" pointcut-ref="save"/>
<!--配置后置通知-->
<aop:after-returning method="afterReturning" pointcut-ref="save" returning="obj"/>
<!--配置前置通知-->
<aop:before method="before" pointcut-ref="update"/>
<!--配置后置通知-->
<aop:after-returning method="afterReturning" pointcut-ref="update" returning="obj"/>
</aop:aspect>
</aop:config>
</beans>
package com.cd4356.spring_aspectj.xml;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
//加载spring配置文件
@ContextConfiguration("classpath:spring/applicationContext2.xml")
public class SpringDemo {
@Autowired
private UserService userService;
@Test //使用了@Test注解的方法作用相当于main方法
public void demo(){
userService.save();
userService.update();
userService.delete();
userService.find();
userService.findAll();
}
}
运行效果:
前面案例我们是,对save方法和update方法,进行前置通知和后置通知。下面改变需求:对每个方法使用一种不同类型的增强(通知)
<?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="userService" class="com.cd4356.spring_aspectj.xml.UserServiceImpl"/>
<!--配置通知类-->
<bean id="aspectXml" class="com.cd4356.spring_aspectj.xml.AspectXml"/>
<!--配置aop完成增强-->
<aop:config>
<!--配置切入点: 哪些类的哪些方法需要增强-->
<aop:pointcut id="save" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.save(..))"/>
<aop:pointcut id="update" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.update(..))"/>
<aop:pointcut id="delete" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.delete(..))"/>
<aop:pointcut id="find" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.find(..))"/>
<aop:pointcut id="findAll" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.findAll(..))"/>
<!--配置切面-->
<aop:aspect ref="aspectXml">
<!--对save方法使用前置增强-->
<aop:before method="before" pointcut-ref="save"/>
<!--对update方法使用后置增强,后置通知方法中若没有定义参数,则无需使用returning属性-->
<aop:after-returning method="afterReturning" pointcut-ref="update" returning="obj"/>
<!--对delete方法使用环绕增强-->
<aop:around method="around" pointcut-ref="delete"/>
<!--对find方法使用异常抛出增强-->
<aop:after-throwing method="afterThrowing" pointcut-ref="find" throwing="e"/>
<!--对findAll方法使用最终增强-->
<aop:after method="after" pointcut-ref="findAll"/>
</aop:aspect>
</aop:config>
</beans>
运行效果:
案例源码:
链接:https://pan.baidu.com/s/1MUJZyPG2aMp41rV-oFX3Uw
提取码:wthk