ToB企服应用市场:ToB评测及商务社交产业平台

标题: Spring框架IoC核心详解 [打印本页]

作者: 科技颠覆者    时间: 2024-12-12 08:37
标题: Spring框架IoC核心详解
介绍

IoC(Inversion of Control:控制反转) 是一种设计思想,而不是一个具体的技能实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理,由Spring容器管理bean的整个生命周期。通俗来说就是IoC是设计思想,DI是实现方式。
通过反射实现对其他对象的控制,包括初始化、创建、销毁等,解放手动创建对象的过程,同时降低类之间的耦合度。
在 Spring 中, IoC container 是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象,根据BeanName或者Type获取对象
为何是反转,哪些方面反转了?
有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依靠对象,也就是正转;而反转则是由容器来帮忙创建及注入依靠对象;
控制:指的是对象创建(实例化、管理)的权力
反转:控制权交给外部环境(Spring 框架、IoC 容器)
IOC的利益

ioc的思想最核心的地方在于,资源不由利用资源者管理,而由不利用资源的第三方管理,这可以带来很多利益。
好比在实际项目中一个 Service 类大概依靠了很多其他的类,如果我们需要实例化这个 Service,大概要每次都要搞清这个 Service 所有底层类的构造函数,这就变得复杂了。而如果利用 IoC 的话,只需要配置好,然后在需要的地方引用就行了,这大大增长了项目的可维护性且降低了开发难度。
IOC配置的四种方式

Spring - 概述 一文中已经给出了三种配置方式,这里再总结下;总体上目前的主流方式是 注解 + Java 配置
xml 配置

顾名思义,就是将bean的信息配置.xml文件里,通过Spring加载文件来创建bean。这种方式出现很多早前的SSM项目中,将第三方类库或者一些配置工具类都以这种方式进行配置,重要原因是由于第三方类不支持Spring注解。
举例
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6.    
  7.     <bean id="userService" >
  8.         <property name="userDao" ref="userDao"/>
  9.         
  10.     </bean>
  11.    
  12. </beans>
复制代码
Java 配置

将类的创建交给我们配置的JavcConfig类来完成,Spring只负责维护和管理,采用纯Java创建方式。其本质上就是把在XML上的配置声明转移到Java配置类中
举例
  1. @Configuration
  2. public class BeansConfig {
  3.     @Bean("userDao")
  4.     public UserDaoImpl userDao() {
  5.         return new UserDaoImpl();
  6.     }
  7.     @Bean("userService")
  8.     public UserServiceImpl userService() {
  9.         UserServiceImpl userService = new UserServiceImpl();
  10.         userService.setUserDao(userDao());
  11.         return userService;
  12.     }
  13. }
复制代码
注解配置

通过在类上加注解的方式,来声明一个类交给Spring管理,Spring会主动扫描带有@Component,@Controller,@Service,@Repository这四个注解的类,然后帮我们创建并管理,条件是需要先配置Spring的注解扫描器。
举例
  1. @Service
  2. public class UserServiceImpl {
  3.     @Autowired
  4.     private UserDaoImpl userDao;
  5.     public List<User> findUserList() {
  6.         return userDao.findUserList();
  7.     }
  8. }
复制代码
依靠注入DI的方式

其原理是将对象的依靠关系由外部容器来管理和注入。这样,对象只需要关注自身的核心功能,而不需要关心如何获取依靠对象。它的目的是解耦组件之间的依靠关系,进步代码的机动性、可维护性和可测试性。
在Spring创建对象的过程中,把对象依靠的属性注入到对象中。
依靠注入重要有三种方式:构造器注入,Setter方式注入(属性注入)、基于字段的依靠注入
此中基于字段的依靠注入被广泛利用,但是 idea 或者其他静态代码分析工具会给出提示信息,不推荐利用。
Setter方式注入

在基于 setter 的依靠注入中,setter 方法被标注为 @Autowired。一旦利用无参数构造函数或无参数静态工厂方法实例化 Bean,为了注入 Bean 的依靠项,Spring 容器将调用这些 setter 方法。
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6.    
  7.     <bean id="userService" >
  8.         <property name="userDao" ref="userDao"/>
  9.         
  10.     </bean>
  11.    
  12. </beans>
复制代码
本质上包含两步:

  1. @Component
  2. public class UserServiceImpl {
  3.     private UserDao userDao;
  4.     @Autowired //这个 @Autowired可以省略
  5.     public void setUserDao(UserDao userDao) {
  6.         this.userDao = userDao;
  7.     }
  8. }
复制代码
将@Autowired写在被注入的成员变量上,setter或者构造器上,就不用再xml文件中配置了。
基于属性的依靠注入

在基于属性的依靠注入中,以@Autowired(主动注入)注解注入为例,修饰符有三个属性:Constructor,byType,byName。默认按照byType注入。一旦类被实例化,Spring 容器将设置这些字段。
  1. @Component
  2. public class FieldBasedInjection {
  3.     @Autowired
  4.     private InjectedBean injectedBean;
  5. }
