文章目录
- 01. Spring 配置文件包含了哪些信息?
- 02. Spring Bean是什么?
- 03. Spring Bean定义包含什么?
- 04. Spring Bean的作用域有哪些?
- 05. Spring Bean的延迟加载如何配置?
- 06. Spring Bean的初始化方法和销毁方法如何配置?
- 07. Spring Bean的实例化方式有几种?
- 08. Spring Bean的注入方式有几种?
- 09. Spring Bean如何注入集合数据类型?
- 10. Spring Bean自动装配是什么?
- 11. Spring Bean自动装配有哪些方式?
- 12. Spring Bean的自动装配有哪些局限性?
- 13. Spring 出现同名Bean怎么办?
- 14. Spring 如何开启基于基于注解的自动写入?
- 15. Spring @Autowired注解自动装配的过程是怎样的?
- 16. Spring常用的3种getBean方法是什么?
- 17. Spring 内部Bean是什么?
- 18. Spring 可以注入一个null 和一个空字符串吗?
- 19. Spring如何配置非自定义的bean?
- 20. Spring 容器的启动流程?
- 21. Spring Bean的实例化基本流程?
- 22. Spring Bean 后置处理器 BeanFactoryPostProcessor?
- 23. 利用Spring的BeanFactoryPostProcessor完成自定义注解扫描?
- 24. Spring Bean 后置处理器 BeanPostProcessor?
- 25. 利用Spring的BeanPostProcessor对Bean方法进行时间日志增强?
- 26. Spring Bean的生命周期?
- 27. Spring Bean的生命周期?
- 28. Spring 重要的Bean的生命周期方法?
- 29. Spring Bean初始化阶段?
- 30. Spring Bean实例的属性填充?
- 31. Spring Bean的循环依赖问题?
- 32. Spring 如何解决循环依赖问题?
- 33. Spring 常用的Aware接口?
- 34. Spring 的单例Bean是线程安全的吗?
- 35. Spring Bean是线程安全的么?
- 36. Spring 如何处理线程并发问题?
- 37. 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类来保证变量的原子性和线程安全性。
需要注意的是,使用同步机制会带来一定的性能开销,因此需要根据具体情况选择合适的技术和方案。在使用这些技术和方案时,需要注意线程安全性、性能、可伸缩性等问题,以保证程序的稳定性和可靠性。