Spring 揭秘之Spring AOP一世(2)织入实现

2024-01-10 00:08

本文主要是介绍Spring 揭秘之Spring AOP一世(2)织入实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • Spring AOP一世
    • Spring AOP的织入
      • 与ProxyFactory打交道
        • 基于接口的代理
        • 基于类的代理
        • Introduction的织入
      • ProxyFactory的本质
      • 容器中的织入器——ProxyFactoryBean
        • ProxyFactoryBean的本质
        • ProxyFactoryBean的使用
      • 加快织入的自动化进程
        • 自动代理得以实现的原理
        • 可用的AutoProxyCreator
        • 扩展AutoProxyCreator
    • TargetSource
      • 可用的TargetSource
      • 自定义TargetSource
    • 小结

Spring AOP一世

Spring AOP的织入

如今,所有基本概念都已经到位,只差将其拼装在一起,也就是织入。还记得我们曾学习过的AOP和OOP在系统实现当中的关系吗?我们需要将AOP织入到OOP中,以便让我们的准备好的一起,行动起来;这个角色就是织入器啦;

Spring AOP采用ProxyFactory类作为织入器;我们就从最基本的织入器开始吧!

所谓织入,就是在Joinpoint(来自OOP业务系统)处加入Advice(来自AOP)所表示的逻辑;

其中Joinpoint由Pointcut进行匹配,于是我们只需要将Pointcut、Advice和业务系统对象交给织入器,然后Magic Happens!织入过程就完成啦;

与ProxyFactory打交道

Pointcut和Advice由Advisor组合;所以我们只要将Advisor和业务对象交给ProxyFactory,然后获得增强版的业务对象(也就是所谓的代理对象)即可;

除了传入Advisor,也可以传入Advice,ProxyFactory将根据传入的Advice创建相应的Advisor,当然,使用的Pointcut为Pointcut.TRUE;

我们可以通过更多的属性设置来控制ProxyFactory的织入行为。Spring通过代理模式实现AOP的时候,采用动态代理和CGLIB两种机制,分别对实现某些接口和未实现接口的目标类生成代理对象;这里所谓的控制,就是控制Spring AOP采用哪种方式进行织入;

基于接口的代理

默认情况下,只要目标对象实现了相应接口,就会对目标对象进行基于接口的代理;我们也可以通过ProxyFactory的setInterfaces()方法指定具体的接口;

需要注意的是,我们可以将代理对象强转为接口类型(也就是通过接口引用代理对象),但是不能将代理对象强转为被代理类,即便它们都实现了相同接口,这也很好理解嘛;

基于类的代理

如果目标对象没有实现任何接口,默认情况下ProxyFactory会对目标类进行基于类的代理,即使用CGLIB;

需要注意的是,即便目标对象也实现了某个接口,我们也可以强制ProxyFactory使用基于类的代理;

总的来说,如果下列条件满足其一,就会使用基于类的代理:

  1. 目标对象没有实现任何接口,此时直接使用基于类的代理;
  2. proxyTargetClass属性为true;
  3. optimize属性为true;
Introduction的织入

Introduction可以为已经存在的对象类型添加新的行为,只能应用于对象级别的拦截,而不是方法级别的拦截;在进行Introduction的织入过程中,不需要指定Pointcut(用于匹配方法),只需指定目标接口类型;

需要注意的是,对Introduction进行织入,新添加的接口类型必须通过setInterfaces指定;原来的目标对象可以自由选择织入方式;

ProxyFactory的本质

知其表而不知其里,充其量也只能算是一个画匠,而不是画师;只懂得如何使用API,而不知道这些API为何如此设计,终将无法迈出从“画匠”到“画师”的那一步;

认清其本质不仅让我们清楚其如何实现,帮助我们获取系统设计的宝贵经验,还可以让我们更加灵活地使用它;

ProxyFactory的根是AopProxy;Spring AOP使用AopProxy对不同的代理实现机制进行了适度的抽象;目前,Spring提供基于JDK的动态代理和CGLIB这两种机制的AopProxy实现:
[外链图片转存失败(img-gVxGmJ4P-1562428071220)(]?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NseDMzMjA2MTI1NDA=,size_16,color_FFFFFF,t_70)

