自学内容网 自学内容网

38 Spring

38 Spring

参考资料

  1. 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修饰。

原文地址:https://blog.csdn.net/fyh2944163240/article/details/142904959

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!