1、概述
我们在编写代码时,只想关注逻辑代码的实现。但是我们的程序是要放到服务器上去跑的,我们在代码中如果只有业务逻辑代码。虽然代码简洁,但是万一程序在服务器跑的时候数据出现问题时,没办法快速定位到问题所在。但是将日志等非逻辑代码和我们的逻辑代码放在一起,会使得代码冗余,代码的可读性难度加大。
基于上面的问题,Spring为我们提供了一种解决方案,将业务功能逻辑代码和非功能代码分开来写,然后使用动态代理,将非功能性代码嵌入到功能代码中,这就是所谓的AOP(面向切面编程)。
1.1、使用Aop对Bean增强的步骤。
1、我们要使用Aop对我们的bean进行增强,我们要先导aspects包(我这里用的是SpringBoot)
<!--这个包用来配置增强类(非业务逻辑类)-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.20</version>
</dependency>
2、编写业务逻辑类
@Slf4j
@Component
public class Function {
/**
*
* @param name 执行人
* @param taskName 任务名
* @return
*/
public int add(int a, int b) {
//业务逻辑代码
return a+b;
}
}
3、编写增强类代码,并通过切入点表达式对业务逻辑代码(指定到方法)进行增强
@Component
@Slf4j
@Aspect//告诉spring这是一个切面增强类
public class LogAop {
/**
* 切入点表达式:指定要增强的方法
* 切入点表达式格式: 访问修饰符(可省略) 返回值类型 方法的类路径.方法名(参数类1,参数类型2)
* 【*代表所有,方法中的..代表任意参数】
*/
@Pointcut("execution(public * com.tellhow.review.Aop.Function.*(..))")
public void pointcut() {
}
@Before("pointcut()")//前置通知
public void testbefore() {
log.info("前置通知,去青罗司接任务" );
}
@After("com.tellhow.review.Aop.LogAop.pointcut()")//后置通知
public void testAfter(){
log.info("后置通知"+"任务完成,通知青罗司验收");
}
/**
* 发生异常,走异常通知后也会走后置通知
* @AfterReturning的属性信息 1、value指定切入点表达式。2、returning将目标方法的返回值与通知方法的参数绑定
* @param joinPoint 切入点信息(包括方法名、参数等信息)
*/
@AfterReturning(value = "execution(public * com.tellhow.review.Aop.Function.GhostInfestation(String,String))",returning = "result")//返回通知
public void testReturn(JoinPoint joinPoint,String result){
String name = joinPoint.getSignature().getName();//获取方法名
log.info("返回通知"+"返回值是:"+result);
}
/**
* JoinPoint一定要放参数表的第一位
* @AfterThrowing的 value属性指定切入点表达式 throwing属性将抛出的异常绑定到的通知签名中的参数的名称
* @param exception
*/
@AfterThrowing(value="pointcut()",throwing = "exception")//异常通知
public void testException(JoinPoint joinPoint,Exception exception){
log.info("异常通知");
}
/**
*
* @param proceedingJoinPoint procedingjoinpoint公开proceed(..)方法,以支持@AspectJ方面的around通知,proceedingJoinPoint.proceed(args)可以修改参数的值
* @throws Throwable
*/
@Around(value="pointcut()")//环绕通知
public void testAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("前置环绕通知");
Object[] args = proceedingJoinPoint.getArgs();//可以动态修改参数的值
proceedingJoinPoint.proceed(args);
log.info("后置环绕通知");
}
}
4、在配置类上使用@EnableAspectJAutoProxy注解开启对切面的支持
@EnableAspectJAutoProxy//@EnableAspectJAutoProxy开启切面代理功能,切面的底层就是代理
@Configuration
@ComponentScan(value = "com.tellhow.review.Aop")
@Slf4j
public class AopConfguration {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopConfguration.class);
Function bean = context.getBean(Function.class);
bean.GhostInfestation("陈兴","除掉伥鬼");
log.info(bean+"");
}
}
2、原理解析
上面我们讲过,我们要用Aop,一定要在再配置类上添加@EnableAspectJAutoProxy注解。我们来看一下这个注解做了些什么,以及怎么起作用的。
2.1、@EnableAspectJAutoProxy做了什么
1、进入到@EnableAspectJAutoProxy注解,发现它用@Import注解导入了一个bean【AspectJAutoProxyRegistrar】到容器中。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
/**
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
* to standard Java interface-based proxies. The default is {@code false}.
*/
boolean proxyTargetClass() default false;
/**
* Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
* for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
* Off by default, i.e. no guarantees that {@code AopContext} access will work.
* @since 4.3.1
*/
boolean exposeProxy() default false;
}
2、之前我们了解过@Import注解可以为我们导入配置类、普通类、ImportBeanDefinitionRegistrar【bean的定义信息】、以及ImportSelector类型的组件。由于AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口。所以这里的话,属于使用@Import注解导入一个ImportBeanDefinitionRegistrar类型的组件。
3、ImportBeanDefinitionRegistrar接口有一个registerBeanDefinitions()方法,用来注册bean的定义信息。
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
/**
* Register, escalate, and configure the AspectJ auto proxy creator based on the value
* of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
* {@code @Configuration} class.
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);//向容器注册组件
AnnotationAttributes enableAspectJAutoProxy =AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}
4、调用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry)方法,这里才是注册bean的定义信息。registerAspectJAnnotationAutoProxyCreatorIfNecessary()->registerOrEscalateApcAsRequired()->
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {
return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}
//一直在这里才看到注册了AnnotationAwareAspectJAutoProxyCreator的定义信息。
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry