38 Spring
参考资料
- Spring-全面详解(学习总结)
基本概念
Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术。
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。
IOC本质
IOC全称:控制反转**(Inversion of Control)**,是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
举例来说,现在有一个接口UserDao和一个实现类UserDaoImpl,有一个UserServiceImpl接口如下:
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoMySqlImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
现在如果private UserDao userDao = new UserDaoMySqlImpl();需要更改成其他的对象时,我们就需要在这个类中进行更改,如果我们这样更改就会降低耦合:
public class UserServiceImpl implements UserService {
private UserDao userDao;
// 利用set实现
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
传统开发中,需要调用对象的时候,需要调用者手动来创建被调用者的实例,即对象是由调用者new出来的。但是在Spring框架中,创建对象的工作不再由调用者来完成,而是交给IOC容器来创建,再推送给调用者,整个流程完成反转,所以是控制反转。
Bean的生命周期
这个概念在面试中经常被面试官询问,也就是说明这个Bean在Spring中的生生死死非常重要,而且了解Bean是如何创建和如何销毁的,对于我们理解Spring来说也是非常有益。那么接下来我们进入Spring的世界来看看这个世界最重要的Bean的一生。
先看一张图,这就是Bean的一生。
我们初看不明觉厉,我们接着往下走。
我们在了解一个人的一生时,会从他什么时候出生,什么时候接收了教育,什么时候去世来大致了解他的一生。对于Bean来说也是一样,我们只需要关注整体,不需要过多纠结细节,纠结过多的细节会让我们对Bean的了解模糊。
实例化(出生啦)
在Spring容器也就是IoC容器启动之后,就会到处寻找需要出生的Bean。这时候的Bean就是一个刚刚降临这个世界的婴儿,它在这个世界有了它的位置,但是它的身份和它长大工作需要的还没有准备就绪。
分配地址内存空间。
设置对象属性(出生证明)
一个人出生之后,需要给他起名字、赋予身份(身份证号码),以及其他重要信息(比如家庭背景)。这个阶段就像人的基础身份信息的建立。
在实例化之后,Spring会对Bean进行属性赋值。这一步包括依赖注入,把Bean需要的资源和依赖(比如其他Bean、数据库连接等)赋值给它。
对属性进行赋值。
初始化(接收教育,准备工作)
这一步在正式步入职场之前,需要准备一些技能,这一步也就是初始化。初始化分很多步,这也正常,我们还得读小学、中学、大学呢。
我们来一步步看看其中的流程。
检查Aware相关接口并设置相关依赖
Aware
接口的作用是让Bean感知Spring容器的某些特性,并在必要时获取Spring底层组件的引用。这种机制非常灵活,可以帮助开发者在需要时更好地与Spring环境交互,但要注意过度使用这些接口可能会增加代码的耦合性。
/**
* @FileName MyBeanApplicationContextAware
* @Description
* @Author yaoHui
* @date 2024-10-13
**/
@Slf4j
@Component
public class MyBeanApplicationContextAware implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
log.error("ApplicationContextAware is running");
}
public void displayUserServiceBean(){
UserService userService = applicationContext.getBean(UserService.class);
userService.testApplicationContextAware();
}
}
BeanPostProcessor前置处理
BeanPostProcessor
允许我们在Spring管理的Bean的生命周期中,插入自定义逻辑,这种机制使得我们可以非常灵活地管理和扩展Bean的行为。
常见的应用场景包括日志记录、动态代理、以及某些Bean的特殊处理。
在我们SSIC项目中,其中自定义的HuiMQ中,消费者会需要请求HuiMQ得到消息,需要得到消息的方法会被@HuiListener注解标注。而我们需要知道当前所有Bean中哪些方法是被该注解标记的,被该注解标记的需要加入一个带接受消息的方法集合中,后续将会通过反射来执行该方法。
这就是代码。
/**
* @FileName HuiListenerAnnotationBeanPostProcessor
* @Description
* @Author yaoHui
* @date 2024-10-12
**/
@Component
public class HuiListenerAnnotationBeanPostProcessor implements BeanPostProcessor{
private static final HuiListenerRegistry huiListenerRegistry = new HuiListenerRegistry();
public static boolean huiListenerFlag = false;
/***
* @Description 在 bean 的初始化方法(如 @PostConstruct 注解的方法或 init-method 指定的方法)之前调用。
* @param bean
* @param beanName
* @return {@link Object }
* @Author yaoHui
* @Date 2024/10/12
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
/***
* @Description 在 bean 的初始化方法之后调用。查看当前的bean是否存在被HuiListener注解过的方法
* @param bean
* @param beanName
* @return {@link Object }
* @Author yaoHui
* @Date 2024/10/12
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Method[] methods = bean.getClass().getMethods();
for(Method method : methods){
if(method.isAnnotationPresent(HuiListener.class)){
processHuiListener(method,bean);
huiListenerFlag = true;
}
}
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
private void processHuiListener(Method method,Object bean){
HuiListener huiListener = method.getAnnotation(HuiListener.class);
HuiListenerEndpoint huiListenerEndpoint = new HuiListenerEndpoint();
huiListenerEndpoint.setBean(bean);
huiListenerEndpoint.setMethod(method);
huiListenerRegistry.registerListenerEndpoint(huiListener.queueName(),huiListenerEndpoint);
}
是否实现InitializingBean接口
InitializingBean
接口的afterPropertiesSet()
方法只会在实现该接口的特定Bean类的实例中执行一次。对于其他没有实现InitializingBean
接口的Bean,这个方法不会被调用。
其会在Bean初始化之后执行,在所有参数赋值之后实现了这个接口的类就会执行。
/**
* @FileName MyBeanPostProcessor
* @Description
* @Author yaoHui
* @date 2024-10-13
**/
@Component
@Slf4j
public class MyBeanPostProcessor implements BeanPostProcessor, InitializingBean {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// log.error("初始化之前执行:" + bean.toString());
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// log.error("初始化之后执行:" + bean.toString());
return bean;
}
@Override
public void afterPropertiesSet() throws Exception {
log.error("afterPropertiesSet is running");
}
}
BeanPostProcessor后置处理
/**
* @FileName HuiListenerAnnotationBeanPostProcessor
* @Description
* @Author yaoHui
* @date 2024-10-12
**/
@Component
public class HuiListenerAnnotationBeanPostProcessor implements BeanPostProcessor{
private static final HuiListenerRegistry huiListenerRegistry = new HuiListenerRegistry();
public static boolean huiListenerFlag = false;
/***
* @Description 在 bean 的初始化方法(如 @PostConstruct 注解的方法或 init-method 指定的方法)之前调用。
* @param bean
* @param beanName
* @return {@link Object }
* @Author yaoHui
* @Date 2024/10/12
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
/***
* @Description 在 bean 的初始化方法之后调用。查看当前的bean是否存在被HuiListener注解过的方法
* @param bean
* @param beanName
* @return {@link Object }
* @Author yaoHui
* @Date 2024/10/12
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Method[] methods = bean.getClass().getMethods();
for(Method method : methods){
if(method.isAnnotationPresent(HuiListener.class)){
processHuiListener(method,bean);
huiListenerFlag = true;
}
}
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
private void processHuiListener(Method method,Object bean){
HuiListener huiListener = method.getAnnotation(HuiListener.class);
HuiListenerEndpoint huiListenerEndpoint = new HuiListenerEndpoint();
huiListenerEndpoint.setBean(bean);
huiListenerEndpoint.setMethod(method);
huiListenerRegistry.registerListenerEndpoint(huiListener.queueName(),huiListenerEndpoint);
}
注册Destruction相关接口
DisposableBean
接口提供了一个destroy()
方法,当Bean被销毁时,Spring会调用该方法。
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Component;
@Component
public class MyBean implements DisposableBean {
@Override
public void destroy() throws Exception {
// 自定义的销毁逻辑
System.out.println("MyBean is being destroyed!");
}
}
这里并不是真正的销毁,Bean还没开始使用呢,这只是定义了一个方法,方便之后进行销毁时进行调用该方法。
使用@PreDestroy
注解
Spring还允许使用@PreDestroy
注解来标记一个方法,在Bean被销毁之前调用。示例代码如下:
java复制代码import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
public class AnotherBean {
@PreDestroy
public void cleanup() {
// 自定义的清理逻辑
System.out.println("AnotherBean is being destroyed!");
}
}
使用
不必多说
销毁
是否实现了DisposableBean接口
DisposableBean
是 Spring 框架中的一个接口,专门用于处理 Bean 的销毁过程。通过实现该接口,开发者可以在 Bean 的生命周期结束时执行自定义的清理逻辑,以确保资源的正确释放。
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Component;
@Component
public class MyBean implements DisposableBean {
// 这里可以定义一些需要的资源,比如数据库连接等
private String resource;
public MyBean() {
// 模拟资源的初始化
this.resource = "Some Resource";
System.out.println("MyBean initialized with resource: " + resource);
}
// 实现 DisposableBean 接口的 destroy 方法
@Override
public void destroy() throws Exception {
// 自定义的清理逻辑
System.out.println("MyBean is being destroyed! Releasing resource: " + resource);
// 这里可以添加资源释放的代码,比如关闭数据库连接等
}
}
是否配置自定义的destory-method
destroy-method
是 Spring 框架中用于定义 Bean 销毁方法的一个属性,主要用于在 Bean 被销毁时指定一个特定的方法来执行清理逻辑。这种方式通常用于 XML 配置文件中,允许开发者在 Spring 容器关闭或 Bean 被销毁时执行自定义的逻辑。
Bean的作用域
Bean的作用域是指Bean实例的生命周期及可见性范围,Spring框架定义了以下6种作用域:
- singleton:单例作用域,所有对该Bean的请求都返回同一个Bean实例。
- prototype:原型作用域,每次请求时都创建一个新的Bean实例。
- request:请求作用域,每个HTTP请求都会创建一个新的Bean实例,该Bean实例仅在当前请求内有效。
- session:会话作用域,每个HTTP会话都会创建一个新的Bean实例,该Bean实例仅在当前会话内有效。
- application:全局作用域,一个bean 定义对应于单个ServletContext 的生命周期。
- websocket: HTTP WebSocket 作用域,一个bean 定义对应于单个websocket 的生命周期。
singleton作用域是Spring中默认的作用域,
使⽤ @Scope 标签就可以⽤来声明 Bean 的作⽤域,⽐如设置 Bean 的作⽤域,如下代码所示:
@Component
public class Users {
@Scope(prototype)
@Bean(name = "u")
public User user() {
User user = new User();
user.setId(1);
user.setName("Hi user");
return user;
}
}
Spring依赖注入的方式
构造器注入
import org.springframework.stereotype.Component;
@Component
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void addUser(String username) {
userRepository.save(username);
}
}
@Component
public class UserRepository {
public void save(String username) {
System.out.println("User " + username + " saved.");
}
}
Setter 方法注入
import org.springframework.stereotype.Component;
@Component
public class OrderService {
private PaymentService paymentService;
// Setter 方法
public void setPaymentService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void processOrder() {
if (paymentService != null) {
paymentService.processPayment();
} else {
System.out.println("No payment service available.");
}
}
}
@Component
public class PaymentService {
public void processPayment() {
System.out.println("Payment processed.");
}
}
字段注入
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class NotificationService {
@Autowired
private EmailService emailService;
public void sendNotification() {
emailService.sendEmail();
}
}
@Component
public class EmailService {
public void sendEmail() {
System.out.println("Email sent.");
}
}
注解配置方式
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class AppConfig {
@Value("${app.name}")
private String appName;
public void printAppName() {
System.out.println("Application Name: " + appName);
}
}
XML 配置
<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="userRepository" class="com.example.UserRepository"/>
<bean id="userService" class="com.example.UserService">
<constructor-arg ref="userRepository"/>
</bean>
</beans>
BeanFactory和ApplicationContext有什么区别
是spring的核心接口,都可以作为容器,ApplicationContext是BeanFactory的子接口。
BeanFactory: 是spring最底层的接口,包含各种Bean的定义和Bean的管理。
区别:
- BeanFactroy采用的是延迟加载形式来注入Bean的,使用到bean才会加载。ApplicationContext一次性加载所有bean。
- BeanFactory需要手动注册,而ApplicationContext则是自动注册。
- BeanFactory不支持国际化,ApplicationContext支持国际化(实现MessageSource接口)。
- BeanFactory不支持AOP,ApplicationContext支持AOP,可以与Spring的AOP框架集成,提供声明式事务管理。
Spring中的单例bean的线程安全问题
虽然Spring中的Bean是singleton,但是在一些多线程环境下,会出现线程安全的问题。比如下面这种情况:
import org.springframework.stereotype.Component;
@Component
public class CounterService {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class AppRunner implements CommandLineRunner {
@Autowired
private CounterService counterService;
@Override
public void run(String... args) throws Exception {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counterService.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counterService.increment();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final count: " + counterService.getCount());
}
}
因为count++;操作不是原子性的,所以会出现线程不安全的问题。一般的解决方法是:
- 所定义的Bean是无状态的,即不涉及一些数据,但是这不现实;
- 在类中定义一个ThreadLocal成员变量,将需要的可变变量保存在ThreadLocal中;
- 把成员变量写在方法内。
- 修改bean的作用域,singleton改为prototype。(@Scope(“prototype”))
- 使用synchronized修饰。