一、基于XML的声明式
基于 XML 的声明式是指通过 Spring 配置文件的方式定义切面、切入点及声明通知,而所有的切面和通知都必须定义在 <aop:config> 元素中。
步骤如下:
(1)导入jar包。
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
(2)创建Book类
public class Book {
public void addBook(){
System.out.println("add Book……");
}
public void updateBook(){
System.out.println("update Book……");
}
}
(3)创建BookProxy类
public class BookProxy {
public void before(){
System.out.println("before……");
}
public void after(){
System.out.println("after……");
}
public void afterReturing(){
System.out.println("afterReturing……");
}
public void afterThrowing(){
System.out.println("afterThrowing……");
}
public void around(ProceedingJoinPoint point) throws Throwable {
System.out.println("around 之前……");
point.proceed();
System.out.println("around 之后……");
}
}
(4)在bean.xml中进行bean配置和aop的配置。
<!-- 基于XML的AOP实现 -->
<!-- 配置bean -->
<bean id="book" class="com.yht.example6.Book"></bean>
<bean id="bookProxy" class="com.yht.example6.BookProxy"></bean>
<!-- aop配置 -->
<aop:config>
<!--
expression : 配置切入点的具体位置
id:切入点的id
-->
<aop:pointcut id="p1" expression="execution(* com.yht.example6.Book.*(..))"/>
<!-- -->
<aop:aspect ref="bookProxy">
<aop:before method="before" pointcut-ref="p1"></aop:before>
<aop:after method="after" pointcut-ref="p1"></aop:after>
<aop:after-returning method="afterReturing" pointcut-ref="p1"></aop:after-returning>
<aop:after-throwing method="afterThrowing" pointcut-ref="p1"></aop:after-throwing>
<aop:around method="around" pointcut-ref="p1"></aop:around>
</aop:aspect>
</aop:config>
(5)进行单元测试
@Test
public void testBook(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Book book = context.getBean("book", Book.class);
book.addBook();
}
执行结果如下:
二、基于注解的声明式
在 Spring 中,尽管使用 XML 配置文件可以实现 AOP 开发,但是如果所有的相关的配置都集中在配置文件中,势必会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。AspectJ 框架为 AOP 开发提供了另一种开发方式——基于 Annotation 的声明式。AspectJ 允许使用注解定义切面、切入点和增强处理,而 Spring 框架则可以识别并根据这些注解生成 AOP 代理。常用注解介绍如下:
名称 | 说明 |
---|---|
@Aspect | 用于定义一个切面 |
@Before | 用于定义前置通知,相当于BeforeAdvice |
@AfterReturning | 用于定义后置通知,相当于AfterReturningAdvice |
@Around | 用于定义环绕通知,相当于MethodInterceptor |
@AfterThrowing | 用于定义异常抛出通知,相当ThrowAdvice |
@After | 用于定义最终(final)通知,不管是否异常,该通知都会执行 |
@DeclareParents | 用于定义引介通知,相当于IntroductionInterceptor(了解) |
(1)在bean.xml中配置。
<!-- 配置注解扫描的包 -->
<context:component-scan base-package="com.yht.example6"></context:component-scan>
<!--
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。
-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。在<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,如果proxy-target-class属值被设置为false或者这个属性被省略,那么标准的JDK 基于接口的代理将起作用。当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
(2)创建User类
@Component
public class User {
public void addUser(){
System.out.println("添加用户");
}
public void updateUser(){
System.out.println("修改用户");
}
public void deleteUser(){
System.out.println("删除用户");
}
public void searchUser(){
System.out.println("查找用户");
}
}
(3)创建UserProxy
@Component
@Aspect
public class UserProxy {
@Before(value = "execution(* com.yht.example6.User.addUser(..))")
public void before(){
System.out.println("方法执行之前");
}
@After(value = "execution(* com.yht.example6.User.addUser(..))")
public void after(){
System.out.println("方法执行之后");
}
@AfterReturning(value = "execution(* com.yht.example6.User.addUser(..))")
public void afterReturning(){
System.out.println("返回后通知");
}
@AfterThrowing(value = "execution(* com.yht.example6.User.addUser(..))")
public void afterThrowing(){
System.out.println("抛出异常后通知");
}
@Around(value = "execution(* com.yht.example6.User.addUser(..))")
public void around(ProceedingJoinPoint point) throws Throwable {
System.out.println("环绕通知——方法执行之前");
point.proceed();
System.out.println("环绕通知——方法执行之后");
}
}
(4)进行单元测试。
@Test
public void testUser(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = context.getBean("user", User.class);
user.addUser();
}
执行结果如下:
几点说明:
(1)afterThrowing方法是在程序出现异常时执行的,在User的addUser()让程序出现异常。检测该方法是否会执行。
@AfterThrowing(value = "execution(* com.yht.example6.User.addUser(..))")
public void afterThrowing(){
System.out.println("抛出异常后通知");
}
制造一个NumberFormatException:
public void addUser(){
int val = Integer.parseInt("123ab");//异常
System.out.println("添加用户");
}
结果如下:
(2)@Order的介绍
@Order 注解用来声明组件的顺序,值越小,优先级越高,越先被执行/初始化。如果没有该注解,则优先级最低。对于上述代码,再添加一个PersonProxy类,给定@Order(1),UserProxy给定@Order(5),则执行结果PersonProxy的before方法会在UserProxy的before方法之前执行。
1)定义PersonProxy类:
@Component
@Aspect
@Order(1)
public class PersonProxy {
@Before(value = "execution(* com.yht.example6.User.addUser(..))")
public void beforeMethod(){
System.out.println("我是PersonProxy的before方法");
}
}
2) 给UserProxy添加@Order(5):
3)进行测试。得到结果如下:
(3)相同切入点的提取
在上面的例子中,UserProxy中的五种通知的配置,它们的切入点表达式是相同的,我们可以将其提取出来,然后在每个通知的注解中引用即可。所以UserProxy类就如下面:
@Component
@Aspect
@Order(5)
public class UserProxy {
@Pointcut(value = "execution(* com.yht.example6.User.addUser(..))")
public void commonMsg(){}
@Before(value = "commonMsg()")
public void before(){
System.out.println("方法执行之前");
}
@After(value = "commonMsg()")
public void after(){
System.out.println("方法执行之后");
}
@AfterReturning(value = "commonMsg()")
public void afterReturning(){
System.out.println("返回后通知");
}
@AfterThrowing(value = "commonMsg()")
public void afterThrowing(){
System.out.println("抛出异常后通知");
}
@Around(value = "commonMsg()")
public void around(ProceedingJoinPoint point) throws Throwable {
System.out.println("环绕通知——方法执行之前");
point.proceed();
System.out.println("环绕通知——方法执行之后");
}
}
最终得到的结果和前面也将是一样的。
(4)AOP纯注解开发
AOP纯注解开发 和IOC注解开发基本类似,提供一个配置类。加入注解:
@Configuration
@ComponentScan(basePackages = "com.yht.example6")
//@EnableAspectJAutoProxy标记在主配置类上,表示开启基于注解的aop模式
@EnableAspectJAutoProxy
public class AopConfig {
}
测试如下:
@Test
public void testUserAnno(){
ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
User user = context.getBean("user", User.class);
user.addUser();
}