【SpringUtil】2、深入理解

一、废话

在了解SpringUtil的基本内容后,其实我对SpringUtil还是有好多疑惑,因此,在这里以提问的方式逐一解答。
PS:主要还是个人理解,如有问题请大佬们纠正。

二、SpringUtil

为了方便查看代码,这里再放一样的SpringUtil代码出来。

@Component
public class SpringUtil implements ApplicationContextAware {
    
    
    /** 静态成员*/
    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    
    
        context = applicationContext;
    }

    public static <T> T getBean(Class<T> beanClass) {
    
    
        return context.getBean(beanClass);
    }
}

三、问题

这里将以ShiroUtil类引入RedisSessionDAO为例子,并且主要是以static静态成员为例。

1、什么时候需要使用SpringUtil?

当需要在非Spring容器管理的Bean中,使用Spring容器中的Bean时,这时就可以使用SpringUtil类来获取Spring管理的Bean对象。
【假设有一个非Spring管理的工具类(Bean,如ShiroUtil),需要使用Spring容器中的某些对象(例如,RedisSessionDAO)。】
在这里插入图片描述
因为不在Spring管理的范围内,我们无法使用@Autowired注解来注入对象,
所以先需要在ShiroConfig中利用@Configuration和@Bean,将RedisSessionDAO注册到Spring容器中。

@Configuration
public class ShiroConfig {
    
    

	@Bean
    public SecurityManager securityManager() {
    
    
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 自定义Ssession管理
        securityManager.setSessionManager(sessionManager());
        // 自定义Cache实现
        securityManager.setCacheManager(cacheManager());
        // 自定义Realm验证
        securityManager.setRealm(shiroRealm());
        return securityManager;
    }
	@Bean
    public RedisSessionDAO redisSessionDAO() {
    
    
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        return redisSessionDAO;
    }
	@Bean
    public SessionManager sessionManager() {
    
    
        ShiroSessionManager shiroSessionManager = new ShiroSessionManager();
        shiroSessionManager.setSessionDAO(redisSessionDAO());
        return shiroSessionManager;
    }
	···
}

通过反射获取到对象,之后才能调用里面的属性和方法
引入的jar包,需要先注册到Spring容器中,然后就可以直接使用SpringUtil来获取对象

public class ShiroUtils {
    
    
	/** 采用SpringUtil静态成员方法**/
	private static RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class);

	private static ...{
    
    }
	···
}

补充:这里使用static是因为,我这里ShiroUtil的方法都是static方法,目的是为了使用ShiroUtil的时候,能直接 类名.方法
【SpringUtil】1、基础内容

补充2:
相同点:两者的结果都是为spring容器注册Bean.
不同点:
@Component 通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中。
@Bean 注解通常是我们在标有该注解的方法中定义产生这个bean的逻辑。
@Component 和 @Bean 的区别 文章1
@Component 和 @Bean 的区别 文章2

2、除了使用SpringUtil重写的getBean方法外,可以使用@Autowired的方式获取该对象吗?

可以
①静态成员static【主要使用

@Component
public class ShiroUtils {
    
    
	/** set方法依赖注入**/
    private static RedisSessionDAO redisSessionDAO;
    @Autowired
    public void setRedisSessionDAO(RedisSessionDAO redisSessionDao){
    
    
        redisSessionDAO = redisSessionDao;
    }

	···
}
  1. 因为RedisSeeionDAO不属于Spring容器管理,所以需要使用@Component将ShiroUtil引入Spring容器中管理。
  2. 由于这里使用的是静态成员变量(private static RedisSessionDAO redisSessionDAO),因此不能直接在属性上面加@Autowired,需要通过在set方法上面加上@Autowired来进行依赖注入。

②非静态成员【不建议使用
因为没有测试到,在使用上,大概率还有什么加载的坑在里面!!!

@Component
public class ShiroUtils {
    
    
	/** @Autowired依赖注入**/
    @Autowired
    private RedisSessionDAO redisSessionDAO;
	···
}

注意:
1、使用非静态成员后,下面的方法都不能用static方法。
在静态方法中不能访问非静态变量和方法;非静态方法可以访问静态和非静态变量和方法
2、别的类调用这个非静态成员RedieSessionDAO的ShiroUtil时,不能直接 类名.方法,需要创建对象使用。
【SpringUtil】1、基础内容

3、为什么不能在static静态成员变量上加@Autowired?而需要在set方法上加上@Autowired来进行依赖注入?

①为什么不能加?

1、初始化顺序的问题:静态成员变量属于类级别,它在类加载时被初始化赋值,而spring容器是在运行时才开始工作的。如果某个静态成员变量被@Autowired注解自动装配后,当使用它时,它还没有被初始化,静态成员变量就不知道赋值什么东西,这会导致NullPointerException异常。
2、Spring容器的管理范围:静态成员变量是类级别,而Spring容器管理的是对象级别,即Spring容器可以管理的对象都是实例化的类对象,而不是类本身。因此,如果将@Autowired用于静态成员变量,其实并不被容器所托管,这与Spring框架的初衷不符。
3、综上所述,@Autowired不能作用在静态成员变量上,因为静态成员变量初始化顺序在Spring容器之前,而Spring容器只能管理被实例化的对象。因此,建议将@Autowired注解使用在实例变量上。

②为什么在set方法上加?

1、静态成员变量不依赖于对象的实例化,因此无法通过传递对象的方式进行依赖注入。
2、静态成员变量依赖于类的定义和加载,实现静态成员变量的依赖注入,需要设置set方法,并加上@Autowired,通过反射实现。
【当Spring容器实例化ShiroUtil时,会自动注入RedieSessionDAO实例,并通过set方法赋值给静态成员变量redisSessionDAO】
3、在Spring容器初始化时,它会扫描@Autowired注解,然后Spirng框架会查找其注解的类对象,通过反射机制,获取该类对象的实例,并将其赋值给需要注入的变量。

补充:反射主要就是获取类的对象。通过类获取它的实例,在程序的运行上,顺序要比new先(类加载先于对象的实例化)

4、SpringUtil为什么需要设置static静态ApplicationContext?

单纯个人理解,有问题请指正。
因为SpringUtil需要设置getBean方法,目的就是要像反射那样,通过类获取该对象的实例,且方便,因此从顺序上需要在类加载时完成,从而使用静态。

5、除了问题2中提到的方法,可以对静态成员变量 直接对类进行实例化 或者 采用SpringUtil非静态成员方法 获取对象吗?

不可以
①直接对类进行实例化

public class ShiroUtils {
    
    
	/** 直接实例化   不行,没有进行配置 会为null**/
    private static RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
	···
}

直接实例化静态成员变量,而不是让Spring来注入,可能导致其他的依赖无法设置。
如果是不用配置的类,可以直接实例化。

②采用SpringUtil非静态成员方法
这里的SpringUtil需要用非静态的方式

@Component
public class SpringUtil implements ApplicationContextAware {
    
    
    /** 非静态成员*/
    private ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    
    
        context = applicationContext;
    }

    public <T> T getBean(Class<T> beanClass) {
    
    
        return context.getBean(beanClass);
    }
}

因为这里使用的SpringUtil是非静态的,不能直接类.方法,所以这里使用getBean需要将SpringUtil进行实例化。

public class ShiroUtils {
    
    
	/** 采用SpringUtil非静态成员方法     不行 会为null**/
  private static RedisSessionDAO redisSessionDAO = new SpringUtil().getBean(RedisSessionDAO.class);
	···
}
  1. 因为静态成员在类加载的时候被初始化,这里给静态成员变量赋值new,这一整句代码,应该在类加载时完成,然而RedisSessionDAO在ShiroConfig中还没有被@Configuration和@Bean注册到;此外,在类加载的时候,还无法实例化SpringUtil,所以静态成员不能直接对类进行实例化。
  2. 静态成员变量是类级别的,而Spring容器管理的是对象级别的;静态成员变量初始化先于Spring容器的实例化,也就意味着ApplicationContext没有加载完,无法进行实例化。

6、为什么ApplicationContext没有加载完,就不能实例化?

类加载完后才会到Spring项目启动
Spring项目启动的流程一般分为以下几个步骤:
1、加载Spring配置文件:Spring项目在启动时会加载配置文件,包括xml文件、Java配置类等,在这些配置文件中定义了Spring容器应该包括哪些组件、如何进行依赖注入等。
2、创建和初始化ApplicationContext:当Spring配置文件被加载之后,Spring会根据其中的定义来创建和初始化ApplicationContext实例。这个过程会进行一些预处理操作,比如解析占位符、解析属性等。
3、创建和初始化Bean:在ApplicationContext被创建并初始化完成之后,Spring会根据配置文件的定义来创建各个Bean(也叫实例化Bean),并将其注入到容器中。在这个过程中,Spring会根据依赖关系来对这些Bean进行排序,以保证依赖关系正确注入。【能在日志INFO中看到】
4、暴露ApplicationContext到外部环境:在ApplicationContext初始化完成之后,Spring会将其暴露到外部环境中,以便其他组件可以使用它。
【这个地方就是我们所看到的setApplicationContext方法】

主要原因:
Bean没有被容器管理,没有被注入相关依赖,导致一个Bean是一个不完整的实例,无法正常工作,就会出现空指针异常。

