AOP使用与原理分析

一、AOP简介

1. AOP概念

AOP,Aspect Oriented Programming ,面向切面编程,是对面向对象编程 OOP 的升华。 OOP 是纵向对一个事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。而 AOP 是横向的对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面,而用这种思维去设计编程的方式叫做面向切面编程。

AOP是指在程序的运行期间动态的将某段代码切入到指定方法、指定位置进行运行的编程方式。AOP的底层是使用动态代理实现的。

在这里插入图片描述

2. AOP思想的实现方案

动态代理技术,在运行期间,对目标对象的方法进行增强,代理对象同名方法内可以执行原有逻辑的同时,再嵌入执行其他增强逻辑或其他对象的方法

在这里插入图片描述

3. 模拟AOP的基础代码

其实在之前学习BeanPostProcessor 时,在 BeanPostProcessor 的 after 方法中使用动态代理对 Bean 进行了增强,实际存储到单例池 singleObjects 中的不是当前目标对象本身,而是当前目标对象的代理对象 Proxy ,这样在调用目标对象方法时,实际调用的是代理对象 Proxy 的同名方法,起到了目标方法前后都进行增强的功能,对该方式进行一下优化,将增强的方法提取到一个增强类中,且只对 com.itheima.service.impl 包下的任何类的任何方法进行增强。

// 自定义增强类
public class MyAdvice {
    
    
	public void beforeAdvice() {
    
    
		System.out.println("beforeAdvice...");
	}
	public void afterAdvice() {
    
    
		System.out.println("afterAdvice...");
	}
}
public class MockAopBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
    
    

	// 注入 Spring 容器对象
    private ApplicationContext applicationContext;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    
    
		// 获得 Advice 对象
        MyAdvice myAdvice = applicationContext.getBean(MyAdvice.class); 
		String packageName = bean.getClass().getPackage().getName();
        if("com.itheima.service.impl".equals(packageName)) {
    
    
            // 对 Bean 进行动态代理,返回的是 Proxy 代理对象
            Object proxyBean = Proxy.newProxyInstance(
                    bean.getClass().getClassLoader(),
                    bean.getClass().getInterfaces(),
                    (Object proxy, Method method, Object[] args) -> {
    
    
                        // 执行增强对象的before方法
                        myAdvice.beforeAdvice();
                        // 执行目标对象的目标方法
                        Object result = method.invoke(bean, args);
                        // 执行增强对象的after方法
                        myAdvice.afterAdvice();
                        return result;
                    }
            );
			// 返回代理对象
            return proxyBean;
        }

        return bean;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    
    
        this.applicationContext = applicationContext;
    }
}

4. AOP相关概念

在这里插入图片描述

二、基于XML方式配置的AOP

1. XML方式配置AOP

前面我们自己编写的AOP 基础代码还是存在一些问题的,主要如下:

  • 被增强的包名在代码写死了
  • 通知对象的方法在代码中写死了

在这里插入图片描述

通过配置文件的方式去解决上述问题

  • 配置哪些包、哪些类、哪些方法需要被增强
  • 配置目标方法要被哪些通知方法所增强,在目标方法执行之前还是之后执行增强配置方式的设计、配置文件(注解)的解析工作,Spring 已经帮我们封装好了

xml方式配置 AOP 的步骤:

  1. 导入 AOP 相关坐标;
  2. 准备目标类、准备增强类,并配置给 Spring 管理;
  3. 配置切点表达式(哪些方法被增强);
  4. 配置织入(切点被哪些通知方法增强,是前置增强还是后置增强)。

(1)导入 AOP 相关坐标

<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.9.6</version>
</dependency>

在这里插入图片描述

(2)准备目标类、准备增强类,并配置给 Spring 管理

在这里插入图片描述

(3)配置切点表达式(哪些方法被增强)

(4)配置织入(切点被哪些通知方法增强,是前置增强还是后置增强)

<!--aop配置-->
<aop:config>
    <!--配置切点表达式,目的是要指定哪些方法被增强-->
    <aop:pointcut id="myPointcut" expression="execution(void com.itheima.service.impl.UserServiceImpl.show1())"/>   
    <!--配置织入,目的是要执行哪些切点与那些通知进行结合-->
    <!--切面=切点+通知-->
    <aop:aspect ref="myAdvice">
        <!--前置通知-->
        <aop:before method="beforeAdvice" pointcut-ref="myPointcut"/>
        <!--后置通知-->
        <aop:after-returning method="afterAdvice" pointcut-ref="myPointcut"/>
    </aop:aspect>
</aop:config>

2. XML方式AOP配置详解

xml配置 AOP 的方式还是比较简单的,下面看一下 AOP 详细配置的细节:

  • 切点表达式的配置方式
  • 切点表达式的配置语法
  • 通知的类型
  • AOP配置的两种方式

切点表达式的配置方式有两种,直接将切点表达式配置在通知上,也可以将切点表达式抽取到外面,在通知上进行引用

切点表达式是配置要对哪些连接点(哪些类的哪些方法)进行通知的增强,语法如下:

execution([访问修饰符] 返回值类型 包名.类名.方法名(参数))

其中:

  • 访问修饰符可以省略不写;
  • 返回值类型、某一级包名、类名、方法名可以使用 * 表示任意;
  • 包名与类名之间使用单点. 表示该包下的类,使用双点 … 表示该包及其子包下的类;
  • 参数列表可以使用两个点… 表示任意参数。

切点表达式举几个例子方便理解

// 表示访问修饰符为 public 、无返回值、在 com.itheima.aop 包下的 TargetImpl 类的无参方法 show
execution(public void com.itheima.aop.TargetImpl.show())
// 表述 com.itheima.aop 包下的 TargetImpl 类的任意方法
execution(* com.itheima.aop.TargetImpl.*(..))
// 表示 com.itheima.aop 包下的任意类的任意方法
execution(* com.itheima.aop.*.*(..))
// 表示 com.itheima.aop 包及其子包下的任意类的任意方法
execution(* com.itheima.aop..*.*(..))
// 表示任意包中的任意类的任意方法
execution(* *..*.*(..))

AspectJ的通知有以下五种类型

在这里插入图片描述

环绕通知

public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    
    
    System.out.println("环绕前的增强....");
    proceedingJoinPoint.proceed();// 执行目标方法
    System.out.println("环绕后的增强....");
}
<aop:around method="around" pointcut-ref="myPointcut"/>

异常通知,当目标方法抛出异常时,异常通知方法执行,且后置通知和环绕通知不再执行

public void afterThrowing(){
    
    
    System.out.println("目标方法抛出异常时,后置通知和环绕通知不再执行");
}
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>

最终通知,类似异常捕获中的finally ,不管目标方法有没有异常,最终通知都会执行

public void after() {
    
    
    System.out.println("最终的增强....");
}
<aop:after method="after" pointcut-ref="myPointcut"/>

通知方法在被调用时,Spring 可以为其传递一些必要的参数

在这里插入图片描述

JoinPoint对象

public void 通知方法名称(JoinPoint joinPoint) {
    
    
	// 获得目标方法的参数
	System.out.println(joinPoint.getArgs());
	// 获得目标对象
	System.out.println(joinPoint.getTarget());
	// 获得精确的切点表达式信息
	System.out.println(joinPoint.getStaticPart());
}

ProceedingJoinPoint对象

public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
	// 获得目标方法的参数
	System.out.println(joinPoint.getArgs());
	// 获得目标对象
	System.out.println(joinPoint.getTarget());
	// 获得精确的切点表达式信息
	System.out.println(joinPoint.getStaticPart());
	// 执行目标方法
	Object result = joinPoint.proceed(); 
	// 返回目标方法返回值
	return result; 
}

Throwable对象

public void afterThrowing(JoinPoint joinPoint, Throwable th) {
    
    
	// 获得异常信息
	System.out.println("异常对象是:" + th + ",异常信息是:" + th.getMessage());
}
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" throwing="th"/>

AOP的另一种配置方式,该方式需要通知类实现 Advice 的子功能接口

public interface Advice {
    
    
}

Advice的子功能接口

在这里插入图片描述

例如:通知类实现了前置通知和后置通知接口

public class Advices implements MethodBeforeAdvice, AfterReturningAdvice {
    
    

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
    
    
        System.out.println("前置通知..........");
    }

    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
    
    
        System.out.println("后置通知...........");
    }

}

切面使用 advisor 标签配置

<aop:config>
	<!--将通知和切点进行结合-->
	<aop:advisor advice-ref="advices" pointcut="execution(void com.itheima.aop.TargetImpl.show())"/>
<aop:config>

又例如:通知类实现了方法拦截器接口

public class MyMethodInterceptor implements MethodInterceptor {
    
    

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
    
    
        System.out.println("环绕前******");
        // 执行目标方法
        Object res = methodInvocation.getMethod().invoke(methodInvocation.getThis(),  methodInvocation.getArguments());
        System.out.println("环绕后******");
        return res;
    }
}

切面使用advisor 标签配置

<aop:config>
	<!--将通知和切点进行结合-->
	<aop:advisor advice-ref="myMethodInterceptor" pointcut="execution(void com.itheima.aop.TargetImpl.show())"/>
<aop:config>

使用 aspect 和 advisor 配置区别如下:

  1. 配置语法不同:
<!--使用 advisor 配置-->
<aop:config>
	<!--advice-ref: 通知Bean 的 id-->
	<aop:advisor advice-ref="advices" pointcut="execution(void com.itheima.aop.TargetImpl.show())"/>
<aop:config>

<!--使用 aspect 配置-->
<aop:config>
	<!--ref: 通知Bean 的 id-->
	<aop:aspect ref="advices" pointcut="execution(void com.itheima.aop.TargetImpl.show())"/>
<aop:config>
  1. 通知类的定义要求不同,advisor 通知类需要实现 Advice 的子功能接口:
public class Advices implements MethodBeforeAdvice {
    
    

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
    
    
        System.out.println("前置通知..........");
    }
}

aspect不需要通知类实现任何接口,在配置的时候指定哪些方法属于哪种通知类型即可,更加灵活方便:

public class Advices {
    
    
	public void before() {
    
    
		System.out.println("前置通知..........");
	}
}
  1. 可配置的切面数量不同
  • 一个advisor 只能配置一个固定通知和一个切点表达式;
  • 一个aspect 可以配置多个通知和多个切点表达式任意组合,粒度更细。
  1. 使用场景不同
  • 如果通知类型多、允许随意搭配情况下可以使用aspect 进行配置;
  • 如果通知类型单一、且通知类中通知方法一次性都会使用到的情况下可以使用 advisor 进行配置;
  • 在通知类型已经固定,不用人为指定通知类型时,可以使用 advisor 进行配置,例如后面要学习的 Spring 事务控制的配置;

由于实际开发中,自定义 aop 功能的配置大多使用 aspect 的配置方式,所以我们后面主要讲解 aspect 的配置,advisor 是为了后面 Spring 声明式事务控制做铺垫,此处大家了解即可。

3. XML方式AOP原理剖析

通过
xml 方式配置 AOP 时,我们引入了 AOP 的命名空间,根据前面章节讲解的,要去找 spring-aop 包下的 META -INF ,在去找 spring.handlers 文件

在这里插入图片描述

最终加载的是AopNamespaceHandler ,该 Handler 的 init 方法中注册了 config 标签对应的解析器

public class AopNamespaceHandler extends NamespaceHandlerSupport {
    
    

	@Override
	public void init() {
    
    
		registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
		registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
		registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
	}

}

以ConfigBeanDefinitionParser 作为入口进行源码剖析,最终会注册一个AspectJAwareAdvisorAutoProxyCreator,进入到 Spring 容器中,那该类作用是什么呢?看一下集成体系图

在这里插入图片描述

AspectJAwareAdvisorAutoProxyCreator的上上级父类 AbstractAutoProxyCreator 中的postProcessAfterInitialization 方法

// 参数 bean :为目标对象
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    
    
	if (bean != null) {
    
    
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		if (this.earlyProxyReferences.remove(cacheKey) != bean) {
    
    
			// 如果需要被增强,则 wrapIfNecessary 方法最终返回的就是一个 Proxy 对象
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}

通过断点方式观察,当 bean 是匹配切点表达式时, this.wrapIfNecessary (bean, beanName , cacheKey 返回的是一个 JDKDynamicAopProxy

在这里插入图片描述

可以再深入一点,对 wrapIfNecessary 再剖析一下,看看是不是我们熟知的通过 JDK 的
Proxy.newProxyInstance ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) 的方式创建的代理对象呢?经过如下一系列源码跟踪

在这里插入图片描述

动态代理的实现的选择,在调用getProxy () 方法时,我们可选用的 AopProxy 接口有两个实现类,如上图,这两种都是动态生成代理对象的方式,一种就是基于 JDK 的,一种是基于 Cglib 的。

在这里插入图片描述
在这里插入图片描述

JDK的动态代理代码,之前已经写过了,下面看一下 Cglib 基于超类的动态代理

Target target = new Target(); // 目标对象
Advices advices = new Advices(); // 通知对象
Enhancer enhancer = new Enhancer(); // 增强器对象
enhancer.setSuperclass(Target.class); // 增强器设置父类
// 增强器设置回调
enhancer.setCallback((MethodInterceptor )(o, method, objects, methodProxy) -> {
    
    
	advices.before();
	Object result = method.invoke(target, objects);
	advices.afterReturning();
	return result;
});	
// 创建代理对象
Target targetProxy = (Target) enhancer.create();
// 测试
String result = targetProxy.show("haohao");

三、基于注解方式配置的AOP

1. 注解方式配置AOP

Spring的 AOP 也提供了注解方式配置,使用相应的注解替代之前的 xml 配置, xml 配置 AOP 时,我们主要配置了三部分:目标类被 Spring 容器管理、通知类被 Spring 管理、通知与切点的织入(切面),如下:

在这里插入图片描述

目标类被Spring 容器管理、通知类被 Spring 管理

在这里插入图片描述

其实配置 aop 主要就是配置通知类中的哪个方法(通知类型)对应的切点表达式是什么

在这里插入图片描述

2. 注解方式AOP配置详解

各种注解方式通知类型

在这里插入图片描述

切点表达式的抽取,使用一个空方法,将切点表达式标注在空方法上,其他通知方法引用即可

在这里插入图片描述

3. 注解方式AOP原理剖析

之前在使用xml 配置 AOP 时,是借助的 Spring 的外部命名空间的加载方式完成的,使用注解配置后,就抛弃了< aop:config> 标签,而该标签最终加载了名为 AspectJAwareAdvisorAutoProxyCreator 的 BeanPostProcessor,最终,在该 BeanPostProcessor 中完成了代理对象的生成。

同样,从aspectj-autoproxy 标签的解析器入手

public class AopNamespaceHandler extends NamespaceHandlerSupport {
    
    

	@Override
	public void init() {
    
    
		registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
		registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
		registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
	}

}

而 AspectJAutoProxyBeanDefinitionParser 代码内部,最终也是执行了和 xml 方式 AOP 一样的代码

registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class , registry, source);

如果核心配置使用的是配置类的话,需要配置注解方式的 aop 自动代理

@Configuration
@ComponentScan("com.itheima.aop")
@EnableAspectJAutoProxy // 第三步
public class ApplicationContextConfig {
    
    
}

查看@EnableAspectJAutoProxy 源码,使用的也是 @Import 导入相关解析类

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({
    
    AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
    
    
    boolean proxyTargetClass() default false;

    boolean exposeProxy() default false;
}

使用@Import 导入的 AspectJAutoProxyRegistrar 源码,一路追踪下去,最终还是注册了AnnotationAwareAspectJAutoProxyCreator 这个类。

AspectJAutoProxyRegistrar 源码:

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    
    
    AspectJAutoProxyRegistrar() {
    
    
    }

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
        // ...
    }
}

进入AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry)

@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
    
    
	return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
}

@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
		BeanDefinitionRegistry registry, @Nullable Object source) {
    
    
		
	// 最终还是注册了AnnotationAwareAspectJAutoProxyCreator 这个类
	return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

xml方式与注解方式原理对比

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_36602071/article/details/129166235