复制代码
大概存在的缺点

正如所看到的,这是依靠注入最干净的方法,因为它制止了添加样板代码,并且不需要声明类的构造函数。代码看起来很干净简洁,但是正如代码检查器已经向我们暗示的那样,这种方法有一些缺点:
@Autowired和@Resource以及@Inject等注解注的区别

@Autowired

在Spring 2.5 引入了 @Autowired 注解
  1. @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface Autowired {
  5.   boolean required() default true;//默认是true
  6. }
复制代码
从Autowired注解源码上看,可以利用在下面这些地方:
  1. @Target(ElementType.CONSTRUCTOR) #构造函数
  2. @Target(ElementType.METHOD) #方法
  3. @Target(ElementType.PARAMETER) #方法参数
  4. @Target(ElementType.FIELD) #字段、枚举的常量
  5. @Target(ElementType.ANNOTATION_TYPE) #注解
复制代码
在SpringBoot中也可以利用@Bean + @Autowired进行组件注入,将@Autowired加到参数上,其实也可以省略。
  1. @Bean
  2. public Person getPerson(@Autowired Car car){
  3. return new Person();
  4. }
  5. // @Autowired 其实也可以省略
复制代码
@Resource

Resource注解源码:
  1. @Target({TYPE, FIELD, METHOD})
  2. @Retention(RUNTIME)
  3. public @interface Resource {
  4.     String name() default "";//指定注入指定名称的组件,name 的作用类似 @Qualifier
  5.     // 其他省略
  6. }
复制代码
从Resource注解源码上看,可以利用在下面这些地方:
  1. @Target(ElementType.TYPE) #接口、类、枚举、注解
  2. @Target(ElementType.FIELD) #字段、枚举的常量
  3. @Target(ElementType.METHOD) #方法
复制代码
@Inject

  1. @Target({ METHOD, CONSTRUCTOR, FIELD })
  2. @Retention(RUNTIME)
  3. @Documented
  4. public @interface Inject {}
复制代码
从Inject注解源码上看,可以利用在下面这些地方:
  1. @Target(ElementType.CONSTRUCTOR) #构造函数
  2. @Target(ElementType.METHOD) #方法
  3. @Target(ElementType.FIELD) #字段、枚举的常量
复制代码
构造器注入

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6.    
  7.     <bean id="userService" >
  8.         <property name="userDao" ref="userDao"/>
  9.         
  10.     </bean>
  11.    
  12. </beans>
复制代码
在基于构造函数的依靠注入中,类构造函数被标注为 @Autowired,并包含了许多与要注入的对象相关的参数。
  1. @Component
  2. public class ConstructorBasedInjection {
  3.    
  4.     private final InjectedBean injectedBean;
  5.    
  6.     @Autowired //当然,这个@Autowired可以省略
  7.     public ConstructorBasedInjection(InjectedBean injectedBean) {        
  8.         this.injectedBean = injectedBean;   
  9.     }
  10. }
复制代码
将@Autowired写在被注入的成员变量上,setter或者构造器上,就不用再xml文件中配置了。
注意:不能提供无参构造方法,否则Springboot默认会加载无参的构造方法,Bean实例对象会为null。并且构造器的权限需要为public
为什么建议利用构造器注入

一样平常推荐构造器注入,为什么?Spring文档里的解释如下:
The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.
翻译一下就是:这个构造器注入的方式能够保证注入的组件不可变,并且确保需要的依靠不为空。此外,构造器注入的依靠总是能够在返回客户端(组件)代码的时候保证完全初始化的状态。
如果利用setter注入,缺点显而易见,对于IOC容器以外的环境,除了利用反射来提供它需要的依靠之外,无法复用该实现类。而且将一直是个潜在的隐患,因为你不调用将一直无法发现NPE的存在
  1. // 这里只是模拟一下,正常来说我们只会暴露接口给客户端,不会暴露实现。
  2. UserServiceImpl userService = newUserServiceImpl();
  3. userService.findUserList();// -> NullPointerException, 潜在的隐患
复制代码
总结:对于必须的依靠,建议利用基于构造函数的注入,设置它们为不可变的,并防止它们为 null。对于可选的依靠项,建议利用基于 setter 的注入。
借助lombok简化

然而,手动编写构造函数大概会使代码显得冗长不雅观。Lombok 是一个非常好的工具,它能够通过注解主动生成构造函数,从而使代码更加简洁和优雅。
Lombok提供的@AllArgsConstructor注解可以资助我们主动生成包含所有字段的构造函数。此外,@RequiredArgsConstructor注解可以生成包含所有final字段和带有@NonNull注解字段的构造函数,这通常是我们在依靠注入中需要的。
  1. @Component
  2. @RequiredArgsConstructor // 自动生成包含final字段的构造函数
  3. public class ConstructorBasedInjection {
  4.    
  5.     private final InjectedBean injectedBean;
  6.    
  7. }
