目录
一、AspectJ基本介绍
Aspectj是一个基于Java语言的AOP框架,由于传统AOP开发过于麻烦,Spring2.0后将其添加到自己体系中。
使用AspectJ需要导入Spring AOP 和 AspectJ相关的jar包
需要在spring包的基础上再添加如下jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>com.xyz.spring</groupId>
<artifactId>spring_aspect</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
applicationContext.xml配置文件
使用<aop:aspectj-autoproxy/>就可以使用AspectJ的注解
<?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">
<!--开启AspectJ的注解开发,自动代理=====================-->
<aop:aspectj-autoproxy/>
</beans>
二、@AspectJ的通知类型
@Before前置通知,在目标方法执行之前,相当于Beforeadvice
@Afterreturning后置通知,在目标方法执行之后,相当于Afterreturningadvice
@Around环绕通知,在目标方法执行前和执行后,相当于Methodinterceptor
@Afterthrowing异常抛出通知,相当于Throwadvice
@After最终final通知,不管是否异常,该通知都会执行
三、在通知中通过value属性定义切点
通过execution函数,可以定义切点的方法切入
语法:execution(<访问修饰符><返回类型><方法名>(参数)<异常>)
其中访问修饰符可以省略
举例:
1.匹配所有类public方法 :execution(public * *(..))
第一个public是访问修饰符,第一个*是返回类型任意,第二个*是任意方法名,(..)标识任意参数
2.匹配指定包下所有类方法:execution(* com.xyz.dao.*(..))
第一个*代表任意的返回类型,com.xyz.dao.*(..)代表dao包下的所有类的任意方法,但不包含子包
execution(* com.xyz.dao..*(..)) 这种写法dao后面两"."是包含dao包及其子包
3.匹配实现了特定接口的所有类方法 :execution(* com.xyz.dao.UserDao+.*(..))
4.匹配所有save开头的方法:execution(* save*(..))
四、使用案例
1.首先创建目标类以及切面类
创建目标类
public class ProductDao {
public void save(){
System.out.println("保存商品");
}
public void update(){
System.out.println("修改商品");
}
public void delete(){
System.out.println("删除商品");
}
public void findOne(){
System.out.println("查询某个商品");
}
public void findAll(){
System.out.println("查询所有商品");
}
}
创建创建切面类(需使用@Aspect进行标识)
@Aspect
public class MyAspectAnno {
public void before(){
System.out.println("前置通知");
}
}
在配置文件中将类配置
<?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">
<!--开启AspectJ的注解开发,自动代理=====================-->
<aop:aspectj-autoproxy/>
<!--目标类-->
<bean id="ProductDao" class="com.xyz.aspectj.demo1.ProductDao"/>
<!--切面类-->
<bean class="com.xyz.aspectj.demo1.MyAspectAnno"/>
</beans>
2.使用AspectJ进行AOP开发
①:@Before前置通知
@Aspect
public class MyAspectAnno {
@Before(value="execution(* com.xyz.aspectj.demo1.ProductDao.*(..))")
public void before(){
System.out.println("前置通知");
}
}
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Test {
@Resource(name = "ProductDao")
private ProductDao productDao;
@org.junit.Test
public void demo1(){
productDao.save();
productDao.delete();
productDao.findAll();
productDao.findOne();
productDao.update();
}
}
可以看到在每个方法前都执行了@Before指定的前置通知
②:@AfterReturing前置通知
在切面类中加上一个@AfterReturining后置通知,并且仅对ProductDao类的update方法起作用
@Aspect
public class MyAspectAnno {
@Before(value="execution(* com.xyz.aspectj.demo1.ProductDao.*(..))")
public void before(){
System.out.println("前置通知");
}
@AfterReturning(value = "execution(* com.xyz.aspectj.demo1.ProductDao.update(..))")
public void after(){
System.out.println("后置通知!!");
}
}
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Test {
@Resource(name = "ProductDao")
private ProductDao productDao;
@org.junit.Test
public void demo1(){
productDao.save();
productDao.delete();
productDao.findAll();
productDao.findOne();
productDao.update();
}
}
后置通知还可以得到方法的返回值,此时将ProductDao方法的update方法改成有返回值的
修改切面类,在@AfterReturning中新增了一个参数为returning接收返回值,随意命名,但要保证与下面after方法中参数名一直
@Aspect
public class MyAspectAnno {
@Before(value="execution(* com.xyz.aspectj.demo1.ProductDao.*(..))")
public void before(){
System.out.println("前置通知");
}
@AfterReturning(value = "execution(* com.xyz.aspectj.demo1.ProductDao.update(..))",returning = "result")
public void after(Object result){
System.out.println("后置通知!!"+result);
}
}
可以看到测试结果,表明收到了返回值
③:@Around环绕通知
around()方法的返回值就是目标方法的返回值,使用Object保证任意返回值都OK
around()方法中的ProceedingJoinPoint参数用来调用目标方法执行,当joinPoint.proceed()就相当于目标方法的执行
@Aspect
public class MyAspectAnno {
@Around(value = "execution(* com.xyz.aspectj.demo1.ProductDao.delete(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前通知");
Object proceed = joinPoint.proceed();
System.out.println("环绕后通知");
return proceed;
}
}
将目标方法修改为下图
测试结果
如果joinPoint.proceed()方法不执行,则目标方法也不会执行,这时就可以在这里加一下业务上的判断
④:@AfterThrowing异常抛出通知
使用该注解在目标方法发生异常时会触发,可以用于事务回滚。
⑤:@After最终通知
相当于try catch的finally,无论目标方法如何都会执行
五、使用@Pointcut 为切点命名
在案例4可以看出,添加切点时优点麻烦,每次都要写长长的一串,这时就可以使用@Pointcut简化
举例:下面是案例4的代码,其中execution(* com.xyz.aspectj.demo1.ProductDao.delete(..))表示对包下的delete方法进行环绕通知,如果此时该delete方法有多个通知就可以采用@Pointcut进行别名
@Aspect
public class MyAspectAnno {
@Around(value = "execution(* com.xyz.aspectj.demo1.ProductDao.delete(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前通知");
Object proceed = joinPoint.proceed();
System.out.println("环绕后通知");
return proceed;
}
}
下图是使用了@Pointcut将execution(* com.xyz.aspectj.demo1.ProductDao.delete(..))直接用myPointcut1()使用
@Aspect
public class MyAspectAnno {
@Around(value = "myPointcut1()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前通知");
Object proceed = joinPoint.proceed();
System.out.println("环绕后通知");
return proceed;
}
@Pointcut(value = "execution(* com.xyz.aspectj.demo1.ProductDao.delete(..))")
private void myPointcut1(){}
}