JAVA框架-Spring03(AspectJ)
基于AspectJ的AOP
AspectJ是一个基于Java语言的AOP框架,Spring2.0以后新增了对AspectJ切点表达式支持。因为Spring1.0的时候Aspectj还未出现;
AspectJ1.5中新增了对注解的支持,允许直接在Bean类中定义切面。新版本的Spring框架建
议我们都使用AspectJ方式来开发AOP,并提供了非常灵活且强大的切点表达式 ;
当然无论使用Spring自己的AOP还是AspectJ相关的概念都是相同的;
比如通知类型:
首先需要创建一个普通类作为通知类,@AspectJ用于标注其属于通知类,见下面案例:
- @Before 前置通知 在原始方法执行前执行
- @AfterReturning 后置通知 在原始方法执行后执行
- @Around 环绕通知 彻底拦截原始方法的执行,执行前后都可以增加逻辑,也可以不执行原始方法
- @AfterThrowing抛出通知,执行原始方法出现异常时执行
- @After 最终final通知,不管是否异常,原始方法调用后都会执行
- @DeclareParents 引介通知,相当于IntroductionInterceptor (了解即可)
配置
导入依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
定义切点
通过execution函数来定义切点
语法:execution(访问修饰符 返回类型 方法名 参数 异常)
比如:
匹配所有类public方法:execution(public * *(..))
第一个*表示返回值 ..表示任意个任意类型的参数
匹配指定包下所有类所有方法: execution(* cn.xxx.dao.*.*(..))
第一个想*表示忽略权限和返回值类型
匹配指定包下所有类所有方法:execution(* cn.xxx.dao..*(..))
包含子包
匹配指定类所有方法: execution(* cn.xxx.service.UserService.*(..))
匹配实现特定接口所有类方法 : execution(* cn.xxx.dao.GenericDAO+.*(..))
匹配所有save开头的方法: execution(* save*(..))
匹配某个指定的方法: execution(* com.yh.dao.StudentService.save(..))
前置通知
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.2.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.2.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
xml需要添加aop名称空间及xsd:
<?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/>
<!-- 目标-->
<bean id="personDao" class="demo1.PersonDao"/>
<!-- 切面-->
<bean class="demo1.MyAspect"/>
</beans>
测试代码:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Test1 {
@Autowired
PersonDao personDao;
@Test
public void test(){
personDao.delete();
personDao.update();
}
}
切面类
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MyAspect {
//表示PersonDao下所有方法都作为切点
@Before(value = "execution(* demo1.PersonDao.*(..))")
public void beforeAdvice(){
System.out.println("before code run.....");
}
}
当我们需要获取切点信息(被增强的代码)时,可以在通知添加参数,像下面这样
@Aspect
public class MyAspect {
@Before(value = "execution(* demo1.PersonDao.*(..))")
public void beforeAdvice2(JoinPoint point){
System.out.println("before code run2....." + point);
}
}
后置通知
//当需要获取原始方法的返回值时可以在注解中添加returning参数来指定参数名 Aspectj会自动将返回值放到参数中
@AfterReturning(value = "execution(* demo1.PersonDao.delete(..))",returning = "result")
public void afterAdvice(Object result){
System.out.println("删除方法执行后 ..... 返回值为:"+ result);
}
后置通知可以获取目标方法的返回值
环绕通知
@Around(value = "execution(* demo1.PersonDao.insert(..))")
public void aroundAdvice(ProceedingJoinPoint point) throws Throwable {
//code............
System.out.println("环绕前置..");
//执行原始方法 __当需要获取返回值时可以声明变量接收
Object result = point.proceed();
System.out.println("原始方法返回值: "+result);
//code............
System.out.println("环绕后置..");
}java
环绕通知与其他通知最大的区别在于环绕通知可以控制是否调用原始方法
注意:参数类型必须为ProceedingJoinPoint,否则 无法执行原始方法,
最终通知
@After(value = "execution(* *delete(..))")
public void afterRun(){
System.out.println("最终");
}
最终通知叫做after 即调用原始方法之后执行无论原始方法中是否出现异常
而后置叫做afterReturning表示在成功返回后才会执行执行
使用xml
XML配置所需的jar 以及各个对象之间的依赖关以及表达式的写法都是一样的,仅仅是换种方式来写而已;
xml:
<!--目标-->
<bean id="studentDao" class="demo2.StudentDao"/>
<!--通知-->
<bean id="advices" class="demo2.XMLAdvice"/>
<!--织入信息-->
<aop:config>
<!--切点定义-->
<aop:pointcut id="select" expression="execution(* demo2.StudentDao.select(..))"/>
<!--切面定义-->
<aop:aspect ref="advices">
<aop:before method="before" pointcut-ref="select"/>
<aop:after-returning method="afterReturning" pointcut-ref="select" returning="result"/>
<aop:after method="after" pointcut-ref="select" />
<aop:after-throwing method="exception" pointcut-ref="select" throwing="e"/>
<aop:around method="around" pointcut-ref="select"/>
</aop:aspect>
<!--入侵式通知 即通知需要实现指定接口 与aop:aspect选其一 -->
<aop:advisor advice-ref="advice2" pointcut-ref="select"/>
</aop:config>
<!--入侵式通知Bean-->
<bean id="advice2" class="demo2.XMLAdvice2"/>
通知类:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.JoinPoint;
public class XMLAdvice {
public void before(JoinPoint pointcut){ System.out.println("前置通知 切点:"+pointcut); }
public void afterReturning(JoinPoint point,Object result){
System.out.println("后置通知 切点:"+point);
}
public void after(JoinPoint point){ System.out.println("最终通知 切点:"+point); }
public void exception(JoinPoint point,Throwable e){
System.out.println("异常通知: " + e+"切点:"+point);
}
public void around(ProceedingJoinPoint point) throws Throwable {
System.out.println("环绕前");
point.proceed();
System.out.println("环绕后");
}
}