AspectJ 是一个基于Java语言的AOP框架,Spring 2.0以后新增了对AspectJ切点表达式支持. @AspectJ 是AspectJ 1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面. 新版本Spring框架,建议使用AspectJ方式来开发AOP 使用AspectJ需要导入Spring AOP和AsprctJ相关jar包 使用AspectJ 实现AOP 有两种方式: 注解方式 XML方式
基于AspectJ的注解AOP开发
要想使用AspectJ 需要先添加AspectJ依赖
<!-- 引入 AspectJ 依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
然后在applicationContext.xml中进行配置
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启 AspectJ的注解开发 自动代理-->
<aop:aspectj-autoproxy/>
<!-- 配置bean 也可以用Spring ioc的注解方式去实现 -->
<bean id="productDao" class="com.prim.aspectj.ProductDao"/>
<!-- 配置切面类 -->
<bean class="com.prim.aspectj.MyAspectAnno"/>
</beans>
定义切面类使用@Aspect注解
/**
* 切面类
* 注意将切面定义到applicationContext
*
* @author prim
*/
@Aspect
public class MyAspectAnno {}
@AspectJ 提供不同的通知类型
- @Before 前置通知,相当于BeforeAdvice
- @AfterReturning 后置通知,相当于AfterReturningAdvice
- @Around 环绕通知,相当于MethodInterceptor
- @AfterThrowing 异常抛出通知,相当于ThrowAdvice
- @After 最终final通知,不管是否异常,该通知都会执行
- @DeclareParents 引介通知,相当于IntroductionInterceptor(可以先不用掌握)
在通知中通过value属性定义切点
- 通过execution函数,可以定义切点的方法切入
- 语法:
- execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
- 例如:匹配所有类:excution(public (..)) 任意返回类型 任意方法 任意参数 只要是public方法都会执行
- 匹配指定包下所有类方法 execution( com.imooc.dao.(..)) 不包含子包
- elecution( com.imooc.dao..(..)) ..* 表示包、子孙包下所有类
- 匹配指定类所有方法 execution( com.imooc.service.UserService.(..))
- 匹配实现特定接口所有类方法 execution( com.imooc.dao.GenericDAO+.(..)) GenericDAO+ 实现表示接口或抽象类GenericDAO 的所有类
- 匹配所有save开头的方法 execution( save(..))
为目标类,定义切面类. 通过例子来进行理解
通过@Aspect 来定义切面类,然后在切面类的方法加入通知注解.
前置通知 @Before
- 可以在方法中传入JoinPoint对象,用来获得切点信息.
/**
* 前置通知
* value 定义切入点:在save的时候进行校验
* JoinPoint 获得切点信息
*/
@Before(value = "execution(* com.prim.aspectj.ProductDao.save(..))")
public void before(JoinPoint joinPoint) {
System.out.println("MyAspectAnno.before 权限校验 ===================" + joinPoint);
}
后置通知 @AfterReturning ,可以通过returning属性得到切入方法的返回值
/**
* 后置通知
* returning 获得切入方法的返回值
*/
@AfterReturning(value = "execution(* com.prim.aspectj.ProductDao.update(..))", returning = "result")
public void afterReturning(Object result) {
System.out.println("MyAspectAnno.afterReturning 后置通知 ===================" + result);
}
环绕通知 @Around
- around方法的返回值就是目标代理方法执行返回值
- 参数为ProceedingJoinPoint可以调用拦截目标方法执行
如果不调用ProceedingJoinPoint的proceed方法,那么目标方法就被拦截了
/**
* 环绕通知
* @param joinPoint
* @return
* @throws Throwable
*/
@Around(value = "execution(* com.prim.aspectj.ProductDao.delete(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("MyAspectAnno.around 环绕前通知=======================");
//执行目标方法 如果不调用目标方法就会不执行了
Object proceed = joinPoint.proceed();
System.out.println("MyAspectAnno.around 环绕后通知======================");
return proceed;
}
异常抛出通知 @AfterThrowing
- 通过设置throwing属性,可以设置发生异常对象参数
@AfterThrowing(value = "execution(* com.prim.aspectj.ProductDao.findAll(..))", throwing = "e")
public void afterThrowing(Throwable e) {
System.out.println("MyAspectAnno.afterThrowing 异常抛出通知" + e.getMessage());
}
最终通知@After
- 无论是否出现异常,最终通知总是会被执行
@After(value = "execution(* com.prim.aspectj.ProductDao.findOne(..))")
public void after() {
System.out.println("MyAspectAnno.after 最终通知===============================");
}
下面测试一下,上述写的几个通知
@Resource(name = "productDao")
private ProductDao productDao;
@Test
public void test1() {
productDao.save();
productDao.delete();
productDao.update();
productDao.findAll();
productDao.findOne();
}
运行结果如下:
在上述的使用过程中,大家有没有觉得很麻烦,比如save()增强前置通知,突然有个需求需要对save方法要求增强后置通知,那我们还得需要重新写一个方法标记后置通知,一旦多起来非常难以维护.通过@Pointcut 为切点命名,统一进行管理
- 每个通知内定义切点,会造成工作量大,不易维护,对于重复的切点,可以使用@Pointcut进行定义
- 切点方法:private void 无参数方法,方法名为切点名
- 但通知多个切点时,可以通过||连接
修改如下,我们可以将定义好的切点随意放到定义好的通知方法中,便于管理
/**
* 前置通知
* value 定义切入点:在save的时候进行校验
* JoinPoint 获得切点信息
*/
@Before(value = "myPointcut1()||myPointcut2()")
public void before(JoinPoint joinPoint) {
System.out.println("MyAspectAnno.before 权限校验 ===================" + joinPoint);
}
/**
* 后置通知
* returning 获得切入方法的返回值
*/
@AfterReturning(value = "myPointcut2()", returning = "result")
public void afterReturning(Object result) {
System.out.println("MyAspectAnno.afterReturning 后置通知 ===================" + result);
}
@Around(value = "myPointcut3()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("MyAspectAnno.around 环绕前通知=======================");
//执行目标方法 如果不调用目标方法就会不执行了
Object proceed = joinPoint.proceed();
System.out.println("MyAspectAnno.around 环绕后通知======================");
return proceed;
}
@AfterThrowing(value = "myPointcut4()", throwing = "e")
public void afterThrowing(Throwable e) {
System.out.println("MyAspectAnno.afterThrowing 异常抛出通知" + e.getMessage());
}
@After(value = "myPointcut5()")
public void after() {
System.out.println("MyAspectAnno.after 最终通知===============================");
}
/**
* 定义一个切点 方便维护
*/
@Pointcut(value = "execution(* com.prim.aspectj.ProductDao.save(..))")
private void myPointcut1() {
}
@Pointcut(value = "execution(* com.prim.aspectj.ProductDao.update(..))")
private void myPointcut2() {
}
@Pointcut(value = "execution(* com.prim.aspectj.ProductDao.delete(..))")
private void myPointcut3() {
}
@Pointcut(value = "execution(* com.prim.aspectj.ProductDao.findAll(..))")
private void myPointcut4() {
}
@Pointcut(value = "execution(* com.prim.aspectj.ProductDao.findOne(..))")
private void myPointcut5() {
}
基于AspectJ的XML方式的AOP
使用xml配置切面
- 编写切面类
/**
* 定义切面类 配置到配置文件中
*/
public class MyAspectXml {
/**
* 前置通知
*/
public void before(JoinPoint joinPoint) {
System.out.println("MyAspectXml.before XML方式 前置通知================" + joinPoint);
}
/**
* 后置通知
*/
public void afterReturning(Object result) {
System.out.println("MyAspectXml.afterReturning XML方式 后置通知====================" + result);
}
/**
* 环绕通知
*/
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("MyAspectXml.around XML方式环绕通知之前================");
Object proceed = joinPoint.proceed();
System.out.println("MyAspectXml.around XML方式环绕通知之后================");
return proceed;
}
public void afterThrowing(Throwable e) {
System.out.println("MyAspectXml.afterThrowing XML方式异常抛出通知:" + e.getMessage());
}
public void after(){
System.out.println("MyAspectXml.after XML方式最终通知");
}
}
- xml切面类配置applicationContext.xml中
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- XML配置方式完成AOP开发 -->
<bean id="customerDao" class="com.prim.aspectj.xml.CustomerDaoImpl"/>
<!-- 配置切面类 -->
<bean id="myAspectXml" class="com.prim.aspectj.xml.MyAspectXml"/>
</bean>
- 配置aop增强
<!-- aop 的配置 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut expression="execution(* com.prim.aspectj.xml.CustomerDao.save(..))" id="pointcut1"/>
<aop:pointcut expression="execution(* com.prim.aspectj.xml.CustomerDao.update(..))" id="pointcut2"/>
<aop:pointcut expression="execution(* com.prim.aspectj.xml.CustomerDao.delete(..))" id="pointcut3"/>
<aop:pointcut expression="execution(* com.prim.aspectj.xml.CustomerDao.findOne(..))" id="pointcut4"/>
<aop:pointcut expression="execution(* com.prim.aspectj.xml.CustomerDao.findAll(..))" id="pointcut5"/>
<!-- 告知哪个类是切面 配置切面类 -->
<aop:aspect ref="myAspectXml">
<!-- 配置切入点的通知 -->
<!-- 前置通知 method 设置切入类的方法 前置通知应用到的切入点 pointcut-ref -->
<aop:before method="before" pointcut-ref="pointcut1"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut2" returning="result"/>
<aop:around method="around" pointcut-ref="pointcut3"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="e"/>
<aop:after method="after" pointcut-ref="pointcut5"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut3" throwing="e"/>
<!-- 前置通知添加其他的切入点 -->
<aop:before method="before" pointcut-ref="pointcut2"/>
</aop:aspect>
</aop:config>
@Resource(name = "customerDao")
public CustomerDao customerDao;
@Test
public void test() {
customerDao.save();
customerDao.update();
customerDao.delete();
customerDao.findOne();
customerDao.findAll();
}
运行结果:
可以看出,XML方式和注解的方式没有多大的区别,XML的方式便于管理和维护、注解的方式便于开发.
这两种开发方式,在实际开发中都会使用基于AspectJ的AOP的开发方式,而很少会使用Spring 传统AOP的方式. 在使用AspectJ AOP 可能有的企业会用到注解的方式,也可能会用到xml的开发方式.