1.IOC、bean


1.IOC是什么?
  IOC(Inversion of Control控制反转):一种设计思想,就是将原本在程序中手动创建对象的控制权,交给IOC容器来管理,并由IOC容器完成对象的注入。
  BeanFactory是Spring里面最底层的接口,是Ioc的核心,定义了Ioc的基本功能,包含了各种Bean的定义、加载、实例化,依赖注入和生命周期管理;
  将对象之间的相互依赖关系交给Ioc容器来管理,由Ioc容器完成对象的注入。这样设计的好处是降低了程序里面对象与对象之间的耦合性,资源变得容易管理,使得程序的整个体系结构变得更加灵活。

DI:IoC最常见最合理的实现方式叫做依赖注入(Dependency Injection,简称 DI),DI将在Ioc容器内有依赖关系的bean进行关系绑定。
目标:充分解耦

 
  Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。


2.IOC实现原理:
  XML解析 + 工厂设计模式 + 反射(令耦合度尽可能的降低到最小)
  在配置文件(例如 Bean.xml)中,对各个对象以及它们之间的依赖关系进行配置;
  我们可以把 IoC 容器当做一个工厂,这个工厂的产品就是 Spring Bean;
  容器启动时会加载并解析这些配置文件,得到对象的基本信息以及它们之间的依赖关系;
  Xml解析技术读取配置文件
  通过全类名反射创建对象,放到容器中
  工厂模式返回Bean对象(getBean方法)
 
3.IOC启动(工作)流程:
  第一步:准备上下文环境,即ApplicationContext,这也是Spring的核心,底层主要实现的接口是BeanFactory,也是生成Bean的核心接口。
  第二步:扫描所有的XML文件以及注解,扫描Xml中一个个的Bean定义,得到一个个的BeanDefinition对象,记录了是哪些Bean,它的依赖是什么,名字是什么等属性。
  第三步:根据Bean的定义生成相应的Bean(ApplicationContext会先生成DefaultListableBeanFactory这样一个Bean工厂,这个工厂会根据BeanDefinition来创建单例Bean)
  第四步:将生成的Bean放入Spring容器中,即HashMap中,此时就可以使用这些Bean


4.什么是Bean
  Bean:被IOC容器管理的对象。


5.Spring如何创建一个bean对象?
  类—>无参构造方法(推断构造方法)—>得到一个对象—>通过依赖注入为属性赋值—>初始化前(a())通过@PostConstruct—>初始化(InitializingBean)—>初始化后(AOP)--->代理对象---》Bean—>放入Map单例池—>Bean对象
  (1)利用该类的构造方法来实例化得到一个对象(但是如何一个类中有多个构造方法, Spring则会进行选择,这个叫做推断构造方法)。
  (2)得到一个对象后,Spring会判断该对象中是否存在被@Autowired注解了的属性,把这些属性找出来并由Spring进行赋值(依赖注入)。
  (3)依赖注入后,Spring会判断该对象是否实现了BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了,就表示当前对象必须实现该接口中所定义的setBeanName()、setBeanClassLoader()、setBeanFactory()方法,那Spring就会调用这些方法并传入相应的参数(Aware回调)。
  (4)Aware回调后,Spring会判断该对象中是否存在某个方法被@PostConstruct注解了,如果存在,Spring会调用当前对象的此方法(初始化前)。
  (5)紧接着,Spring会判断该对象是否实现了InitializingBean接口,如果实现了,就表示当前对象必须实现该接口中的afterPropertiesSet()方法,那Spring就会调用当前对象中的afterPropertiesSet()方法(初始化)。
  (6)最后,Spring会判断当前对象需不需要进行AOP,如果不需要那么Bean就创建完了,如果需要进行AOP,则会进行动态代理并生成一个代理对象做为Bean(初始化后)。

  那么Spring是如何判断当前的bean对象需不需要进行AOP的呢:

  1. 找出所有的切面Bean
  2. 遍历切面中的每个方法,看是否写了@Before、@After等注解
  3. 如果写了,则判断所对应的Pointcut是否和当前Bean对象的类是否匹配
  4. 如果匹配则表示当前Bean对象有匹配的的Pointcut,表示需要进行AOP


6.为什么bean默认为单例?
  单例的bean只有第一次创建新的bean后面都会复用该bean,所以不会频繁创建对象原型的bean每次都会新创建
优点:(1)减少了新生成实例的消耗(反射+给对象分配内存涉及复杂算法)(主攻复用)(XML解析后,需要通过全类名反射创建对象,放到容器中,所以会有消耗
   (2)减少jvm垃圾回收(不会给每个请求都新生成bean实例,所以自然回收的对象少了)
   (3)可以快速获取bean(单例的获取bean除了第一次生成外其余都是从缓存里获取)
缺点:单例的bean一个很大的劣势就是它不能做到线程安全。由于所有请求都共享一个bean实例,所以这个bean要是有状态的话,可能在并发场景下出现问题,而原型的bean则不会有这样问题(但也有例外,比如他被单例bean依赖),因为给每个请求都新创建实例。

单例 Bean 的线程安全问题了解吗?
单例Bean存在线程问题,主要是因为当多个线程操作同一个对象时是存在资源竞争的。
常见的有两种解决办法:

(1)将有状态的bean的作用域由“singleton”改为“prototype”。(@Scope(“prototype”));       (2)在Bean中尽量避免定义可变的成员变量。
(3)在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中(推荐)。

ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。不同线程只操作自己线程的副本变量。

Bean是线程安全的吗?(分状态) 

  prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题(取决于 Bean 是否有状态)。如果这个 bean 是有状态的话,那就存在线程安全问题(有状态 Bean 是指包含可变的成员变量的对象)。不过,大部分 Bean 实际都是无状态(没有定义可变的成员变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。


7.什么是单例池?
  单例Bean指向的对象是同一个。实现这一点是由于底层为Map结构。

  以beanName为key,Bean对象为value,这个Map就被称为单例池。该Map存放的是单例Bean对象,如果创建的是多例Bean,那么在创建Bean对象的过程中就不需要放入单例池。

8.将一个类声明为Bean的注解有哪些?
@Component:通用的注解,可标注任意类为Spring组件。如果一个Bean不知道属于哪个层,可以用@Component注解标注。
@Repository: 对应持久层即Dao层,主要用于数据库相关操作。
@Service: 对应服务层,主要涉及一些复杂的逻辑,需要用到Dao层。
@Controller: 对应Spring MVC控制层,主要用于接受用户请求并调用Service层返回数据给前端页面。

9.@Component 和 @Bean 的区别是什么?
@Component 注解作用于类,而@Bean注解作用于方法。
@Component通常是通过类路径扫描(@ComponentScan)来自动侦测以及自动装配到 Spring 容器中。@Bean注解通常是我们在标有该注解的方法中定义产生这个bean的逻辑。
@Bean 注解比 @Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册 bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。

为什么有了@Compent,还需要@Bean呢?

如果想将第三方的类变成组件,又没有没有源代码,也就没办法使用@Component进行自动配置,这种时候使用@Bean就比较合适了。不过同样的也可以通过xml方式来定义。

另外@Bean注解的方法返回值是对象,可以在方法中为对象设置属性。

//作用于类上面,告诉spring当前类是作为配置文件使用的,相当于Spring中的xml配置文件
@Configuration
public class TestConfig {
   //带有 @Bean 的注解方法将返回一个对象,表示该对象被注册为在 Spring 容器中的 bean
   @Bean
   public TestBean getBean(){
       return new TestBean();
   }
}

//1.返回IOC容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig.class);
//2.查看容器里面的组件
String[] strings = applicationContext.getBeanDefinitionNames();
TestBean myBean = applicationContext.getBean("myBean",TestBean.class);


10.注入 Bean 的注解有哪些?
  Spring内置的@Autowired 以及 JDK内置的@Resource和@Inject都可以用于注入Bean。
@Autowired 和 @Resource 的区别是什么?
  @Autowired是Spring提供的注解,@Resource 是JDK提供的注解。
  @Autowired 默认的注入方式为byType(根据类型进行匹配),@Resource默认注入方式为byName(根据名称进行匹配)。
  当一个接口存在多个实现类的情况下,@Autowired 和@Resource都需要通过名称才能正确匹配到对应的Bean。Autowired可以通过@Qualifier注解来显式指定名称,@Resource可以通过name属性来显式指定名称。

11.Bean的作用域有哪些?
  Singleton:IoC容器中只有唯一的bean实例。Spring中的bean默认都是单例,是对单例设计模式的应用。
  Prototype:每次获取都会创建一个新的bean实例。连续getBean()两次,得到的是不同的Bean实例。
  request(仅Web应用可用): 每一次 HTTP请求都会产生一个新的bean(请求bean),该 bean 仅在当前 HTTP request 内有效。
  session(仅Web应用可用) : 每一次来自新session的HTTP请求都会产生一个新的 bean(会话bean),该bean仅在当前 HTTP session内有效。
  application/global-session (仅Web应用可用): 每个Web应用在启动时创建一个Bean(应用Bean),该bean仅在当前应用启动时间内有效。
  websocket(仅Web应用可用):每一次 WebSocket会话产生一个新的bean。

12.如何配置bean的作用域呢?

xml方式:
<bean id="..." class="..." scope="singleton"></bean>

注解方式:
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
    return new Person();
}


13.Spring中bean的生命周期?
·  Bean容器找到配置文件中Spring Bean的定义。
·  Bean容器利用Java Reflection API 创建一个Bean的实例。
·  如果涉及到一些属性值 利用set()方法设置一些属性值。
·  如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。
·  如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
·  如果Bean实现了BeanFactoryAware接口,调用setBeanFactory()方法,传入BeanFactory对象的实例。
·  与上面的类似,如果实现了其他*.Aware接口,就调用相应的方法。
· 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法
·  如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。
·  如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。
·  如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessAfterInitialization()方法
·  当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法。
·  当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。

 
14.beanFactory和applicationContext的区别?
  BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
   BeanFactory是Spring里面最底层的接口,是Ioc的核心,定义了Ioc的基本功能,包含了各种Bean的定义、加载、实例化,依赖注入和生命周期管理;
   ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能,两者区别在于:
1)BeanFactroy采用的是延迟加载形式来注入Bean,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化,这样就不能发现一些存在的Spring的配置问题。而ApplicationContext是在容器启动时,一次性创建了所有的Bean。在容器启动时,我们就可以发现Spring中存在的配置错误。相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
2)BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext是自动注册。
3)BeanFactory主要是面对与spring框架本身。而 Applicationcontex 主要面对使用Spring的开发者。

15.beanFactory和FactoryBean的区别?
  BeanFactory是个Factory,也就是IOC容器或对象⼯⼚,FactoryBean是个Bean。
  在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进⾏管理的。
  但对FactoryBean⽽⾔,这个Bean不是简单的Bean,⽽是⼀个能⽣产或者修饰对象⽣成的⼯⼚Bean, 它的实现与设计模式中的⼯⼚模式和修饰器模式类似。

16.依赖注入三种方式:属性注入、setter注入、构造器注入

注解注入:变量上加注解@Autowired,优点简单,但是无法注入一个不可变对象(final),只适应于IOC框架。

属性(setter)注入:setUserService (UserService  userService); 每个setter只针对一个对象,符合单一职责设计原则,但是也不能注入不可变对象,且注入的对象是可以被修改的。

构造器注入:可注入不可变对象;构造方法在对象创建时只会执行一次,注入对象不会被修改;注入对象会被完全初始化;通用性更好。

 

 



 

posted @ 2023-09-13 19:04  壹索007  阅读(11)  评论(0编辑  收藏  举报