AopProxyFactory通过抽象工厂模式根据传入的AdvisedSupport实例提供的信息,决定创建什么类型的AopProxy:如果isOptimize或者isProxyTargetClass为true,或者目标对象没有实现任何接口,那么就用cglib生成代理对象;否则使用动态代理;

AdvisedSupport实际上就是一个生成代理对象所需信息的载体:
在这里插入图片描述

ProxyConfig类记录生成代理对象的控制信息;Advised记录生成代理对象所需要的必要信息,如目标类、Advise、Advisor;

其中,ProxyConfig有以下属性:

  1. proxyTargetClass:默认为false,即使用基于接口的代理;
  2. optimize:该属性为true的时候,proxyFactory将使用CGLIB进行代理;默认为false;实际上,它还指示是否对代理对象进行进一步优化措施;
  3. opaque:控制代理对象是否可以被强转为Advised;默认为false;
  4. exposeProxy:是否将当前代理对象绑定到ThreadLocal,如果目标对象需要访问代理对象,则可以通过AopContext.currentProxy()获得;默认为false;
  5. frozen:如果为true,那么代理对象生成的信息配置完成,那么就不能更改;

到这里,再上一图:
在这里插入图片描述

前面我们提到过,ProxyFactory是基本的织入器,那么接下来看看织入器体系以及其他场景下的织入器:
在这里插入图片描述

容器中的织入器——ProxyFactoryBean

使用ProxyFactory,我们可以独立使用Spring AOP;所谓独立就是说独立于Spring IoC容器;但是Spring IoC、Spring AOP作为“质量三角”之二,为什么不把它们组合在一起呢?

实际上,合则两利;因为AOP王国里的一切终究是要寄生到OOP王国里的,而Spring IoC 容器对业务对象之间的解耦本领,使得我们构建OOP王国变得简单,那么将Spring AOP同Spring IoC相结合也是“大势所趋”;

Spring IoC 容器内部使用ProxyFactoryBean作为织入器,使用方面同ProxyFactory没有什么大的区别;我们在看清了ProxyFactory的本质之后,自然也要对ProxyFactoryBean一探究竟;

ProxyFactoryBean的本质

ProxyFactoryBean=Proxy+FactoryBean;前者表示其功能为产生Proxy,这是织入器的核心功能;后者表示其本质为FactoryBean;所谓FactoryBean是说,如果容器中的某个对象持有FactoryBean对象引用,那么其获得的不是FactoryBean本身,而是其getObject()方法返回的对象;

FactoryBean创建对象的时候,需要指出其scope:Singleleton或者Prototype;如果是Singleleton的话,FactoryBean就会缓存返回值;

ProxyFactoryBean的使用

同ProxyFactory一样,大部分可以设置的项目都是相同的,因为它们都继承自同一个父类;但是也有一些个性设置:

  1. proxyInterfaces:指定多个接口类型;如果目标对象实现了某个或者多个接口,那么即便我们不显式指定接口,该类也会自动检测,因为有一个属性为autodetectInterfaces,默认值为true;
  2. interceptorNames:指定一组Advisor、Advice或者Interceptor;如果没有通过相应的设置目标对象的方法,明确为ProxyFactoryBean设定目标对象,那么最后一个位置可以放置目标对象;可以使用通配符让ProxyFactoryBean在容器中搜索符合条件的目标对象;
  3. Singleton:属于FactoryBean的属性;

目前为止,AOP中的一切内容都可以在容器中使用啦;从Pointcut到Advice、Advisor;我们可以将生成的代理对象直接注入到需要它的地方;此时,需要注意的是,我们应该依赖代理对象而不是目标对象,否则将不会产生任何拦截效果;

加快织入的自动化进程

在IoC容器中使用ProxyFactoryBean进行织入固然不错,但是如果要对每个目标对象都给出他们各自对应的ProxyFactoryBean配置,太过于麻烦;所以Spring AOP给出了自动代理机制以解决配置工作量较大的问题;

自动代理得以实现的原理

Spring AOP的自动代理依托于ApplicationContext之上,更为准确的说是IoC容器的BeanPostProcessor之上;还记得BeanPostProcessor吗?知识传送门;我们只需要在对象初始化的时候,为其生成代理对象并返回,即可实现织入过程;

