Spring:IOC、AOP

IoC:即控制反转的意思,它是一种创建和获取对象的技术思想,依赖注入(DI)是实现这种技术的一种方式。传统开发过程中,我们需要通过new关键字来创建对象。使用IoC思想开发方式的话,我们不通过new关键字创建对象,而是通过IoC容器来帮我们实例化对象。 通过IoC的方式,可以大大降低对象之间的耦合度。
AOP:是面向切面编程,能够将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,以减少系统的重复代码,降低模块间的耦合度。Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理。

1 IOC和AOP实现机制

1.1 IOC实现机制

反射:Spring IOC容器利用Java的反射机制动态地加载类、创建对象实例及调用对象方法,反射允许在运行时检查类、方法、属性等信息,从而实现灵活的对象实例化和管理。依赖注入:IOC的核心概念是依赖注入,即容器负责管理应用程序组件之间的依赖关系。Spring通过构造函数注入、属性注入或方法注入,将组件之间的依赖关系描述在配置文件中或使用注解。设计模式 - 工厂模式:Spring IOC容器通常采用工厂模式来管理对象的创建和生命周期。容器作为工厂负责实例化Bean并管理它们的生命周期,将Bean的实例化过程交给容器来管理。容器实现:Spring IOC容器是实现IOC的核心,通常使用BeanFactory或ApplicationContext来管理Bean。BeanFactory是IOC容器的基本形式,提供基本的IOC功能;ApplicationContext是BeanFactory的扩展,并提供更多企业级功能。

1.2 AOP实现机制

Spring AOP的实现依赖于动态代理技术。动态代理是在运行时动态生成代理对象,而不是在编译时。它允许开发者在运行时指定要代理的接口和行为,从而实现在不修改源码的情况下增强方法的功能。

Spring AOP支持两种动态代理:
基于JDK的动态代理:使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。这种方式需要代理的类实现一个或多个接口。
基于CGLIB的动态代理:当被代理的类没有实现接口时,Spring会使用CGLIB库生成一个被代理类的子类作为代理。CGLIB(Code Generation Library)是一个第三方代码生成库,通过继承方式实现代理。

2 IOC

IoC(Inversion of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。通过IOC,程序实现了对象的解耦合,使得对象与对象之间是低耦合的,提高了程序的灵活性和可维护性。简单来说,Spring IOC就是让开发者不再需要手动创建对象,而是通过配置或注解等方式,让Spring容器来负责对象的创建、管理和依赖关系的注入

  • 控制:指的是对象创建(实例化、管理)的权力

  • 反转:控制权交给外部环境(Spring 框架、IoC 容器)

在传统的程序设计中,我们直接在对象内部通过new关键字来创建依赖的对象,这种方式会导致代码之间高度耦合,不利于测试和维护。而IoC的思想是,将这些对象的创建权交给Spring容器来管理,由Spring容器负责创建对象并注入依赖。这样,对象与对象之间的依赖关系就交由Spring来管理了,实现了程序的解耦合。在项目中,我们可以通过Spring的配置文件或者注解来定义Bean,然后由Spring容器来创建和管理这些Bean。在Mybatis中,我们可以将SqlSessionFactory、Mapper等对象的创建和依赖关系交由Spring来管理。

Spring框架中的IOC(Inverse of Control,控制反转)是一种设计原则,也是Spring框架的核心之一。==IOC的概念是指控制权的转移,即将对象的创建、依赖注入和生命周期管理等控制权交给了容器来管理,而不是由对象自己来控制。==这种控制反转的思想使得应用程序的组件之间的关系变得更加灵活、可维护和可测试。

在Spring框架中,IOC的实现主要通过依赖注入(Dependency Injection,DI)来实现。依赖注入是指容器负责在对象创建的同时,自动将对象所依赖的其他对象注入到它们之中,而不是由对象自己来创建或查找依赖的对象。

Spring框架通过XML配置文件、注解或Java代码来描述对象之间的依赖关系,然后由Spring容器在运行时根据这些配置来实现依赖注入。通过IOC容器管理对象之间的依赖关系,使得应用程序的组件解耦,提高了代码的灵活性和可维护性。

Spring的IOC容器通过控制对象之间的依赖关系,实现了对象之间的解耦,提高了代码的灵活性和可测试性,是Spring框架的核心特性之一。

[!note] 依赖注入(Dependency Injection,简称 DI)

  • 基于字段的依赖注入

public class Store {
@Autowired
private Item item;
}
  • 基于构造函数的依赖注入

  • 基于setter方法的依赖注入

2.1 为什么依赖注入不适合使用字段注入

依赖注入有三种主要的方式:构造函数注入、Setter方法注入和字段注入。虽然字段注入是最简洁的一种方式,但它也存在一些问题,这就是为什么在某些情况下不推荐使用字段注入的原因。

  1. 可测试性差: 字段注入使得依赖关系被硬编码到了类的字段中,这导致在进行单元测试时很难对这些依赖进行模拟或替换。因为字段注入的依赖是直接赋值给类的字段,而不是通过构造函数或Setter方法来传递,所以在测试时很难通过传入不同的依赖实例来进行测试。

  2. 难以追踪依赖关系: 使用字段注入时,依赖关系是通过字段直接注入到类中的,这使得依赖关系不够明确,很难追踪类与其依赖之间的关系。这可能导致代码的可读性和可维护性下降。

  3. 耦合性高: 字段注入使得依赖关系与类的实现紧密耦合在一起,从而增加了类与其依赖之间的耦合性。当需要修改依赖关系时,可能需要修改类的字段定义,这违反了开闭原则。

相比之下,构造函数注入和Setter方法注入提供了更好的解耦性和可测试性。构造函数注入将依赖关系通过构造函数传入,而Setter方法注入则通过Setter方法设置依赖。这两种方式都能够清晰地标识出类与其依赖之间的关系,提高了代码的可读性和可维护性,同时也更易于进行单元测试和模拟依赖。因此,在使用依赖注入时,推荐优先考虑使用构造函数注入或Setter方法注入,而尽量避免使用字段注入。

2.2 FactoryBean

Spring容器提供的一种可以扩展容器对象实例化逻辑的接口。其本身与其他注册到容器的对象一样,只是一个Bean,只不过,这种类型的Bean本身就是生产对象的工厂(Factory)。

public interface FactoryBean{
//返回该FactoryBean生产的对象实例,实现该方法给出自己的对象实例化逻辑
Object getObject() throws Exception;
//返回getObject方法所返回的对象类型
Class getObjectType();
//getObject生产的对象是否以singleton形式存在于容器中
boolean isSingleton();
}

FactoryBean类型的bean定义,通过正常的id引用,容器返回的是FactoryBean所生产的对应类型,而非FactoryBean实现本身。可以通过在bean定义的id之前加前缀&来取得FactoryBean本身。

3 AOP

[!node]
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

AOP(Aspect Oriented Programming)即面向切面编程,AOP 是 OOP(面向对象编程)的一种延续,二者互补,并不对立。
AOP 的目的是将横切关注点(如日志记录、事务管理、权限控制、接口限流、接口幂等等)从核心业务逻辑中分离出来,通过动态代理、字节码操作等技术,实现代码的复用和解耦,提高代码的可维护性和可扩展性。
OOP 的目的是将业务逻辑按照对象的属性和行为进行封装,通过类、对象、继承、多态等概念,实现代码的模块化和层次化(也能实现代码的复用),提高代码的可读性和可维护性。

AOP,即面向切面编程,是Spring提供的另一个重要特性。它允许开发者在不修改原有业务逻辑的情况下,增强或扩展程序的功能。AOP通过预定义的切点(例如方法执行前、方法执行后等)来织入切面逻辑,从而实现对原有功能的增强。在项目中,我们可以使用AOP来实现日志记录、事务管理、安全检查等功能。在Mybatis中,我们可以使用AOP来实现对数据库操作的日志记录、性能监控等功能。

3.1 相关概念

术语 含义
目标(Target) 被通知的对象
代理(Proxy) 向目标对象应用通知之后创建的代理对象
连接点(JoinPoint) 目标对象的所属类中,定义的所有方法均为连接点
切入点(Pointcut) 被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点)
通知(Advice) 增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情
切面(Aspect) 切入点(Pointcut)+通知(Advice),切面是一个模块化单元,它封装了横切关注点,并定义了在何处、何时应用这些关注点。在Spring框架中,切面通常以Java类的形式存在
Weaving(织入) 将通知应用到目标对象,进而生成代理对象的过程动作

3.2 应用场景

  • 日志记录:自定义日志记录注解,利用 AOP,一行代码即可实现日志记录。

  • 性能统计:利用 AOP 在目标方法的执行前后统计方法的执行时间,方便优化和分析。

  • 事务管理:@Transactional 注解可以让 Spring 为我们进行事务管理比如回滚异常操作,免去了重复的事务管理逻辑。@Transactional注解就是基于 AOP 实现的。

  • 权限控制:利用 AOP 在目标方法执行前判断用户是否具备所需要的权限,如果具备,就执行目标方法,否则就不执行。例如,SpringSecurity 利用@PreAuthorize 注解一行代码即可自定义权限校验。

  • 接口限流:利用 AOP 在目标方法执行前通过具体的限流算法和实现对请求进行限流处理。

  • 缓存管理:利用 AOP 在目标方法执行前后进行缓存的读取和更新

3.3 代理方式

AOP 的常见实现方式有动态代理、字节码操作等方式。

动态代理机制和字节码生成都是在运行期间为目标对象生成一个代理对象,而将横切逻辑织入到这个代理对象中,系统最终使用的是织入了横切逻辑的代理对象,而不是真正的目标对象。

基于代理的AOP: Spring使用动态代理来实现AOP。当一个类被AOP代理后,方法调用会被拦截,从而允许切面在方法调用前、后或者出现异常时执行额外的逻辑。Spring中的基于代理的AOP主要使用JDK动态代理和CGLIB动态代理来实现。

  1. 动态代理

  2. 动态字节码增强(CGLIB, Code Generation Library)
    CGLIB可以对实现了某种接口的类,或者没有实现任何接口的类进行扩展

3.3.1 动态代理

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了。

动态代理(Dynamic Proxy)机制,可以在运行期间,为相应的接口(interface)动态生成对应的代理对象。

image.png

3.3.2 Cgllib

要代理的对象,没有实现接口,Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理
CGLIB可以对实现了某种接口的类,或者没有实现任何接口的类进行扩展。

3.3.3 Spring AOP 和 AspectJ AOP 有什么区别?

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,

如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。

3.3.4 Spring AOP

public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {  
Object cacheKey = this.getCacheKey(beanClass, beanName);
if (beanName == null || !this.targetSourcedBeans.contains(beanName)) {
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}

if (this.isInfrastructureClass(beanClass) || this.shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null; }
}

if (beanName != null) {
TargetSource targetSource = this.getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
this.targetSourcedBeans.add(beanName);
Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = this.createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
}

return null;
}
protected boolean shouldSkip(Class<?> beanClass, String beanName) {  
//获取切面信息
List<Advisor> candidateAdvisors = this.findCandidateAdvisors();
Iterator var4 = candidateAdvisors.iterator();

Advisor advisor;
do {
if (!var4.hasNext()) {
return super.shouldSkip(beanClass, beanName);
}

advisor = (Advisor)var4.next();
} while(!(advisor instanceof AspectJPointcutAdvisor) || !((AbstractAspectJAdvice)advisor.getAdvice()).getAspectName().equals(beanName));

return true;}
protected List<Advisor> findCandidateAdvisors() {  
List<Advisor> advisors = super.findCandidateAdvisors();
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
return advisors;
}
public List<Advisor> buildAspectJAdvisors() {  
List<String> aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
synchronized(this) {
aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
List<Advisor> advisors = new LinkedList();
List<String> aspectNames = new LinkedList();

String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);
String[] var18 = beanNames;
int var19 = beanNames.length;

for(int var7 = 0; var7 < var19; ++var7) {
String beanName = var18[var7];
if (this.isEligibleBean(beanName)) {
Class<?> beanType = this.beanFactory.getType(beanName);
//判断是否为切面
if (beanType != null && this.advisorFactory.isAspect(beanType)) {
aspectNames.add(beanName);
AspectMetadata amd = new AspectMetadata(beanType, beanName);
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
//获取所有的切面列表
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
if (this.beanFactory.isSingleton(beanName)) {
this.advisorsCache.put(beanName, classAdvisors);
} else {
this.aspectFactoryCache.put(beanName, factory);
}

advisors.addAll(classAdvisors);
} else {
if (this.beanFactory.isSingleton(beanName)) {
throw new IllegalArgumentException("Bean with name '" + beanName + "' is a singleton, but aspect instantiation model is not singleton");
}

MetadataAwareAspectInstanceFactory factory = new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
this.aspectFactoryCache.put(beanName, factory);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
}
}

this.aspectBeanNames = aspectNames;
return advisors;
}
}
}
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrderInAspect, String aspectName) {  
this.validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
AspectJExpressionPointcut expressionPointcut = this.getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
return expressionPointcut == null ? null : new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

image.png

4 参考

Spring AOP - 注解方式使用介绍(长文详解) - 掘金 (juejin.cn)
深度剖析Spring AOP源码,图文详解,小白也能看明白。-CSDN博客动态代理异常com.sun.proxy.$Proxy cannot be cast to-CSDN博客面试官:谈谈你对 IoC 和 AOP 的理解! (qq.com)