Spring AOP的使用

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 
Spring AOP 是一个用于实现面向切面编程的框架。它可以通过配置来实现横切关注点的模块化,并将其应用到程序的不同部分。Spring AOP 使用 AspectJ 切入点指示符来定义切入点表达式,用于匹配方法执行连接点。Spring AOP 支持以下 AspectJ 切入点指示符: 1. execution:用于匹配方法执行连接点。这是使用 Spring AOP 时要使用的主要切入点指示符。 2. within:限制匹配以连接某些类型中的点(使用 Spring AOP 时在匹配类型中声明的方法的执行)。 3. this:限制匹配到连接点(使用 Spring AOP 时方法的执行),其中 Bean 引用(Spring AOP 代理)是给定类型的实例。 4. target:限制匹配到连接点(使用 Spring AOP 时方法的执行),其中目标对象(正在代理的应用程序对象)是给定类型的实例。 5. args:限制匹配到连接点(使用 Spring AOP 时方法的执行),其中参数是给定类型的实例。 6. @target:限制匹配到连接点(使用 Spring AOP 时方法的执行),其中执行对象的类具有给定类型的注释。 7. @args:限制匹配到连接点(使用 Spring AOP 时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注释。 8. @within:限制匹配以连接具有给定注释的类型中的点(使用 Spring AOP 时在具有给定注释的类型中声明的方法的执行)。 9. @annotation:限制匹配到连接点的主题(在 Spring AOP 中运行的方法)具有给定注释的连接点。 在使用 Spring AOP 时,需要引入 Spring AOPSpring Context 相关的包,并在配置文件中进行相应的配置。可以通过 Maven 或其他构建工具来引入相关依赖。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值