所以,所谓织入,其实就是为目标对象生成代理对象的过程;该过程需要织入的内容以及寻找目标对象的方法;

可用的AutoProxyCreator

Spring AOP提供了BeanNameAutoProxyCreator和DefaultAdvisorAutoProxyCreator这两个常用类;

  1. BeanNameAutoProxyCreator:指定一组目标对象的beanName,将一组拦截器应用到这些目标对象之上;在指定BeanName的时候,可使用*通配符;
  2. DefaultAdvisorAutoProxyCreator:如果说BeanNameAutoProxyCreator属于半自动配置的话(毕竟还需要手动指定beanName和拦截器),那么DefaultAdvisorAutoProxyCreator就属于全自动配置了;只需要在容器(ApplicationContext)中声明一个该类的实例,然后只需要描述各种Advisor,容器启动后就会完成织入;
扩展AutoProxyCreator

只需要继承AbstractAutoProxyCreator或者AbstractAdvisorAutoProxyCreator即可,不需要什么都重新开始;

所有的AutoProxyCreator都是InstantiationAwareBeanPostProcessor,这种类型的BeanPostProcessor略有不同;当容器中检测到有该类型的BeanPostProcessor时,会直接通过该类中的逻辑构造对象实例并返回,并不会走正常的对象实例化流程;
在这里插入图片描述

所以呢,子类只需要继承合适的父类,然后提供规则匹配一类的逻辑即可;当然,如有必要,进行覆盖即可;

TargetSource

Spring AOP内部会对目标对象做统一的封装——TargetSource。也就是TargetSource代表了调用链终点的目标对象;

它的特性之一就是每次的方法调用都会触发TargetSource的getTarget()方法,getTarget()方法会返回具体的目标对象;

可用的TargetSource

  1. SingletonTargetSource,最为常用的TargetSource实现类;在使用ProxyFactoryBean的setTarget()方法设置完目标对象后,内部处理实际上就是用SingletonTargetSource进行包装;每次方法调用时,都返回同一个目标对象,符合Singleton的语义;
  2. PrototypeTargetSource,符合Prototype的语义,即每次方法调用时都返回新创建的目标对象;此时,目标对象的bean定义声明的Scope必须为prototype;
  3. HotSwappableTargetSource:比较实用的TargetSource实现。它允许我们在系统运行时,根据某种条件动态替换目标对象类的具体实现;通过其swap方法,我们可以使用新的目标对象替换原来的目标对象;
  4. CommonsPoolTargetSource:为目标对象创建一个对象池,然后每次请求对象时,便从对象池中返回;
  5. ThreadLocalTargetSource:将目标对象同线程相关联;不同线程返回不同的目标对象;

自定义TargetSource

直接扩展TargetSource接口即可;

public interface TargetSource extends TargetClassAware {/*** Return the type of targets returned by this {@link TargetSource}.* <p>Can return {@code null}, although certain usages of a {@code TargetSource}* might just work with a predetermined target class.* @return the type of targets returned by this {@link TargetSource}*/@Override@NullableClass<?> getTargetClass();/*** Will all calls to {@link #getTarget()} return the same object?* <p>In that case, there will be no need to invoke {@link #releaseTarget(Object)},* and the AOP framework can cache the return value of {@link #getTarget()}.* @return {@code true} if the target is immutable* @see #getTarget*/boolean isStatic();/*** Return a target instance. Invoked immediately before the* AOP framework calls the "target" of an AOP method invocation.* @return the target object which contains the joinpoint,* or {@code null} if there is no actual target instance* @throws Exception if the target object can't be resolved*/@NullableObject getTarget() throws Exception;/*** Release the given target object obtained from the* {@link #getTarget()} method, if any.* @param target object obtained from a call to {@link #getTarget()}* @throws Exception if the object can't be released*/void releaseTarget(Object target) throws Exception;}

小结

这一节中,我们了解了Spring AOP对AOP的各种概念的实现;它们是整个框架的基础;体现了Spring框架的稳定性和灵活性;

接下来,我们将看看Spring AOP一世的升级版——Spring AOP 二世;

这篇关于Spring 揭秘之Spring AOP一世(2)织入实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/588888

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu