Spring题集 - Spring Bean实例相关面试题总结

文章目录

01. Spring 配置文件包含了哪些信息?

Spring配置文件中包含了如下信息:

① Bean配置:定义了Spring容器中的Bean,包括Bean的ID、类、作用域、依赖关系、属性值、初始化方法和销毁方法等。
② AOP配置:定义了切面、切点、通知和切面的顺序等,用于实现面向切面编程。
③ 数据源配置:定义了数据库连接池、数据源、事务管理器等,用于实现数据库访问。
④ MVC配置:定义了控制器、视图解析器、处理器映射器、拦截器等,用于实现Web应用程序。
⑤ 消息资源配置:定义了消息源、国际化等,用于实现多语言支持。
⑥ 安全配置:定义了安全认证、授权、访问控制等,用于实现应用程序的安全性。
⑦ 其他配置:还可以包含其他配置信息,如缓存、邮件、调度等,用于实现应用程序的其他功能。

Spring配置文件可以使用XML、JavaConfig或注解等方式进行配置,以下是一个简单的Spring配置文件示例:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myBean" class="com.example.MyBean">
        <property name="message" value="Hello Spring!" />
    </bean>

    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/mydb" />
        <property name="username" value="root" />
        <property name="password" value="password" />
    </bean>

    <mvc:annotation-driven />
    <context:component-scan base-package="com.example" />

</beans>

在这个示例中,我们定义了两个Bean,一个是名为"myBean"的Bean,另一个是名为"dataSource"的数据源Bean。我们还使用了MVC和组件扫描的命名空间,用于配置MVC和组件扫描。

02. Spring Bean是什么?

在Spring框架中,Bean是指由Spring IoC容器管理的对象。这些对象被称为Spring beans。Spring beans是在Spring IoC容器中创建、组装和管理的,它们可以是任何Java对象,包括POJO、JavaBean、数据访问对象等等。Spring beans的生命周期由Spring IoC容器管理,这意味着它们可以在应用程序中轻松地进行配置、组装和管理。

03. Spring Bean定义包含什么?

在这里插入图片描述

一个Spring Bean定义包含以下元素:

① Bean的唯一标识符(ID):每个Bean都必须有一个唯一的ID,用于在Spring容器中标识该Bean。
② Bean的类:指定要在Spring容器中创建的Bean的类。
③ Bean的作用域(Scope):指定Bean的生命周期和可见范围。
④ Bean的依赖关系:指定Bean与其他Bean之间的依赖关系,包括构造函数注入、Setter方法注入等。
⑤ Bean的属性值:指定Bean的属性值,包括基本类型、引用类型、集合类型等。
⑥ Bean的初始化方法:指定Bean在创建后要执行的初始化方法。
⑦ Bean的销毁方法:指定Bean在销毁前要执行的方法。

以下是一个简单的示例,演示如何在Spring中定义一个Bean:

<bean id="myBean" class="com.example.MyBean" scope="singleton">
    <property name="message" value="Hello Spring!" />
    <property name="anotherBean" ref="anotherBean" />
    <constructor-arg name="arg1" value="value1" />
    <constructor-arg name="arg2" ref="refBean" />
    <init-method>init</init-method>
    <destroy-method>destroy</destroy-method>
</bean>

在这个示例中,我们定义了一个名为"myBean"的Bean,它的类是"com.example.MyBean",作用域是Singleton。它有一个名为"message"的属性,值为"Hello Spring!",还有一个名为"anotherBean"的引用类型属性,它引用了另一个Bean。我们还使用构造函数注入了两个参数,一个是基本类型,一个是引用类型。最后,我们指定了Bean的初始化方法和销毁方法。

04. Spring Bean的作用域有哪些?

在Spring中,可以使用scope属性来定义Bean的作用域,scope属性可以设置为以下五个值:

① singleton:单例,默认值,整个应用程序中只创建一个Bean实例。在Spring容器创建的时候就会进行Bean的实例化,并存储到容器内部的单例池中,每次getBean时都是从单例池中获取相同的Bean实例;
② prototype:原型,每次请求都会创建一个新的Bean实例。Spring容器初始化时不会创建Bean实例,当调用getBean时才会实例化Bean,每次getBean都会创建一个新的Bean实例。
③ request:请求,每个HTTP请求都会创建一个新的Bean实例;
④ session:会话,每个HTTP会话都会创建一个新的Bean实例;
⑤ globalSession:全局会话模式,仅在基于portlet的web应用程序中使用,为每个portlet应用程序创建一个Bean实例;

<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="singleton"/>

05. Spring Bean的延迟加载如何配置?

Spring中Bean的延迟加载指的是在需要使用Bean时才进行实例化和初始化,而不是在容器启动时就进行实例化和初始化。延迟加载可以提高应用程序的启动速度和内存使用效率,特别是在应用程序中存在大量的Bean时更为明显。

在Spring中,可以通过以下方式来配置延迟加载:

① 在Bean定义中使用lazy-init属性:可以在Bean定义中使用lazy-init属性来指定Bean的延迟加载方式。当lazy-init属性设置为true时,表示该Bean需要延迟加载;当lazy-init属性设置为false时,表示该Bean需要在容器启动时进行实例化和初始化。例如:

<bean id="beanA" class="com.example.BeanA" lazy-init="true">
    <!-- Bean的属性配置 -->
</bean>

在上面的示例中,BeanA被设置为延迟加载,只有在需要使用BeanA时才会进行实例化和初始化。

② 在@Configuration类中使用@Lazy注解:可以在@Configuration类中使用@Lazy注解来指定Bean的延迟加载方式。当@Lazy注解标记在@Bean方法上时,表示该Bean需要延迟加载;当@Lazy注解标记在@Configuration类上时,表示该@Configuration类中所有的Bean都需要延迟加载。例如:

@Configuration
public class AppConfig {
    @Bean
    @Lazy
    public Bean beanA() {
        return new BeanA();
    }
}

在上面的示例中,beanA被设置为延迟加载,只有在需要使用beanA时才会进行实例化和初始化。

需要注意的是,延迟加载可能会导致Bean的初始化顺序发生变化,因此需要注意Bean的初始化顺序和生命周期,以避免出现不必要的问题。另外,延迟加载可能会影响应用程序的性能和响应时间,因此需要根据具体的需求来选择合适的延迟加载方式。

06. Spring Bean的初始化方法和销毁方法如何配置?

① 通过XML配置文件来指定Bean的初始化方法和销毁方法。

指定初始化方法可以在Bean的定义中使用init-method属性来指定Bean的初始化方法。指定销毁方法可以在Bean的定义中使用destroy-method属性来指定Bean的销毁方法。

public class UserServiceImpl implements UserService {
    private UserDao userDao;
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    private void init(){
        System.out.println("初始化方法...");
    }
    private void destroy(){
        System.out.println("销毁方法...");
    }
    @Override
    public void show() {

    }
}
<bean id="userService" class="com.service.impl.UserServiceImpl"
      init-method="init" destroy-method="destroy">
    <property name="userDao" ref="userDao"/>
</bean>

<bean id="userDao" class="com.dao.impl.UserDaoImpl"></bean>

在上面的示例中使用init-method属性指定了初始化方法为"init",并使用destroy-method属性指定了销毁方法为"destroy"。

public class ApplicationContextTest {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext applicationContext
                = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = applicationContext.getBean(UserService.class);
        // 容器关闭式才会调用bean的销毁方法
        applicationContext.close();
    }
}

② 实现 InitializingBean和 DisposableBean接口,让Bean实现InitializingBean和DisposableBean接口中的afterPropertiesSet()和destroy()方法,Bean实例初始化和销毁时Spring容器会自动调用这些方法。例如:

public class BeanA implements InitializingBean, DisposableBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        // Bean的初始化逻辑
    }
    
    @Override
    public void destroy() throws Exception {
        // Bean的销毁逻辑
    }
}

在上面的示例中,BeanA实现了InitializingBean和DisposableBean接口,并分别实现了afterPropertiesSet()和destroy()方法,这些方法会在Bean实例化和销毁时被自动调用。

③ 使用@Bean注解指定初始化方法和销毁方法:可以在@Configuration类中使用@Bean注解来指定Bean的初始化方法和销毁方法。可以使用initMethod属性指定初始化方法,使用destroyMethod属性指定销毁方法。例如:

@Configuration
public class AppConfig {
    @Bean(initMethod = "init", destroyMethod = "destroy")
    public BeanA beanA() {
        return new BeanA();
    }
}

在上面的示例中,beanA被定义为一个Bean,并使用@Bean注解指定了初始化方法为"init",销毁方法为"destroy"。

④ 使用@PostConstruct和@PreDestroy注解:可以在Bean的方法上使用@PostConstruct和@PreDestroy注解来指定初始化方法和销毁方法。@PostConstruct注解标记的方法会在Bean实例化后立即执行,@PreDestroy注解标记的方法会在Bean销毁前执行。例如:

public class BeanA {
    @PostConstruct
    public void init() {
        // Bean的初始化逻辑
    }

    @PreDestroy
    public void destroy() {
        // Bean的销毁逻辑
    }
}

在上面的示例中,BeanA使用@PostConstruct和@PreDestroy注解分别标记了初始化方法和销毁方法。

需要注意的是,Bean的初始化方法和销毁方法可以同时使用多种方式进行配置,但是需要确保这些方式不会产生冲突。另外,Bean的初始化方法和销毁方法可能会影响应用程序的性能和响应时间,因此需要根据具体的需求来选择合适的配置方式。

07. Spring Bean的实例化方式有几种?

在Spring中,Bean的实例化方式主要有以下几种:

① 使用构造函数实例化:可以在Bean的定义中使用构造函数来实例化Bean。

默认情况使用无参构造函数来实例化bean:

public class UserDaoImpl implements UserDao {
    public UserDaoImpl(){
        System.out.println("无参构造函数执行...");
    }
}
<bean id="userDao" class="com.dao.impl.UserDaoImpl"/>

使用带参构造方法实例化bean:

public class UserDaoImpl implements UserDao {
    public UserDaoImpl(String name,int age){
        System.out.println("带参构造函数执行...");
    }
}
<bean id="userDao" class="com.dao.impl.UserDaoImpl">
    <constructor-arg name="name" value="haha"/>
    <constructor-arg name="age" value="19"/>
</bean>

在上面的示例中,UserDaoImpl被定义为一个Bean,并使用构造函数来实例化UserDaoImpl。

② 使用静态工厂方法实例化:可以在Bean的定义中使用静态工厂方法来实例化Bean。

public class MyBeanFactory {
    public static UserDao userDao(String name,int age){
        return new UserDaoImpl();
    }
}
<bean id="userDao" class="com.factory.MyBeanFactory" factory-method="userDao">
    <constructor-arg name="name" value="haha"/>
    <constructor-arg name="age" value="18"/>
</bean>

在上面的示例中,UserDaoImpl被定义为一个Bean,并使用静态工厂方法userDao() 来实例化UserDaoImpl。

③ 使用实例工厂方法实例化:可以在Bean的定义中使用实例工厂方法来实例化Bean。

public class MyBeanFactory {
    public UserDao userDaoImpl(String name,int age){
        return new UserDaoImpl();
    }
}
<bean id="myBeanFactory" class="com.factory.MyBeanFactory"/>
<bean id="userDao" factory-bean="myBeanFactory" factory-method="userDaoImpl">
    <constructor-arg name="name" value="haha"/>
    <constructor-arg name="age" value="18" type="int"/>
</bean>

在上面的示例中,BeanFactory被定义为一个Bean,并使用实例工厂方法userDaoImpl来实例化UserDaoImpl。

需要注意的是,Bean的实例化方式可能会影响Bean的属性注入和初始化顺序等问题,因此需要根据具体的需求来选择合适的实例化方式。另外,Bean的实例化方式可能会影响应用程序的性能和响应时间,因此需要根据具体的需求来选择合适的实例化方式。

08. Spring Bean的注入方式有几种?

Spring Bean的注入方式有2种:如果是普通类型通过value属性注入,如果是引用数据类型使用ref属性注入

在这里插入图片描述

① 构造器注入:通过构造器来注入Bean的依赖关系,可以保证Bean的依赖关系在实例化时就已经被注入,避免了在使用时出现空指针异常等问题。

public class UserServiceImpl implements UserService {
    private UserDao userDao;
    // 构造方法注入
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }
}
<bean id="userService" class="com.service.impl.UserServiceImpl">
    <constructor-arg name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.dao.impl.UserDaoImpl"/>

② Setter方法注入:通过Setter方法来注入Bean的依赖关系,可以在Bean实例化后动态地注入依赖关系。

public class UserServiceImpl implements UserService {
    private UserDao userDao;
    // setter方法注入
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}
<bean id="userService" class="com.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.dao.impl.UserDaoImpl"/>

09. Spring Bean如何注入集合数据类型?

依赖注入的数据类型有如下三种:
(1) 普通数据类型,例如:String、int、boolean等,通过value属性指定。
(2) 引用数据类型,例如:UserDaoImpl、DataSource等,通过ref属性指定。
(3) 集合数据类型,例如:List、Map、Properties等。

在Spring中,可以使用<list>、<set>、<map>和<props>等标签来注入Java集合:

public class UserServiceImpl implements UserService {

    // 注入集合类型
    private Set<String> stringSet;
    private List<String> stringList;
    private Map<String,Integer> stringMap;
    private Properties properties;
    private List<UserDao> userDaoList;

    public void setStringSet(List<String> list){
        this.stringList = list;
    }

    public void setStringList(Set<String> set){
        this.stringSet = set;
    }

    public void setStringMap(Map<String,Integer> map){
        this.stringMap = map;
    }

    public void setProperties(Properties properties){
        this.properties = properties;
    }

    public void setUserDaoList(List<UserDao> userDaoList){
        this.userDaoList = userDaoList;
    }
}
<bean id="userService" class="com.service.impl.UserServiceImpl">
    <property name="stringList">
        <list>
            <value>item1</value>
            <value>item2</value>
            <value>item3</value>
        </list>
    </property>
    <property name="stringSet">
        <set>
            <value>item1</value>
            <value>item2</value>
            <value>item3</value>
        </set>
    </property>
    <property name="stringMap">
        <map>
            <entry key="key1" value="13" />
            <entry key="key2" value="14" />
            <entry key="key3" value="15" />
        </map>
    </property>
    <property name="properties">
        <props>
            <prop key="prop1">value1</prop>
            <prop key="prop2">value2</prop>
            <prop key="prop3">value3</prop>
        </props>
    </property>
    <property name="userDaoList">
        <list>
            <ref bean="userDao"/>
            <ref bean="userDao1"/>
            <ref bean="userDao2"/>
        </list>
    </property>
</bean>

<bean id="userDao" class="com.dao.impl.UserDaoImpl"/>
<bean id="userDao1" class="com.dao.impl.UserDaoImpl"/>
<bean id="userDao2" class="com.dao.impl.UserDaoImpl"/>

在这些示例中,我们使用不同的标签来注入Java集合,包括List、Set、Map和Properties集合。每个集合都有自己的标签,可以使用<value>、<entry>和<prop>等子标签来定义集合元素。

10. Spring Bean自动装配是什么?

手动装配方式,手动地将一个Bean的依赖项注入到另一个Bean中:

public class UserServiceImpl implements UserService {
    private UserDao userDao;
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>

Bean的自动装配是Spring框架中一种依赖注入的方式,它可以自动地将一个Bean的依赖项注入到另一个Bean中,而无需显式地在XML配置文件中进行配置。自动装配可以减少配置工作量,提高开发效率。

Spring框架支持以下三种自动装配模式:

① byName:根据Bean的名称自动装配依赖项。Spring容器会查找与依赖项名称相同的Bean,并将其注入到目标Bean中。

<bean id="userService" class="com.service.impl.UserServiceImpl" autowire="byName"/>
<bean id="userDao" class="com.dao.impl.UserDaoImpl"/>

在这个示例中,我们将autowire属性设置为byName,表示使用byName自动装配模式。Spring容器会查找与依赖项名称相同的Bean,并将其注入到userService中的userDao属性中。因为我们的依赖项名称为userDao(setUserDao),所以Spring容器会查找名称为userDao的Bean,并将其注入到userService中。

② byType:根据Bean的类型自动装配依赖项。Spring容器会查找与依赖项类型相同的Bean,并将其注入到目标Bean中。如果存在多个与依赖项类型相同的Bean,则会抛出异常。

<bean id="userService" class="com.service.impl.UserServiceImpl" autowire="byType"/>
<bean id="userDao" class="com.dao.impl.UserDaoImpl"/>

③ constructor:根据构造函数参数类型自动装配依赖项。Spring容器会查找与构造函数参数类型相同的Bean,并将其注入到目标Bean中。如果存在多个与构造函数参数类型相同的Bean,则会抛出异常。

<bean id="userService" class="com.service.impl.UserServiceImpl" autowire="constructor"/>
<bean id="userDao" class="com.dao.impl.UserDaoImpl"/>

除了在XML配置文件中使用autowire属性进行自动装配外,还可以使用注解或JavaConfig进行自动装配。

11. Spring Bean自动装配有哪些方式?

Spring Bean自动装配有以下几种方式:

① byName自动装配:根据Bean的名称自动装配依赖项。Spring容器会查找与依赖项名称相同的Bean,并将其注入到目标Bean中。

② byType自动装配:根据Bean的类型自动装配依赖项。Spring容器会查找与依赖项类型相同的Bean,并将其注入到目标Bean中。如果存在多个与依赖项类型相同的Bean,则会抛出异常。

③ constructor自动装配:根据构造函数参数类型自动装配依赖项。Spring容器会查找与构造函数参数类型相同的Bean,并将其注入到目标Bean中。如果存在多个与构造函数参数类型相同的Bean,则会抛出异常。

④ no自动装配:不进行自动装配,需要手动指定依赖项。

12. Spring Bean的自动装配有哪些局限性?

Spring Bean的自动装配虽然可以减少配置工作量,提高开发效率,但也存在一些局限性,需要注意:

① 自动装配可能会导致歧义性:当存在多个与依赖项名称或类型相同的Bean时,自动装配可能会导致歧义性,无法确定应该注入哪个Bean。这时需要手动指定依赖项,或者使用限定符或主要Bean来解决歧义性。

② 自动装配可能会导致不必要的依赖项注入:当存在多个与依赖项名称或类型相同的Bean时,自动装配可能会注入不必要的依赖项,导致程序出现错误或异常。这时需要手动指定依赖项,或者使用限定符或主要Bean来解决问题。

③ 自动装配可能会导致Bean的可读性和可维护性降低:自动装配可能会隐藏Bean之间的依赖关系,使代码难以理解和维护。这时需要使用注释或其他文档来解释Bean之间的依赖关系,提高代码的可读性和可维护性。

④ 自动装配可能会导致Bean的性能降低:自动装配需要在运行时进行依赖项查找和注入,可能会导致Bean的性能降低。这时需要使用手动装配或其他优化技术来提高Bean的性能。

因此,在使用自动装配时,需要注意这些局限性,并根据具体情况选择合适的装配方式,以提高程序的可靠性、可读性和性能。

13. Spring 出现同名Bean怎么办?

① 使用限定符(Qualifier):可以在注入Bean时使用限定符来指定要注入的Bean。限定符可以是自定义的字符串,也可以是注解。例如:

@Autowired
@Qualifier("beanA")
private Bean beanA;

② 使用Bean名称:可以在注入Bean时使用Bean的名称来指定要注入的Bean。例如:

@Autowired
private Bean beanA;
@Autowired
private Bean beanB;

在上面的示例中,如果存在两个名称为"beanA"和"beanB"的Bean,则可以通过名称来区分要注入的Bean。

③ 使用Primary注解:可以在Bean定义中使用Primary注解来指定首选的Bean。如果存在多个同类型的Bean,则Spring容器会优先选择被Primary注解标记的Bean。例如:

@Component
@Primary
public class BeanA implements Bean {
    // ...
}

@Component
public class BeanB implements Bean {
    // ...
}

在上面的示例中,如果存在多个实现了Bean接口的Bean,Spring容器会优先选择被@Primary注解标记的Bean。

需要注意的是,当存在多个同名的Bean时,需要确保它们的类型和作用域等信息相同,否则可能会导致不必要的问题。另外,使用限定符、Bean名称或Primary注解等方式时,需要确保注入的Bean是唯一的,否则可能会出现注入错误的情况。

14. Spring 如何开启基于基于注解的自动写入?

在Spring中,可以通过在配置类上添加@ComponentScan注解来开启基于注解的自动装配。@ComponentScan注解会扫描指定的包及其子包中的所有类,将其中被@Component、@Service、@Repository、@Controller等注解标记的类自动注册为Bean。

以下是一个示例:

@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
    // 配置其他Bean
}

在上面的示例中,@ComponentScan注解指定了要扫描的包为com.example,Spring容器会自动扫描该包及其子包中的所有类,并将其中被@Component、@Service、@Repository、@Controller等注解标记的类自动注册为Bean。

需要注意的是,开启基于注解的自动装配时,需要确保被注解标记的类已经被正确地扫描到,并且注解的作用范围和生命周期符合预期。如果出现了无法自动装配的情况,可以通过调整@ComponentScan注解的参数或添加其他注解来解决问题。

15. Spring @Autowired注解自动装配的过程是怎样的?

Spring的@Autowired注解是一种自动装配的方式,它可以自动将一个Bean的依赖项注入到另一个Bean中。@Autowired注解的自动装配过程如下:

① Spring容器在初始化Bean时,会扫描所有的Bean,查找与依赖项类型相同的Bean。
② 如果找到了与依赖项类型相同的Bean,则将其注入到目标Bean中。如果找到了多个与依赖项类型相同的Bean,则会寻找与依赖项名称相同的Bean。
③ 如果找到了与依赖项名称相同的Bean,则将其注入到目标Bean中。如果找到了多个与依赖项名称相同的Bean,则会抛出异常。
④ 如果没有找到与依赖项名称相同的Bean,则会抛出异常。

需要注意的是,@Autowired注解默认使用byType自动装配模式,如果需要使用byName自动装配模式,可以结合@Qualifier注解来使用。@Qualifier注解可以指定要注入的Bean的名称,以解决byName自动装配模式下的歧义性问题。

另外,@Autowired注解还可以用于构造函数、Setter方法和字段上,以实现不同的自动装配方式。在构造函数和Setter方法上使用@Autowired注解,可以实现构造函数和Setter方法的自动装配;在字段上使用@Autowired注解,可以实现字段的自动装配。

16. Spring常用的3种getBean方法是什么?

在这里插入图片描述

public class ApplicationContextTest {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext applicationContext
                = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = applicationContext.getBean(UserService.class);
        UserService userService1 = (UserService) applicationContext.getBean("userService");
        UserService userService2 = applicationContext.getBean("userService", UserService.class);
    }
}

17. Spring 内部Bean是什么?

Spring的内部Bean是指在另一个Bean的内部定义的Bean,它们的生命周期与外部Bean相同,但只能在外部Bean中使用。内部Bean通常用于封装和隐藏一些细节,使外部Bean更加简洁和易于使用。

在Spring中,可以使用<bean>标签来定义内部Bean。以下是一个示例:

<bean id="outerBean" class="com.example.OuterBean">
    <property name="innerBean">
        <bean class="com.example.InnerBean">
            <property name="message" value="Hello Inner Bean!" />
        </bean>
    </property>
</bean>

在这个示例中,我们定义了一个名为 outerBean 的外部Bean,并在其中定义了一个名为 innerBean 的内部Bean。innerBean 的生命周期与 outerBean 相同,但只能在 outerBean 中使用。innerBean 的定义嵌套在outerBean 的<property>标签中,使用了<bean>标签来定义。

使用内部Bean可以使Bean的定义更加简洁和易于维护,同时也可以隐藏一些细节,提高代码的可读性和可维护性。但是,需要注意内部Bean只能在外部Bean中使用,不能在其他地方引用。

18. Spring 可以注入一个null 和一个空字符串吗?

在Spring中,可以注入一个null值,但不能直接注入一个空字符串。如果需要注入一个空字符串,可以使用<null>标签或<value>标签来实现。

以下是一个示例,演示如何在Spring中注入null值和空字符串:

<bean id="myBean" class="com.example.MyBean">
    <property name="nullValue">
        <null />
    </property>
    <property name="emptyString">
        <value></value>
    </property>
</bean>

在这个示例中,我们使用<null>标签来注入null值,使用<value>标签来注入空字符串。<value>标签中不填写任何内容,表示注入一个空字符串。

需要注意的是,如果直接在<value>标签中填写空字符串,Spring会将其解析为一个非空字符串,而不是一个空字符串。因此,如果需要注入一个空字符串,必须使用<null>标签或<value>标签中不填写任何内容的方式来实现。

19. Spring如何配置非自定义的bean?

有些bean的配置时我们自己定义的,比如UserDao,UserService,但是,在实际开发中有些功能类并不是我们自己定义的,而是使用的第三方jar包中的,那么这些bean要想让Spring进行管理,也需要对其进行配置。配置非自定义的bean需要考虑两个问题:

被配置的bean的实例化方式是什么?无参构造、有参构造、静态工厂方式还是实例工厂方式。

被配置的bean是否需要注入必要属性。

① 配置数据源信息交给Spring容器管理

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.23</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>
<!--加载properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!--配置数据源信息-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root

② 配置Date交给Spring容器管理

<!--配置日期对象-->
<bean id="simpleDateFormat" class="java.text.SimpleDateFormat">
    <constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"></constructor-arg>
</bean>
<bean id="date" factory-bean="simpleDateFormat" factory-method="parse">
    <constructor-arg name="source" value="2023-08-27 12:00:00"></constructor-arg>
</bean>

20. Spring 容器的启动流程?

Spring容器的启动流程包括以下几个步骤:

① 加载配置文件:Spring容器会读取配置文件,包括XML配置文件、Java配置类等,以获取Bean的定义和其他配置信息。
② 创建Bean实例:Spring容器会根据Bean的定义创建Bean的实例,包括实例化Bean、注入属性等操作。
③ Bean的初始化:Spring容器会调用Bean的初始化方法,包括实现InitializingBean接口的afterPropertiesSet()方法、通过@Bean注解指定的init-method方法等。
④ 注册Bean:Spring容器会将创建好的Bean实例注册到容器中,以便其他Bean或应用程序使用。
⑤ 使用Bean:Bean实例可以被其他Bean或应用程序使用。

需要注意的是,Spring容器的启动流程可以受到配置文件的影响。例如,如果配置文件中包含了Bean的作用域、生命周期等信息,那么Spring容器会根据这些信息来创建和管理Bean实例。另外,Spring容器的启动流程还可以受到Bean的依赖关系、循环依赖等因素的影响,因此需要注意Bean的定义和配置,以避免出现不必要的问题。

在Spring Boot中,启动流程与传统的Spring容器类似,但是会自动配置很多常用的Bean,以简化开发者的工作。同时,Spring Boot还提供了一些特殊的注解和配置方式,以便更方便地配置和管理Bean。

21. Spring Bean的实例化基本流程?

Spring 容器在进行初始化时,会将xml配置的<bean>的信息封装成一个BeanDefinition对象,所有的BeanDefinition存储到一个名为beanDefinitionMap的Map集合中去,Spring框架对该Map进行遍历,使用反射常见Bean实例对象,创建好的Bean对象存储在一个名为singletonObjects的Map集合中,当调用getBean方法时则最终从Map集合中取出Bean实例对象返回。

① 加载xml配置文件,解析获取配置中的每个<bean>的信息,封装成一个个的BeanDefinition对象;

在这里插入图片描述

② 将BeanDefinition存储在一个名为beanDefinitionMap的Map<String,BeanDefinition>中;
③ ApplicationContext底层遍历beanDefinitionMap,创建Bean实例对象;

DefaultListableBeanFactory对象内部维护着一个Map用于存储封装好的BeanDefinitionMap,Spring框架会取出beanDefinitionMap中的每个BeanDefinition信息,反射构造方法或调用指定的工厂方法生成Bean实例对象,所以只要将BeanDefinition注册到beanDefinitionMap这个Map中,Spring就会进行对应的Bean的实例化操作。

在这里插入图片描述

④ 创建好的Bean实例对象,被存储到一个名为singletonObjects的Map<String,Object>中;
⑤ 当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配Bean实例返回。

Bean实例及单例池singletonObjects, beanDefinitionMap中的BeanDefinition会被转化成对应的Bean实例对象
,存储到单例池singletonObjects中去,在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中,维护着singletonObjects,源码如下:

在这里插入图片描述

⑥ Bean 实例化的基本流程:

在这里插入图片描述

22. Spring Bean 后置处理器 BeanFactoryPostProcessor?

Spring的后处理器是Spring对外开发的重要扩展点,允许我们介入到Bean的整个实例化流程中来,以达到动态注册BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用。Spring主要有两种后处理器:

BeanFactoryPostProcessor:Bean工厂后处理器,在BeanDefinitionMap填充完毕,Bean实例化之前执行;
BeanPostProcessor:Bean后处理器,一般在Bean实例化之后,填充到单例池singletonObjects之前执行。

BeanFactoryPostProcessor是一个接口规范,实现了该接口的类只要交由Spring容器管理的话,那么Spring就会回调该接口的方法,用于对BeanDefinition注册和修改的功能。

@FunctionalInterface
public interface BeanFactoryPostProcessor {
   void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

postProcessBeanFactory 参数本质就是 DefaultListableBeanFactory,拿到BeanFactory的引用,自然就可以
对beanDefinitionMap中的BeanDefinition进行操作了。

① 例如,对UserDaoImpl的BeanDefinition进行修改操作:

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("MyBeanFactoryPostProcessor的postProcessBeanFactory");
        //修改某个Beandifinition
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("userService");
        beanDefinition.setBeanClassName("com.dao.impl.UserDaoImpl"); 
    }
}
<bean id="myBeanFactoryPostProcessor" class="com.processor.MyBeanFactoryPostProcessor"/>

② 对BeanDefiition进行注册操作:

public class PersonDaoImpl implements PersonDao {
}
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("MyBeanFactoryPostProcessor的postProcessBeanFactory");
        // 注册BeanDefinition
        BeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClassName("com.dao.impl.PersonDaoImpl");
        // 强转成DefaultListableBeanFactory
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
        defaultListableBeanFactory.registerBeanDefinition("personDao",beanDefinition);
    }
}
public class ApplicationContextTest {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext applicationContext
                = new ClassPathXmlApplicationContext("applicationContext.xml");
        PersonDao personDao = applicationContext.getBean(PersonDao.class);
        System.out.println(personDao);
    }
}

执行结果:

MyBeanFactoryPostProcessor的postProcessBeanFactory
com.dao.impl.PersonDaoImpl@6ebc05a6

③ Spring 提供了一个BeanFactoryPostProcessor的子接口BeanDefinitionRegistryPostProcessor专门用于注册
BeanDefinition操作。

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
   void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

BeanDefinitionRegistryPostProcessor接口的使用:

public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        System.out.println("MyBeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法");
        //注册Beandefinition
        BeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClassName("com.dao.impl.PersonDaoImpl");
        beanDefinitionRegistry.registerBeanDefinition("personDao",beanDefinition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("MyBeanDefinitionRegistryPostProcessor的postProcessBeanFactory方法");
    }
}
<bean id="myBeanDefinitionRegistryPostProcessor" class="com.processor.MyBeanDefinitionRegistryPostProcessor"/>
public class ApplicationContextTest {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext applicationContext
                = new ClassPathXmlApplicationContext("applicationContext.xml");
        PersonDao personDao = applicationContext.getBean(PersonDao.class);
        System.out.println(personDao);
    }
}

执行结果:

MyBeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法
MyBeanDefinitionRegistryPostProcessor的postProcessBeanFactory方法
MyBeanFactoryPostProcessor的postProcessBeanFactory
com.dao.impl.PersonDaoImpl@3cef309d

④ BeanFactoryPostProcessor 在Spring Bean的实例化过程中的体现:

在这里插入图片描述

23. 利用Spring的BeanFactoryPostProcessor完成自定义注解扫描?

要求如下:

  • 自定义@MyComponent注解,使用在类上;
  • 使用包扫描器工具BaseClassScanUtils 完成指定包的类扫描;
  • 自定义BeanFactoryPostProcessor完成注解@MyComponent的解析,解析后最终被Spring管理。

① 自定义注解 @MyComponent

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent {
    String value();
}

② 在类上使用@MyComponen

@MyComponent("otherBean")
public class OtherBean {
}

@MyComponent("xxx")
public class XxxBean {
}

③ 自定义BeanFactoryPostProcessor完成注解解析

public class MyComponentBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        //通过扫描工具去扫描指定包及其子包下的所有类,收集使用@Mycomponent的注解的类
        Map<String, Class> myComponentAnnotationMap = BaseClassScanUtils.scanMyComponentAnnotation("com.hh");
        //遍历Map,组装BeanDefinition进行注册
        myComponentAnnotationMap.forEach((beanName,clazz)->{
            //获得beanClassName
            String beanClassName = clazz.getName();//com.hh.beans.OtherBean
            //创建BeanDefinition
            BeanDefinition beanDefinition = new RootBeanDefinition();
            beanDefinition.setBeanClassName(beanClassName);
            //注册
            beanDefinitionRegistry.registerBeanDefinition(beanName,beanDefinition);
        });
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }
}
<bean id="myComponentBeanFactoryPostProcessor" class="com.processor.MyComponentBeanFactoryPostProcessor"/>

④ 包扫描器工具BaseClassScanUtils

public class BaseClassScanUtils {

    //设置资源规则
    private static final String RESOURCE_PATTERN = "/**/*.class";

    public static Map<String, Class> scanMyComponentAnnotation(String basePackage) {

        //创建容器存储使用了指定注解的Bean字节码对象
        Map<String, Class> annotationClassMap = new HashMap<String, Class>();

        //spring工具类,可以获取指定路径下的全部类
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        try {
            String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    ClassUtils.convertClassNameToResourcePath(basePackage) + RESOURCE_PATTERN;
            Resource[] resources = resourcePatternResolver.getResources(pattern);
            //MetadataReader 的工厂类
            MetadataReaderFactory refractory = new CachingMetadataReaderFactory(resourcePatternResolver);
            for (Resource resource : resources) {
                //用于读取类信息
                MetadataReader reader = refractory.getMetadataReader(resource);
                //扫描到的class
                String classname = reader.getClassMetadata().getClassName();
                Class<?> clazz = Class.forName(classname);
                //判断是否属于指定的注解类型
                if(clazz.isAnnotationPresent(MyComponent.class)){
                    //获得注解对象
                    MyComponent annotation = clazz.getAnnotation(MyComponent.class);
                    //获得属value属性值
                    String beanName = annotation.value();
                    //判断是否为""
                    if(beanName!=null&&!beanName.equals("")){
                        //存储到Map中去
                        annotationClassMap.put(beanName,clazz);
                        continue;
                    }
                    //如果没有为"",那就把当前类的类名作为beanName
                    annotationClassMap.put(clazz.getSimpleName(),clazz);

                }
            }
        } catch (Exception exception) {
        }
        return annotationClassMap;
    }

    public static void main(String[] args) {
        Map<String, Class> stringClassMap = scanMyComponentAnnotation("com.hh");
        System.out.println(stringClassMap);
    }
}

⑤ 测试:

public class ApplicationContextTest {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext applicationContext
                = new ClassPathXmlApplicationContext("applicationContext.xml");
        OtherBean otherBean = applicationContext.getBean(OtherBean.class);
        System.out.println(otherBean);
    }
}

执行结果:

com.hh.beans.OtherBean@7e2d773b

24. Spring Bean 后置处理器 BeanPostProcessor?

Bean被实例化后,到最终缓存到名为singletonObjects单例池之前,中间会经过Bean的初始化过程,例如:属性的填充、初始方法init的执行等,其中有一个对外进行扩展的点BeanPostProcessor,我们称为Bean后处理。跟上面的Bean工厂后处理器相似,它也是一个接口,实现了该接口并被容器管理的BeanPostProcessor,会在流程节点上被Spring自动调用。

public interface BeanPostProcessor {
    // 在属性注入完毕,init初始化方法执行之前被回调
	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

    // 在初始化方法执行之后,被添加到单例池singletonObjects之前被回调
	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
}

自定义MyBeanPostProcessor,完成快速入门测试:

public class UserDaoImpl implements UserDao,InitializingBean {
    private String username;

    public void setUsername(String username) {
        this.username = username;
    }

    public UserDaoImpl(){
        System.out.println("userDao实例化.....");
    }

    public void  init(){
        System.out.println("init初始化方法.....");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("属性设置后执行.....");
    }
}
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof UserDaoImpl){
            UserDaoImpl userDao = (UserDaoImpl) bean;
            userDao.setUsername("haohao");
        }
        System.out.println(beanName+":postProcessBeforeInitialization");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName+":postProcessAfterInitialization");
        return bean;
    }
}
<bean id="myBeanPostProcessor" class="com.processor.MyBeanPostProcessor"/>
<bean id="userDao" class="com.dao.impl.UserDaoImpl" init-method="init"/>
public class ApplicationContextTest {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext applicationContext
                = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = applicationContext.getBean(UserDao.class);
        System.out.println(userDao);
    }
}

执行结果:

userDao实例化…
userDao:postProcessBeforeInitialization
属性设置后执行…
init初始化方法…
userDao:postProcessAfterInitialization
com.dao.impl.UserDaoImpl@4e9ba398

BeanPostProcessor 在 Spring Bean的实例化过程中的体现:

在这里插入图片描述

25. 利用Spring的BeanPostProcessor对Bean方法进行时间日志增强?

要求如下:

Bean的方法执行之前控制台打印当前时间;
Bean的方法执行之后控制台打印当前时间。

分析:

对方法进行增强主要就是代理设计模式和包装设计模式;
由于Bean方法不确定,所以使用动态代理在运行期间执行增强操作;
在Bean实例创建完毕后,进入到单例池之前,使用Proxy代替真是的目标Bean

public class UserDaoImpl implements UserDao{
    @Override
    public void show(){
        System.out.println("执行show....");
    }
}
public class TimeLogBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //使用动态代理对目标Bean进行增强,返回proxy对象,进而存储到单例池singletonObjects中
        Object beanProxy = Proxy.newProxyInstance(
                bean.getClass().getClassLoader(),
                bean.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    //1、输出开始时间
                    System.out.println("方法:" + method.getName() + "-开始时间:" + new Date());
                    //2、执行目标方法
                    Object result = method.invoke(bean, args);
                    //3、输出结束时间
                    System.out.println("方法:" + method.getName() + "-结束时间:" + new Date());
                    return result;
                }
        );
        return beanProxy;
    }
}
<bean id="timeLogBeanPostProcessor" class="com.processor.TimeLogBeanPostProcessor"/>
<bean id="userDao" class="com.dao.impl.UserDaoImpl"/>
public class ApplicationContextTest {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext applicationContext
                = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = applicationContext.getBean(UserDao.class);
        userDao.show();
    }
}

执行结果:

方法:show-开始时间:Wed Mar 15 07:12:42 SGT 2023
执行show…
方法:show-结束时间:Wed Mar 15 07:12:42 SGT 2023

26. Spring Bean的生命周期?

Spring Bean的生命周期是从 Bean 实例化之后,即通过反射创建出对象之后,到Bean成为一个完整对象,最终存储到单例池中,这个过程被称为Spring Bean的生命周期。Spring Bean的生命周期大体上分为三个阶段:

① Bean的实例化阶段:Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的,
是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化;

② Bean的初始化阶段:Bean创建之后还仅仅是个"半成品",还需要对Bean实例的属性进行填充、执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。

③ Bean的完成阶段:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池
singletonObjects中去了,即完成了Spring Bean的整个生命周期。

在这里插入图片描述

27. Spring Bean的生命周期?

Spring Bean的生命周期包括以下阶段:

① 实例化:Spring容器根据Bean的定义创建Bean的实例。
② 属性赋值:Spring容器将Bean的属性值注入到Bean实例中。
③ 初始化前回调:如果Bean实现了InitializingBean接口,Spring容器会在Bean的初始化前调用其afterPropertiesSet()方法。
④ 自定义初始化方法:如果Bean定义中指定了自定义初始化方法,Spring容器会在Bean的初始化前调用该方法。
⑤ 初始化后回调:如果Bean实现了BeanPostProcessor接口,Spring容器会在Bean的初始化后调用其postProcessBeforeInitialization()方法和postProcessAfterInitialization()方法。
⑥ 使用:Bean实例可以被其他Bean或应用程序使用。
⑦ 销毁前回调:如果Bean实现了DisposableBean接口,Spring容器会在Bean销毁前调用其destroy()方法。
⑧ 自定义销毁方法:如果Bean定义中指定了自定义销毁方法,Spring容器会在Bean销毁前调用该方法。

需要注意的是,Bean的生命周期可以受到作用域的影响。例如,单例Bean的生命周期与容器的生命周期相同,而原型Bean的生命周期仅限于每次请求。因此,在使用Bean时需要注意其作用域和生命周期,以避免出现不必要的问题。

28. Spring 重要的Bean的生命周期方法?

Spring Bean的生命周期方法包括以下几个:

① afterPropertiesSet()方法:在Bean的属性赋值完成后,Spring容器会调用该方法,用于执行一些初始化操作。该方法是InitializingBean接口中定义的方法。

② init-method方法:在Bean的属性赋值完成后,Spring容器会调用该方法,用于执行一些初始化操作。该方法是在Bean定义中通过init-method属性指定的。

③ postProcessBeforeInitialization()方法:在Bean的初始化前,Spring容器会调用该方法,用于对Bean进行一些自定义的处理。该方法是BeanPostProcessor接口中定义的方法。

④ postProcessAfterInitialization()方法:在Bean的初始化后,Spring容器会调用该方法,用于对Bean进行一些自定义的处理。该方法是BeanPostProcessor接口中定义的方法。

⑤ destroy()方法:在Bean被销毁前,Spring容器会调用该方法,用于执行一些清理操作。该方法是DisposableBean接口中定义的方法。

⑥ destroy-method方法:在Bean被销毁前,Spring容器会调用该方法,用于执行一些清理操作。该方法是在Bean定义中通过destroy-method属性指定的。

可以重载这些生命周期方法来实现自定义的初始化和销毁操作。例如,可以在Bean中实现InitializingBean接口和DisposableBean接口,重载afterPropertiesSet()方法和destroy()方法来实现自定义的初始化和销毁操作。也可以在Bean定义中通过init-method属性和destroy-method属性指定自定义的初始化方法和销毁方法。

需要注意的是,重载生命周期方法时需要遵循一定的规范,以确保Spring容器能够正确地调用这些方法。例如,在重载afterPropertiesSet()方法时,需要调用父类的方法以确保Bean的属性赋值完成。在重载destroy()方法时,需要注意清理资源的顺序,以避免出现不必要的问题。

29. Spring Bean初始化阶段?

Spring Bean的初始化过程涉及如下几个过程:

(1) Bean实例的属性填充
(2) Aware接口属性注入
(3) BeanPostProcessor的before()方法回调
(4) InitializingBean接口的初始化方法回调
(5) 自定义初始化方法init回调
(6) BeanPostProcessor的after()方法回调

30. Spring Bean实例的属性填充?

BeanDefinition 中有对当前Bean实体的注入信息通过属性propertyValues进行了存储。Spring在进行属性注入时,会分为如下几种情况:

(1) 注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去;

(2) 注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作;

(3) 注入双向对象引用属性时,就比较复杂了,涉及了循环引用(循环依赖)问题。

① 注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去;

public class UserServiceImpl implements UserService {
    private String username;
    public void setUsername(String username) {
        this.username = username;
    }
}
<bean id="userService" class="com.service.impl.UserServiceImpl">
    <property name="username" value="zhangsan"/>
</bean>

② 注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作;

public class UserServiceImpl implements UserService {
    private UserDao userDao;
    public void setUserDao(UserDao userDao){
        this.userDao = userDao;
    }
}
<bean id="userService" class="com.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.dao.impl.UserDaoImpl"/>

③ 注入双向对象引用属性时,就比较复杂了,涉及了循环引用(循环依赖)问题。

31. Spring Bean的循环依赖问题?

多个实体之间相互依赖并形成闭环的情况就叫做"循环依赖",也叫做"循环引用"

在这里插入图片描述

Spring singleObjects单例池中没有UserService和UserDao实例的引用,因为UserService和UserDao都是半成品,因此循环判断容器中是否有UserService和UserDao。

在这里插入图片描述

Spring提供了三级缓存存储 完整Bean实例 和 半成品Bean实例 ,用于解决循环引用问题,在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中提供如下三个Map:

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    // 最终存储单例Bean成品的容器,即实例化和初始化都完成的Bean,称之为"一级缓存"
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    // 早期Bean单例池,缓存半成品对象,且当前对象已经被其他对象引用了,称之为"二级缓存"
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

    // 单例Bean的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建Bean,称之为"三级缓存"
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}
@FunctionalInterface
public interface ObjectFactory<T> {
   T getObject() throws BeansException;
}

在这里插入图片描述

UserService和UserDao循环依赖的过程结合上述三级缓存描述一下:

(1) UserService 实例化对象,但尚未初始化,将UserService存储到三级缓存;
(2) UserService 属性注入,需要UserDao,从缓存中获取,没有UserDao;
(3) UserDao实例化对象,但尚未初始化,将UserDao存储到到三级缓存;
(4) UserDao属性注入,需要UserService,从三级缓存获取UserService,UserService从三级缓存移入二级缓存;
(5) UserDao执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存;
(6) UserService 注入UserDao;
(7) UserService执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存。

32. Spring 如何解决循环依赖问题?

Spring通过三级缓存解决循环依赖问题,三级缓存包括singletonObjects、earlySingletonObjects和singletonFactories。

当Spring容器创建Bean时,会先创建Bean的实例,然后将其放入singletonFactories缓存中。接着,Spring容器会注入Bean的依赖,如果依赖的Bean还未创建,则会递归创建依赖的Bean。如果依赖的Bean已经创建,则会从singletonObjects缓存中获取Bean的实例,并将其注入到当前Bean中。如果当前Bean的所有依赖都已经注入完成,则会将当前Bean的实例放入singletonObjects缓存中,并从singletonFactories缓存中移除。

在处理循环依赖时,Spring容器会将正在创建的Bean实例放入earlySingletonObjects缓存中,以便在后续的依赖注入中使用。如果后续的依赖注入中出现了循环依赖,则会从earlySingletonObjects缓存中获取Bean的实例,以避免出现死循环的情况。当循环依赖解决后,Spring容器会将Bean的实例从earlySingletonObjects缓存中移除,并将其放入singletonObjects缓存中。

需要注意的是,Spring容器处理循环依赖的能力是有限的,只能处理单例Bean之间的循环依赖。如果出现了原型Bean之间的循环依赖,则需要使用其他方式来解决问题,例如使用方法注入或通过ApplicationContextAware接口获取ApplicationContext对象等。

另外,循环依赖可能会导致Bean的初始化顺序发生变化,因此需要注意Bean的初始化顺序和生命周期,以避免出现不必要的问题。

33. Spring 常用的Aware接口?

Aware接口是一种框架辅助属性注入的一种思想,其他框架中也可以看到类似的接口。框架具备高度封装性,我们接触到的一般都是业务代码,一个底层功能API不能轻易的获取到,但是这不意味着永远用不到这些对象,如果用到了,就可以使用框架提供的类似Aware的接口,让框架给我们注入该对象。

在这里插入图片描述

public class UserServiceImpl implements UserService,BeanFactoryAware,BeanNameAware {
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println(beanFactory);
    }

    @Override
    public void setBeanName(String name) {
        System.out.println(name);
    }
}

34. Spring 的单例Bean是线程安全的吗?

在Spring中,单例Bean是线程安全的,因为Spring容器会保证在同一时间只有一个线程访问单例Bean。这是因为Spring容器在创建单例Bean时,会将其实例化并缓存起来,以便在后续的请求中重复使用。因此,所有对单例Bean的访问都是在同一个实例上进行的,不会出现多个线程同时访问的情况。

需要注意的是,虽然单例Bean本身是线程安全的,但如果单例Bean中包含了可变状态,那么就需要考虑线程安全的问题。如果多个线程同时访问一个包含可变状态的单例Bean,可能会导致数据不一致或其他问题。在这种情况下,可以使用同步机制或其他线程安全的技术来保证单例Bean的线程安全性。

另外,如果需要在多线程环境下使用Spring Bean,还可以考虑使用原型Bean或会话Bean等非单例Bean,以避免线程安全的问题。原型Bean和会话Bean都是在每次请求时创建一个新的实例,因此可以避免多个线程同时访问同一个实例的问题。

35. Spring Bean是线程安全的么?

Spring框架中的Bean是否线程安全取决于Bean的作用域。Spring框架中有多种作用域,包括单例(Singleton)、原型(Prototype)、会话(Session)、请求(Request)等。其中,单例作用域的Bean是线程不安全的,而原型、会话和请求作用域的Bean是线程安全的。

单例作用域的Bean是线程不安全的,因为所有线程都共享同一个实例。如果多个线程同时访问一个单例Bean,可能会导致数据不一致或其他问题。在这种情况下,可以使用同步机制或其他线程安全的技术来保证单例Bean的线程安全性。

原型、会话和请求作用域的Bean是线程安全的,因为它们在每次请求时都会创建一个新的实例。这样,每个线程都可以独立地访问自己的实例,不会出现多个线程同时访问同一个实例的问题。

如果需要在多线程环境下使用单例Bean,可以考虑使用同步机制或其他线程安全的技术来保证单例Bean的线程安全性。另外,也可以考虑使用原型、会话或请求作用域的Bean,以避免线程安全的问题。

需要注意的是,即使使用了原型、会话或请求作用域的Bean,也需要注意Bean中是否包含了可变状态。如果多个线程同时访问一个包含可变状态的Bean,可能会导致数据不一致或其他问题。在这种情况下,仍然需要使用同步机制或其他线程安全的技术来保证Bean的线程安全性。

36. Spring 如何处理线程并发问题?

Spring框架本身并不提供处理线程并发问题的功能,但是可以结合其他技术来处理线程并发问题。以下是一些处理线程并发问题的技术:

① 同步机制:可以使用Java中的同步机制,如synchronized关键字、Lock接口等来保证线程安全。在Spring中,可以使用同步机制来保证单例Bean的线程安全性。

② 线程池:可以使用Java中的线程池来管理线程,避免线程创建和销毁的开销,提高程序的性能。在Spring中,可以使用线程池来处理异步任务,提高程序的并发性能。

③ 分布式锁:可以使用分布式锁来保证多个进程或多个服务器之间的数据一致性。在Spring中,可以使用分布式锁来保证分布式系统中的数据一致性。

④ 缓存:可以使用缓存来提高程序的性能,避免重复计算和重复访问数据库等操作。在Spring中,可以使用缓存来提高程序的性能,如使用Spring Cache抽象来实现缓存功能。

需要注意的是,处理线程并发问题需要根据具体情况选择合适的技术和方案。在使用这些技术和方案时,需要注意线程安全性、性能、可伸缩性等问题,以保证程序的稳定性和可靠性。

37. Spring 单例Bean的线程安全问题?

Spring中的单例Bean是线程不安全的,因为所有线程都共享同一个实例。如果多个线程同时访问一个单例Bean,可能会导致数据不一致或其他问题。在这种情况下,可以使用同步机制或其他线程安全的技术来保证单例Bean的线程安全性。

在单例Bean中,如果包含了可变状态,那么就需要考虑线程安全的问题。如果多个线程同时访问一个包含可变状态的单例Bean,可能会导致数据不一致或其他问题。在这种情况下,可以使用同步机制或其他线程安全的技术来保证单例Bean的线程安全性。

以下是一些保证单例Bean线程安全的技术:

① 同步方法:可以使用synchronized关键字来保证方法的原子性和线程安全性。
② 同步块:可以使用synchronized关键字来保证代码块的原子性和线程安全性。
③ volatile关键字:可以使用volatile关键字来保证变量的可见性和线程安全性。
④ Atomic类:可以使用Java中的Atomic类来保证变量的原子性和线程安全性。

需要注意的是,使用同步机制会带来一定的性能开销,因此需要根据具体情况选择合适的技术和方案。在使用这些技术和方案时,需要注意线程安全性、性能、可伸缩性等问题,以保证程序的稳定性和可靠性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我一直在流浪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值