复制代码
Bean注入的七种方式

Bean的作用域

如何配置 bean 的作用域呢?

xml 方式:
  1. [/code]注解方式:
  2. [code]@Bean
  3. @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
  4. public Person personPrototype() {
  5.     return new Person();
  6. }
复制代码
Bean 是线程安全的吗

Spring 框架中的 Bean 是否线程安全,取决于其作用域和状态。
这里以最常用的两种作用域 prototype 和 singleton 为例介绍。几乎所有场景的 Bean 作用域都是利用默认的 singleton ,重点关注 singleton 作用域即可。
prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争标题,以是不存在线程安全标题。singleton 作用域下,IoC 容器中只有唯一的 bean 实例,大概会存在资源竞争标题(取决于 Bean 是否有状态)。如果这个 bean 是有状态的话,那就存在线程安全标题(有状态 Bean 是指包含可变的成员变量的对象)。
不过,大部门 Bean 实际都是无状态(没有定义可变的成员变量)的(好比 Dao、Service),这种情况下, Bean 是线程安全的。
对于有状态单例 Bean 的线程安全标题,常见的有两种解决办法:
IOC的结构设计


Spring Bean的创建是典型的工厂模式,这一系列的Bean工厂,也即IOC容器为开发者管理对象间的依靠关系提供了很多便利和基础服务,在Spring中有许多的IOC容器的实现供用户选择和利用,这是IOC容器的基础;在顶层的结构设计重要围绕着BeanFactory和xxxRegistry进行:

BeanFactory

BeanFactory作为最顶层的一个接口类,它定义了IOC容器的基本功能规范,BeanFactory 有三个子类:ListableBeanFactory、HierarchicalBeanFactory 和AutowireCapableBeanFactory。先看下BeanFactory接口:
  1. public interface BeanFactory {   
  2.       
  3.     //用于取消引用实例并将其与FactoryBean创建的bean区分开来。例如,如果命名的bean是FactoryBean,则获取将返回Factory,而不是Factory返回的实例。
  4.     String FACTORY_BEAN_PREFIX = "&";
  5.         
  6.     //根据bean的名字和Class类型等来得到bean实例   
  7.     Object getBean(String name) throws BeansException;   
  8.     Object getBean(String name, Class requiredType) throws BeansException;   
  9.     Object getBean(String name, Object... args) throws BeansException;
  10.     <T> T getBean(Class<T> requiredType) throws BeansException;
  11.     <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
  12.     //返回指定bean的Provider
  13.     <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
  14.     <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
  15.     //检查工厂中是否包含给定name的bean,或者外部注册的bean
  16.     boolean containsBean(String name);
  17.     //检查所给定name的bean是否为单例/原型
  18.     boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
  19.     boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
  20.     //判断所给name的类型与type是否匹配
  21.     boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
  22.     boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
  23.     //获取给定name的bean的类型
  24.     @Nullable
  25.     Class<?> getType(String name) throws NoSuchBeanDefinitionException;
  26.     //返回给定name的bean的别名
  27.     String[] getAliases(String name);
  28.      
  29. }
复制代码
BeanFactory的其他接口重要是为了区分在 Spring 内部在操作过程中对象的传递和转化过程中,对对象的数据访问所做的限制。
BeanRegistry

Spring 配置文件中每一个节点元素在 Spring 容器里都通过一个 BeanDefinition 对象表示,它描述了 Bean 的配置信息。而 BeanDefinitionRegistry 接口提供了向容器手工注册 BeanDefinition 对象的方法。
BeanDefinition

各种Bean对象及其相互的关系
BeanDefinition

SpringIOC容器管理了定义的各种Bean对象及其相互的关系,Bean对象在Spring实现中是以BeanDefinition来描述的,其继承体系如下

BeanDefinitionReader

Bean 的解析过程非常复杂,功能被分的很细,因为这里需要被扩展的地方很多,必须保证有充足的机动性,以应对大概的变化。Bean 的解析重要就是对 Spring 配置文件的解析。这个解析过程重要通过下图中的类完成:

BeanDefinitionHolder

BeanDefinitionHolder 是BeanDefination的包装类,用来存储BeanDefinition,name以及aliases等

ApplicationContext

IoC容器的接口类是ApplicationContext,很显然它一定继承BeanFactory对Bean规范(最基本的ioc容器的实现)进行定义。而ApplicationContext表示的是应用的上下文,除了对Bean的管理外,还至少应该包含了
接口设计

ApplicationContext整体结构:

接口的实现

ApplicationContext接口的实现,关键的点在于,差别Bean的配置方式(好比xml,groovy,annotation等)有着差别的资源加载方式,这便衍生除了众多ApplicationContext的实现类。

面试题专栏

Java面试题专栏已上线,接待访问。
那么可以私信我,我会尽我所能资助你。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4