Spring提供了4种类型的AOP支持:
- 基于代理的经典Spring AOP;
- 纯POJO切面;
- @AspectJ注解驱动的切面;
- 注入式AspectJ切面(适用于Spring各版本)
前三种都是Spring AOP实现的变体,Spring AOP建立在动态代理的基础之上,所以仅支持方法拦截。在运行期把切面织入到spring管理的bean中,切面相当于一个代理类,拦截被通知方法的调用,并转发调用给真正的目标对象,在目标方法被调用之前,织入横切逻辑。
代理类的作用有点类似装饰器,其实这两种模式的概念本来就有点混淆,都是在目标对象的方法被调用前或者调用后增加逻辑。
Spring中涉及到的几个基本概念:
切面:切面由通知和起点组成,通知就是该切面的功能,更准确点说就是需要为目标对象所增强的功能和契机。
连接点:通知织入的点。
切点:就是带有通知的连接点,比如切点匹配了一个类的所有方法,此时所有方法都是连接点,那这些方法都会被织入通知。如果匹配的是一个类的某个方法,该方法就是切点也是连接点。如果匹配的是接口的方法,重写该方法的子类都可以成为切点,这点很重要。
切点是通过切点表达式来指定的
execution(),用于匹配某个方法执行时触发,也就是用来指定切点表达式。
使用注解方式定义切面
@Component
@Aspect
public class Audience {
// @Pointcut定义一个切点,该切点表达式代表匹配aspect包及其子包下所有类的所有方法,
// 如果是单个.,就不包含子包,该切点可以被其他注解引用。
@Pointcut("execution(* com.self.application.aspect..*.*(..))")
public void performance() {}
@Before("performance()")
public void silenceCallPhones(JoinPoint point){
System.out.println("Before");
System.out.println("方法参数" + point.getArgs());
System.out.println("方法签名" + point.getSignature().getName());
System.out.println("被织入增强处理的目标对象" + point.getTarget());
System.out.println("为目标对象生成的代理对象" + point.getThis());
}
@AfterReturning(value = "performance()",returning = "returnValue")
public void applause(JoinPoint point, Object returnValue){
// 这里有一点需要注意,当AfterReturning和Around共存时,AfterReturning注解的
// 返回值returnValue总是null。
System.out.println("AfterReturning中目标方法的返回值为:" + returnValue);
System.out.println("AfterReturning");
}
@AfterThrowing("performance()")
public void demandRefund(JoinPoint point){
System.out.println("Exception");
}
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint jp){
try {
System.out.println("目标方法调用前");
Object returnValue = jp.proceed();
System.out.println("Around注解中目标方法的返回值为:" + returnValue);
} catch (Throwable e) {
System.out.println("目标方法调用后");
}
}
}
@Component一定要加上,一定要加上,一定要加上,@Aspect只能表明这是一个切面,不能表明是一个bean,所以并未纳入spring容器管理。楼主就是因为没有加导致aop一直未生效,已经哭晕在厕所。
@Aspect 注解表明这是一个切面,访问目标方法最简单的做法是定义增强处理方法时,将第一个参数定义为JoinPoint类型,该参数就代表了织入通知的连接点。JoinPoint里包含了如下几个常用的方法:
Object[] getArgs:返回目标方法的参数
Signature getSignature:返回目标方法的签名
Object getTarget:返回被织入增强处理的目标对象
Object getThis:返回AOP框架为目标对象生成的代理对象
AspectJ提供了五个注解来定义通知,分别是@Before,目标方法之前执行。@AfterReturning,返回后调用。@After,返回或抛出异常后调用。@AfterThrowing,抛出异常后调用。@Around,环绕通知。
如果不使用成员变量存储信息的话,在前置通知和后置通知之间共享信息非常麻烦,但如果是用@Around,目标方法执行前后信息共享就很方便,一般来说如果有该方法就不需要其他方法了。如果同时存在,注解的优先级根据不同配置方式不同,使用注解和使用<aop:aspect>以及<aop:advisor> 都不一样。当不同切面中的多个增强处理需要在同一个连接点被织入时,也就是有多个代理依赖同一对象,如需调整这两个切面类中通知执行的先后顺序,可以让切面类实现org.springframework.core.Ordered接口或者使用@Order注解来修饰一个切面类,这个注解时可以配置一个int类型的value属性,该属性值越小,优先级越高。
测试类如下:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = App.class)
public class RedisTest {
@Autowired
private Performance performance;
@Test
public void testSet(){
performance.perform("la la la ");
}
}
@SpringBootApplication
@EnableAspectJAutoProxy
@Slf4j
public class App {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(App.class);
ApplicationContext applicationContext = springApplication.run(args);
log.info("==================================================================================");
log.info("============================= SpringBoot Start Success============================");
log.info("==================================================================================");
}
}
打印信息如下:
Silence call phone
Before
方法参数[la la la ]
方法签名perform
被织入增强处理的目标对象com.self.application.aspect.PerformanceImpl@461ad730
为目标对象生成的代理对象com.self.application.aspect.PerformanceImpl@461ad730
la la la
Around注解中目标方法的返回值为:la la la
AfterReturning中目标方法的返回值为:null
AfterReturning
使用xml方式定义切面
<?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:tx="http://www.springframework.org/schema/tx"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"
>
<!--启用AspectJ自动代理-->
<aop:aspectj-autoproxy/>
<bean id="performanceImpl" class="com.self.application.aspect.PerformanceImpl"/>
<bean id="audience" class="com.self.application.Audience"/>
<!--XML配置文件方式声明切面-->
<aop:config>
<!--定义切面-->
<aop:aspect ref="audience">
<aop:pointcut
id="perform" expression="execution(* com.self.application.aspect..*.*(..))"/>
<aop:before
pointcut-ref="perform" method="silenceCallPhones"/>
<aop:after-returning
pointcut-ref="perform" method="applause" returning="returnValue"/>
<aop:after-throwing
pointcut-ref="perform" method="demandRefund"/>
<aop:around
pointcut-ref="perform" method="watchPerformance"/>
</aop:aspect>
</aop:config>
</beans>
使用xml方式定义切面的Audience类和使用注解方式没有区别,只是所有需要注解的地方都删掉,切点表达式也不需要,放在xml声明的好处就是容易被人看到。
测试类代码如下:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = App.class)
public class RedisTest {
@Autowired
private Performance performance;
@Test
public void testSet(){
performance.perform("la la la ");
}
}
@SpringBootApplication
@ImportResource("classpath:/config/aspect.xml")
@Slf4j
public class App {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(App.class);
ApplicationContext applicationContext = springApplication.run(args);
log.info("==================================================================================");
log.info("============================= SpringBoot Start Success============================");
log.info("==================================================================================");
}
}
使用xml方式定义切面的另一种方式是使用通知器:
<?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:tx="http://www.springframework.org/schema/tx"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"
>
<!--启用AspectJ自动代理-->
<aop:aspectj-autoproxy/>
<bean id="performanceImpl" class="com.self.application.aspect.PerformanceImpl"/>
<bean id="audience" class="com.self.application.Audience"/>
<!--XML配置文件方式声明切面-->
<aop:config>
<!--定义在service包以及子包里的任意方法的执行-->
<aop:pointcut id="perform" expression="execution(* com.self.application.aspect..*.*(..))"/>
<aop:advisor advice-ref="audience" pointcut-ref="perform" order="1"/>
</aop:config>
</beans>
<aop:advisor/>标签定义通知器,通知器和切面一样,也包括通知和切点,引用的通知必须实现Advice接口。原理基本上是一样的,只是使用的方式不同而已。若<aop:config>元素中同时存在<aop:advisor>元素和<aop:aspect>元素,则它们元素必须按照< aop:pointcut >,<aop:advisor>和<aop:aspect>顺序来定义。当不同切面中的多个增强处理对同一个切入点织入时,如需调整这两个切面类中通知执行的先后顺序,则需添加order属性,order越小,优先级越高。
Audience代码如下:
public class Audience implements MethodBeforeAdvice, AfterReturningAdvice, MethodInterceptor {
// 第一个参数是连接点,第二个是连接点参数,第三个是连接点对象
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("Before");
System.out.println("方法签名" + method.getName());
System.out.println("方法参数" + Arrays.toString(objects));
System.out.println("被织入增强处理的目标对象" + o);
}
// 第一个参数是目标方法返回值,第二个是连接点,第三个是连接点参数,第四个是连接点对象
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("AfterReturning");
System.out.println("afterReturning中目标方法的返回值为:" + o);
}
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("invoke中目标方法执行前");
Object returnValue = methodInvocation.proceed();
System.out.println("invoke中目标方法执行后");
System.out.println("invoke中目标方法返回值" + returnValue);
return returnValue;
}
}
测试类同上,执行结果如下:
invoke中目标方法执行前
Before
方法签名perform
方法参数[la la la ]
被织入增强处理的目标对象com.csii.application.aspect.PerformanceImpl@13d9cbf5
la la la
AfterReturning
afterReturning中目标方法的返回值为:la la la
invoke中目标方法执行后
invoke中目标方法返回值la la la