使用Spring的AOP
- 一、AOP 的常用注解
- 1.切面类@Aspect
- 2.@Pointcut
- 3.前置通知@Before
- 4.后置通知@AfterReturning
- 5.环绕通知@Around
- 6.异常通知@AfterThrowing
- 7.最终通知@After
- 8.切面顺序@Order
- 9.启用自动代理@EnableAspectJAutoProxy
- 二、AOP注解方式开发
- 三、AOP 全注解开发
- 四、基于XML配置方式的AOP(了解)
- Spring 对
AOP
的实现包括以下3种方式:- 第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式。
- 第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式。
- 第三种方式:Spring框架自己实现的AOP,基于XML方式。
- 实际开发种都是Spring + AspectJ来实现的AOP。
- 什么是AspectJ?(Eclipse组织的一个支持AOP的框架。AspectJ框架是独立于Spring框架之外的一个框架,Spring框架用了AspectJ)
- AspectJ项目起源于帕洛阿尔托(Palo Alto)研究中心(缩写为PARC)。该中心由Xerox集团资助,Gregor Kiczales领导,从1997年开始致力于AspectJ的开发,1998年第一次发布给外部用户,2001年发布1.0 release。为了推动AspectJ技术和社团的发展,PARC在2003年3月正式将AspectJ项目移交给了Eclipse组织,因为AspectJ的发展和受关注程度大大超出了PARC的预期,他们已经无力继续维持它的发展。
一、AOP 的常用注解
1.切面类@Aspect
-
@Aspect
作用是把当前类标识为一个切面供容器读取。@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface Aspect { String value() default ""; }
2.@Pointcut
@Pointcut
注解标注在方法上面,用来定义切入点。- 可以这样做:将切点表达式单独的定义出来,在需要的位置引入即可。
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Pointcut { String value() default ""; String argNames() default ""; }
3.前置通知@Before
@Before
目标方法执行之前的通知@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Before { String value(); String argNames() default ""; }
4.后置通知@AfterReturning
@AfterReturning
目标方法执行之后的通知@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface AfterReturning { String value() default ""; String pointcut() default ""; String returning() default ""; String argNames() default ""; }
5.环绕通知@Around
@Around
目标方法之前添加通知,同时目标方法执行之后添加通知。@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Around { String value(); String argNames() default ""; }
6.异常通知@AfterThrowing
@AfterThrowing
发生异常之后执行的通知@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface AfterThrowing { String value() default ""; String pointcut() default ""; String throwing() default ""; String argNames() default ""; }
7.最终通知@After
@After
放在finally语句块中的通知@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface After { String value(); String argNames() default ""; }
8.切面顺序@Order
- 我们知道,业务流程当中不一定只有一个切面,可能有的切面控制事务,有的记录日志,有的进行安全控制,如果多个切面的话,顺序如何控制:
可以使用@Order注解来标识切面类,为@Order注解的value指定一个整数型的数字,数字越小,优先级越高。
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) @Documented public @interface Order { /** * The order value. * <p>Default is {@link Ordered#LOWEST_PRECEDENCE}. * @see Ordered#getOrder() */ int value() default Ordered.LOWEST_PRECEDENCE; }
9.启用自动代理@EnableAspectJAutoProxy
- 开启自动代理之后,凡事带有@Aspect注解的bean都会生成代理对象。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(AspectJAutoProxyRegistrar.class) public @interface EnableAspectJAutoProxy { boolean proxyTargetClass() default false; boolean exposeProxy() default false; }
二、AOP注解方式开发
-
注意:本文使用了
log4j2
日志,如果不知道可以看我的博客 ===> Spring对IoC的实现中的第一个Spring程序 -
注意本文也使用了
junit
进行单元测试。 -
使用
Spring+AspectJ
的AOP
需要引入的依赖如下:<!--spring的核心依赖 aop core beans jcl expression 等--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.1.4</version> </dependency> <!-- AOP 依赖的AspectJ --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.1.4</version> </dependency>
-
Spring配置文件中添加context命名空间和aop命名空间
<?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:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> </beans>
-
第一步:定义目标类以及目标方法
package com.gdb.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; //目标类 @Service public class OrderService { private static final Logger logger = LoggerFactory.getLogger(OrderService.class); //目标方法 public void detail() { logger.info("正在打印订单详情......"); } }
-
第二步:编写切面类
package com.gdb.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; //切面类(通知+切点 = 切面) @Aspect @Component public class MyAspect { private static final Logger logger = LoggerFactory.getLogger(MyAspect.class); //这是需要增强的代码(通知) @Before("execution(* com.gdb.service..* (..))") // com.gdb.service包下的所有方法 public void beforeAdvice() { logger.info("前置通知执行了"); } @AfterReturning("execution(* com.gdb.service..* (..))") // com.gdb.service包下的所有方法, public void afterReturningAdvice() { logger.info("后置通知执行了"); } @Around("execution(* com.gdb.service..* (..))") // com.gdb.service包下的所有方法, public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { logger.info("前置环绕通知执行了"); proceedingJoinPoint.proceed(); // 执行目标方法 logger.info("后置环绕通知执行了"); } @AfterThrowing("execution(* com.gdb.service..* (..))") // com.gdb.service包下的所有方法, public void afterThrowingAdvice() { logger.info("异常通知执行了"); } @After("execution(* com.gdb.service..* (..))") // com.gdb.service包下的所有方法, public void afterAdvice() { logger.info("最终通知执行了"); } }
-
第三步:在配置文件中启动包扫描启用自动代理
<?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:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--开启组件扫描--> <context:component-scan base-package="com.gdb"/> <!--开启自动代理--> <aop:aspectj-autoproxy proxy-target-class="false"/> <!-- <aop:aspectj-autoproxy proxy-target-class="true"/> 开启自动代理之后,凡事带有@Aspect注解的bean都会生成代理对象。 proxy-target-class="true" 表示采用cglib动态代理。 proxy-target-class="false" 表示采用jdk动态代理。默认值是false。即使写成false,当没有接口的时候,也会自动选择cglib生成代理类。 --> </beans>
-
第四步:编写测试程序
@Test public void test(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); OrderService orderService = applicationContext.getBean("orderService", OrderService.class); orderService.detail(); }
-
第五步:执行结果
- 通过上面的执行结果就可以判断他们的执行顺序了,这里不再赘述。
-
第六步:结果中没有异常通知,这是因为目标程序执行过程中没有发生异常。我们尝试让目标方法发生异常:
package com.gdb.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @Service public class OrderService { private static final Logger logger = LoggerFactory.getLogger(OrderService.class); public void detail() { logger.info("正在打印订单详情......"); throw new RuntimeException(); } }
-
第七步:执行结果:
- 通过测试得知,当发生异常之后,最终通知也会执行,因为最终通知
@After
会出现在finally
语句块中。出现异常之后,后置通知
和环绕通知的结束部分
不会执行。
- 通过测试得知,当发生异常之后,最终通知也会执行,因为最终通知
-
优化使用切点表达式:
- 上面编写的切面类的缺点是:
- 第一:切点表达式重复写了多次,没有得到复用。
- 第二:如果要修改切点表达式,需要修改多处,难维护。
- 上面编写的切面类的缺点是:
-
可以这样做:将切点表达式单独的定义出来,在需要的位置引入即可。
package com.gdb.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { private static final Logger logger = LoggerFactory.getLogger(MyAspect.class); @Pointcut("execution(* com.gdb.service..* (..))") public void pointcut() { } @Before("pointcut()") // com.gdb.service包下的所有方法 public void beforeAdvice() { logger.info("前置通知执行了"); } @AfterReturning("pointcut()") // com.gdb.service包下的所有方法, public void afterReturningAdvice() { logger.info("后置通知执行了"); } @Around("pointcut()") // com.gdb.service包下的所有方法, public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { logger.info("前置环绕通知执行了"); proceedingJoinPoint.proceed(); logger.info("后置环绕通知执行了"); } @AfterThrowing("pointcut()") // com.gdb.service包下的所有方法, public void afterThrowingAdvice() { logger.info("异常通知执行了"); } @After("pointcut()") // com.gdb.service包下的所有方法, public void afterAdvice() { logger.info("最终通知执行了"); } }
-
使用@Pointcut注解来定义独立的切点表达式。
-
注意这个@Pointcut注解标注的方法随意,只是起到一个能够让@Pointcut注解编写的位置。
三、AOP 全注解开发
- 第一步:
就是编写一个类,在这个类上面使用大量注解来代替spring的配置文件,spring配置文件消失了
,如下:package com.gdb.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration // 配置类 @ComponentScan("com.gdb") @EnableAspectJAutoProxy(proxyTargetClass = false) public class SpringConfig { }
- 第二步:测试程序也变化了
@Test public void test() { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class); OrderService orderService = applicationContext.getBean("orderService", OrderService.class); orderService.detail(); }
- 第三步:执行结果
四、基于XML配置方式的AOP(了解)
- 第一步:编写目标类
package com.gdb.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
public void detail() {
logger.info("正在打印订单详情......");
}
}
- 第二步:编写切面类,并且编写通知
package com.gdb.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyAspect {
private static final Logger logger = LoggerFactory.getLogger(MyAspect.class);
public void beforeAdvice() {
logger.info("前置通知执行了");
}
public void afterReturningAdvice() {
logger.info("后置通知执行了");
}
public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
logger.info("前置环绕通知执行了");
proceedingJoinPoint.proceed();
logger.info("后置环绕通知执行了");
}
public void afterThrowingAdvice() {
logger.info("异常通知执行了");
}
public void afterAdvice() {
logger.info("最终通知执行了");
}
}
- 第三步:编写spring配置文件
<?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:context="http://www.springframework.org/schema/context"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="myAspect" class="com.gdb.aspect.MyAspect"/>
<bean id="orderService" class="com.gdb.service.OrderService"/>
<!--aop配置-->
<aop:config>
<!--切点表达式-->
<aop:pointcut id="p" expression="execution(* com.gdb.service..* (..))"/>
<!--切面-->
<aop:aspect ref="myAspect">
<!--切面=通知 + 切点-->
<aop:before method="beforeAdvice" pointcut-ref="p"/>
<aop:after-returning method="afterReturningAdvice" pointcut-ref="p"/>
<aop:around method="aroundAdvice" pointcut-ref="p"/>
<aop:after-throwing method="afterThrowingAdvice" pointcut-ref="p"/>
<aop:after method="afterAdvice" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
</beans>
- 第四步:编写测试程序
@Test
public void test() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
orderService.detail();
}
- 第四步:执行结果
通过结果可以看出来顺序和前面的不一样了,我感觉是配置文件中的顺序有关系,由于主要都是使用注解的方式。