AspectJ是基于Java的AOP框架,在Spring2.0之后新增了对AspectJ切点表达式的支持。AspectJ在使用中支持注解和XML配置两种方法,在AspectJ1.5之后提供了@AspectJ
注解允许在Bean中直接定义切面。
引入Maven依赖
<!-- 引入spring基础jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
<!-- aop引用 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- 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.3.13.RELEASE</version>
</dependency>
AspectJ的基础是AOP所以需要在项目中引入AOP联盟的jar包
再在配置文件中添加AOP的约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
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">
<!-- 开启AspectJ自动代理 -->
<aop:aspectj-autoproxy />
</beans>
使用注解进行代理
为切面类添加@AspectJ
注解,可以将切面添加到AspectJ代理中,@AspectJ
提供了6种通知类型:
- 前置通知:
@Before
相当于BeforeAdvice
- 后置通知:
@AfterReturning
相当于AfterReturnimgAdvice
- 环绕通知:
@Around
相当于MethodInterceptor
- 异常抛出通知:
@AfterThrowimg
相当于ThrowAdvice
- 最终通知:
@After
无论是否发生异常都会执行该通知 - 引介通知:
@DeclareParents
相当于IntroductionInterceptor
使用execution函数可以定义切点的方法切入,语法:
execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>
示例:
- 匹配所有类的public方法
execution(public * *(..))
用*
表示通配符,第一个*
表示目标类的路径,第二个*
表示方法名称 - 匹配指定包下所有类的方法
execution( * com.spring.dao.*(..))
不包含子包中的类 execution( * com.spring.dao..*(..))
表示该包和子包下的所有类的方法- 匹配指定类的所有方法
execution( * com.spring.dao.UserDao.*(..))
- 匹配实现特定接口所有类的方法
execution( * com.spring.dao.UserDao+.*(..))
- 匹配所有指定的方法
execution( * save*(..))
JoinPoint对象可以获取切入点的信息以方便获得切入点的类名和方法名即参数。
调用getTarget()
方法可以获取切入点,再调用getClass()
获取切入点的类型,再调用getSimpleName()
获取类名
调用getSignature()
方法获取切入点方法,getName()
获取方法名
调用getArgs()
获取方法中的参数,返回的参数保存在数组中
前置通知
在切面类的方法中为需要执行前置通知的方法添加注解@Before()
参数需要添加execution()
函数填写需要的拦截的方法的表达式。例如:
package com.spring.aspectj.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AspectJDemo {
@Before("execution(* com.spring.aspectj.dao.UserDao.*(..))")
public void before(JoinPoint joinPoint) {
System.out.println("========" + joinPoint + "========");
}
}
为方法传入JoinPoint
参数,可以打印连接点的信息
测试类:
package com.spring.aspectj;
import com.spring.aspectj.dao.UserDao;
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 TestDemo {
@Resource(name = "userDao")
private UserDao userDao;
@Test
public void test1() {
userDao.save();
userDao.delete();
}
}
后置通知
如果目标方法需要进行后置增强,则需要在通知方法上增加@AfterReturning()
注解,如果目标方法包含返回值,可以在注解中添加returning
属性,值可以自己定义,这样就可以拦截目标对象中的返回值在通知方法中进行处理。例如:
package com.spring.aspectj.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AspectJDemo {
@AfterReturning(value = "execution(* com.spring.aspectj.dao.UserDao.delete(..))", returning = "o")
public void afterReturning(Object o) {
System.out.println("========后置通知传入参数:" + o + "========");
}
}
环绕通知
在需要使用环绕通知的增强方法上添加@Around()
注解,环绕通知可以说是前置通知和后置通知的集合体,可以在方法中传入ProceedingJoinPoint
类型的参数,调用proceed()
方法执行切入点的方法,返回一个Object类型,因此如果不执行该方法就可以阻断切入点方法的执行。
package com.spring.aspectj.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
@Aspect
public class AspectJDemo {
@Around(value = "execution(* com.spring.aspectj.dao.UserDao.update(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("========执行前处理========");
Object o = joinPoint.proceed();
System.out.println("========执行后处理========");
return o;
}
}
异常处理通知
当切入点在执行过程中产生异常时可以通过异常处理通知进行拦截,在增强方法上添加@AfterThrowing
注解,其中属性throwing
会捕获切入点的异常信息,并传入增强方法中进行逻辑处理。
package com.spring.aspectj.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
@Aspect
public class AspectJDemo {
@AfterThrowing(value = "execution(* com.spring.aspectj.dao.UserDao.select(..))", throwing = "e")
public void afterThrowing(Throwable e) {
System.out.println("========异常处理========");
System.out.println(e.getMessage());
}
}
最终通知
无论切入点是否产生异常都会被最终通知增强。
package com.spring.aspectj.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
@Aspect
public class AspectJDemo {
@After(value = "execution(* com.spring.aspectj.dao.UserDao.select(..))")
public void after() {
System.out.println("========最终处理========");
}
}
切点命名
如果某一个增强方法需要通知多个切点时,用以上方法就需要重复书写切点,会造成工作量大不易维护,所以AspectJ提供了@Pointcut
注解对多个切点进行管理。由于注解必然是要加载在属性或方法上,所以我们可以创建一个私有的没有返回值的切点方法用于指代各个切点。如果需要通知多个切点可以使用||
连接
package com.spring.aspectj.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
@Aspect
public class AspectJDemo {
@Pointcut("execution(* com.spring.aspectj.dao.UserDao.save(..))")
private void save() {}
@Pointcut("execution(* com.spring.aspectj.dao.UserDao.update(..))")
private void update() {}
@Pointcut("execution(* com.spring.aspectj.dao.UserDao.delete(..))")
private void delete() {}
@Pointcut("execution(* com.spring.aspectj.dao.UserDao.select(..))")
private void select() {}
@Before("save()||select()")
public void before(JoinPoint joinPoint) {
System.out.println("========前置通知连接点:" + joinPoint + "========");
}
@AfterReturning(value = "delete()", returning = "o")
public void afterReturning(Object o) {
System.out.println("========后置通知传入参数:" + o + "========");
}
@Around(value = "update()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("========执行前处理========");
Object o = joinPoint.proceed();
System.out.println("========执行后处理========");
return o;
}
@AfterThrowing(value = "select()", throwing = "e")
public void afterThrowing(Throwable e) {
System.out.println("========异常处理========");
System.out.println(e.getMessage());
}
@After(value = "select()")
public void after() {
System.out.println("========最终处理========");
}
}
使用XML进行代理
除了使用注解添加通知方法外还可以在XML配置文件中进行配置。在XML中添加<aop:config>
标签在标签中添加对应的切入点信息和通知信息
- 标签
<aop:pointcut>
注册所有需要被拦截的切入点,属性id
标识该切入点的名称,属性expression
书写切入点的表达式与注解方法相同 - 标签
<aop:aspect>
配置AOP的切面,可以一个通知对应多个切入点- 子标签
<aop:before>
配置前置通知 - 子标签
<aop:after-returning>
配置后置通知,属性returning
可以获取切入点传入的参数 - 子标签
<aop:around>
配置环绕通知 - 子标签
<aop:after-throwing>
配置异常处理通知,属性throwing
可以获取切入点抛出的异常 - 子标签
<aop:after>
配置最终通知
- 子标签
- 标签
<aop:advisor>
配置AOP切面,与<aop:aspect>
不同的是<aop:advisor>
只能一对一配置切入点和通知
示例:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
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">
<bean id="userDao" class="com.spring.aspectj.dao.UserDao"/>
<bean id="aspectJDemo" class="com.spring.aspectj.aop.AspectJDemo"/>
<bean id="aspectJDemo1" class="com.spring.aspectj.aop.AspectJDemo1"/>
<aop:config>
<aop:pointcut id="select" expression="execution(* com.spring.aspectj.dao.UserDao.select(..))"/>
<aop:pointcut id="save" expression="execution(* com.spring.aspectj.dao.UserDao.save(..))"/>
<aop:pointcut id="update" expression="execution(* com.spring.aspectj.dao.UserDao.update(..))"/>
<aop:pointcut id="delete" expression="execution(* com.spring.aspectj.dao.UserDao.delete(..))"/>
<aop:aspect ref="aspectJDemo1">
<aop:before method="before" pointcut-ref="save"/>
<aop:after-returning method="afterReturning" pointcut-ref="delete" returning="o"/>
<aop:around method="around" pointcut-ref="update"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="select" throwing="e"/>
<aop:after method="after" pointcut-ref="select"/>
</aop:aspect>
</aop:config>
</beans>