详细原因可能是:
1、在实例化时,ApplicationContext还没加载完(Spring上下文还未初始化完),就会导致Spring没有扫描到包,或者尚未完成构造和依赖注入。【由于静态成员变量是在类加载时初始化,还没创建/实例化Bean,所以会出现上述原因】
2、直接实例化静态成员变量,而不是让Spring来注入,可能导致其他的依赖无法设置。

7、Bean到底是什么?

官方给的解释:
在 Spring 中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。bean是一个由Spring IoC容器实例化、组装和管理的对象。
Spring中Bean的讲解

总结:
Bean是Spring中的一个术语,代指一个经过Spring IOC容器管理的实例化过的对象,可以是一个POJO(纯Java对象)、一个业务类、一个数据访问对象、一个服务对象等等。Bean就是一个对象

补充:

  1. 实例化Bean:Spring容器创建Bean,是Spring容器中的对象,也就是根据类new对象,存在Spring容器中,要用就直接调出来,不需要在new对象了。
  2. 直接实例化静态成员变量:就是直接(private static a = new 类)。

8、SpringUtil类上加上@Component,里面的setApplicationContext(ApplicationContext applicationContext)是如何实现applicationContext的依赖注入?

SpringUtil类加上@Component注解后,这个类会被Spring容器扫描加入到容器中,同时Spring也会为其创建一个实例对象,并将其对象纳入容器的管理中。
当Spring容器初始化完毕后,会自动调用@Override的setApplicationContext方法,并将当前容器中的ApplicationContext实例对象set进去。

解释一下:当前容器中的ApplicationContext实例对象
当正在实例化的类是Aware类型时,且是ApplicationContextAware类型时就会调用到setApplicationContext(this.applicationContext)方法把spring上下文设置进去。也就是实现了ApplicationContextAware接口。
ps:这个部分我并没有详细看过,只是知道这个的最终目的。
ApplicationContextAware接口的setApplicationContext方法调用过程

在这里插入图片描述

9、为什么SpringUtil加上@Component,set方法上加@Override就可以自动注入ApplicationContext?

SpringUtil中的setApplicationContext方法不需要@Autowired也可以依赖注入。
因为Spring容器启动时,会扫描所有@Component注解,将其实例化并加入到容器的BeanFactory中管理。同时,Spring会将容器本身的ApplicationContext实例注入到 实现了ApplicationContextAware接口的Bean中(也就是SpringUtil)。因此Spring可以在初始化时自动调用setApplicationContext方法,并将当前容器的ApplicationContext实例传入。

总的来说,SpringUtil通过ApplicationContextAware接口,实现了将ApplicationContext对象注入到了SpringUtil对象的setApplicationContext方法中,完成了依赖注入。

10、为什么有的类可以直接@Autowired自动注入,有的需要set方法上面加@Autowired进行注入?

主要是非静态与静态的区别。

  1. 非静态成员变量:
    可以直接使用@Autowired注入。
    Spring容器实例化的同时,会为已经加载完的类进行实例化Bean(Spring中的对象);
    通过@Autowired注解,从Spring容器中获取Bean对象,注入到变量中。
  2. 静态成员变量:
    初始化是在类加载的时候完成,先于Spring容器的实例化,所以@Autowired没办法在静态成员变量初始化时从容器中获取到Bean。
    先定义静态成员变量,然后需要将非Spring容器管理的对象加入到Spring容器中,再写setter方法并在该方法上加@Autowired。
    在这里插入图片描述

11、Spring为什么会将容器本身的ApplicationContext实例注入到实现ApplicationContextAware接口的Bean中?

与问题9有点类似,这个更加详细的解释。

Spring容器会将容器本身的ApplicationContext实例注入到实现ApplicationContextAware接口的Bean中,是因为ApplicationContextAware是Spring提供的一个接口,用于在Bean实例化后,向Bean对象提供ApplicationContext实例的引用,使得Bean能够访问到Spring容器本身的功能。这样,Bean就可以在自己的代码中访问Spring容器中的其他Bean,以及其他Spring提供的服务,例如配置文件、资源文件等。

实际上,ApplicationContextAware是一个Marker接口,该接口中并没有定义任何方法。通过实现这个接口,Bean并没有向Spring容器注册任何回调函数或事件监听器,而是仅仅向Spring容器表明:这个Bean对Spring容器有一定的依赖,需要Spring容器注入一个ApplicationContext实例。当Spring容器在初始化Bean时,如果发现Bean实现了ApplicationContextAware接口,就会调用Bean的setApplicationContext方法,将ApplicationContext实例注入到Bean中,以满足Bean的依赖需求。

猜你喜欢

转载自blog.csdn.net/weixin_42516475/article/details/130188299