IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Sping高级(源码)--- 1.容器和Bean -> 正文阅读

[Java知识库]Sping高级(源码)--- 1.容器和Bean

一、容器和Bean

1.1容器接口

1.1.1BeanFacroty能做的事

image-20220926165926150


?什么是BeanFactory

  • 他是 ApplicationContext 的父接口
  • 他才是 Spring 的核心容器,主要的ApplicationContext 实现都【组合】了它的功能

?BeanFactory能做些什么

image-20220926171114603


  • 表面上只有 getBean()
  • 实际上IOC、基本的依赖注入、直至Bean的生命周期的各种功能,都是由他的实现类(DefaultListableBeanFactory)提供

🖼 DefaultListableBeanFactory继承关系图:

image-20220926172437118

其中,DefaultSingletonBeanRegistry是单例实现类:

image-20220926172603418

其中singletonObjects是所有单例的集合,因为是私有的,我们通过反射获取其中的值:

@SpringBootApplication
public class  DemoApplication {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);

        Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
        //关闭安全检查
        singletonObjects.setAccessible(true);

        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        Map<String,Object> map = (Map<String,Object>)singletonObjects.get(beanFactory);
        map.forEach((k,v)->{
            System.out.println(k+"==="+v);
        });
    }
}

运行结果较长,可以自行实验

1.1.2ApplicationContext有哪些拓展功能

image-20220926202303633

主要提现在这些类上

MessageSource:处理国际化资源,程序支持多种语言时,支持翻译的能力

示例:

springBoot项目里,未来方便支持多语言,会编写语言转换的文件,比如下面的

image-20220926203631491

其中左边的hi,是程序中所用到的,右边的是翻译出来的值,而MessageSource可以将值具体显示

		ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		System.out.println(context.getMessage("hi", null, Locale.CHINA));
        System.out.println(context.getMessage("hi", null, Locale.ENGLISH));
        System.out.println(context.getMessage("hi", null, Locale.JAPANESE));

运行结果:

你好
hello
こんにちは

ResourcePatternResolver

查找资源路径,返回资源数组

		//查找jar包下的spring.factories文件
		Resource[] resources = context.getResources("classpath*:META-INF/spring.factories");
        for (Resource resource : resources) {
            System.out.println(resource);
        }

运行结果:

URL [jar:file:/E:/environment_tools/apache-maven-3.8.1/maven-repo/org/springframework/boot/spring-boot/2.3.7.RELEASE/spring-boot-2.3.7.RELEASE.jar!/META-INF/spring.factories]
URL [jar:file:/E:/environment_tools/apache-maven-3.8.1/maven-repo/org/springframework/spring-beans/5.2.12.RELEASE/spring-beans-5.2.12.RELEASE.jar!/META-INF/spring.factories]
URL [jar:file:/E:/environment_tools/apache-maven-3.8.1/maven-repo/com/baomidou/mybatis-plus-boot-starter/3.4.2/mybatis-plus-boot-starter-3.4.2.jar!/META-INF/spring.factories]
URL [jar:file:/E:/environment_tools/apache-maven-3.8.1/maven-repo/org/springframework/boot/spring-boot-autoconfigure/2.3.7.RELEASE/spring-boot-atoconfigure-2.3.7.RELEASE.jar!/META-INF/spring.factories]

EnvironmentCapable

获取资源环境的值

		//获取java_home路径
		System.out.println(context.getEnvironment().getProperty("java_home"));
		//获取key为server.port的值,就properties文件内有
        System.out.println(context.getEnvironment().getProperty("server.port"));

运行结果:

C:\Program Files\Java\jdk-17.0.3.1
8080

ApplicationEventPublisher

事件处理(监听、跟踪),其中有三个组成部分:

  • 被监听对象source(也称为事件源)
  • 事件event
  • 监听对象listener

我们模拟监听和发送来测试一下

  • 首先编写一个注册事件
public class UserRegisteredEvent extends ApplicationEvent {

    /**
     * Create a new {@code ApplicationEvent}.
     *
     * @param source the object on which the event initially occurred or with
     *               which the event is associated (never {@code null})
     */
    public UserRegisteredEvent(Object source) {
        super(source);
    }
}
  • 发送事件
        ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
	    context.publishEvent(new UserRegisteredEvent(context));
  • 监听器(收事件)
@Slf4j
@Component
public class Receive {
    
    //方法返回值、名称无要求,对参数有要求,必须加注解
    @EventListener
    public void listen(UserRegisteredEvent event){
        log.debug("监听事件:{}",event);
    }
}
  • 测试
监听事件com.spring.demo.UserRegisteredEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6531a794, started on Tue Sep 27 20:24:43 CST 2022]

接下来来看发布事件最重要的功能:

1.1.3事件解耦

使用ApplicationEventPublisher可以实现事件解耦,比如注册之后需要发送邮件或者短信,但是在其他功能可能需要其他的功能,可能是变化的,所以这时再一起用耦合度就太高了

事件解耦的例子:

假如我们此时有注册之后发短信的需求,正常情况下我们可能这样写:

	public void register(){
        logger.debug("用户注册");
        logger.debug("发短信");
    }

但是我们如果有一天需要把发短信给改了,改成发邮件,如果我们一行一行改代码不就和麻烦了吗,这时我们就需要监听事件了

  • 用户注册
@Component
public class Send {
    private static final Logger logger = LoggerFactory.getLogger(Receive.class);

    //监听事件的父接口
    @Autowired
    private ApplicationEventPublisher context;

    public void register(){
        logger.debug("用户注册");
        //监听该事件,通知需要发短信了
        context.publishEvent(new UserRegisteredEvent(this));
    }
}
  • 监听
@Component
public class Receive {

    private static final Logger logger = LoggerFactory.getLogger(Receive.class);

    @EventListener
    public void listen(UserRegisteredEvent event){
        logger.debug("监听事件:{}",event);
        logger.debug("发送一条短信");
//        System.out.println("监听事件"+event);
    }
}
  • 测试
		 //注册
        context.getBean(Send.class).register();
  • 运行结果
用户注册
监听事件:com.spring.demo.UserRegisteredEvent[source=com.spring.demo.Send@6ee99964]
发送一条短信

1.2容器实现

1.2.1BeanFactory实现的特点

1??例子1

运行结果的解释说明(先看实例再看这个):

该实例模拟在最原始的情况下,spring对各bean的加载情况:

  • 实例1:
    • 最原始的情况下,不配置BeanFactory后置解析器的情况下,仅仅只有config这个bean被输出
    • 配置BeanFactory后置解析器(AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory))之后,可以解析出一部分注解(自行打印测试),但是无法解析出自己定义的注解,比如bean1和bean2
    • 继续进行后置解析器配置,此时添加可以解析BeanFactoryPostProcessor.class,此时会有更多的bean输出,比如自定义的bean1和bean2
  • 实例2:
    • 在实例1的基础上,直接运行获得的结果实际是null,实例2下面有解释说明,此时@Autowired是没有被解析的
    • 同实例1,继续进行后置解析器配置,不过此时配置的是Bean的后置解析器BeanPostProcessor.class,并调用beanFactory.addBeanPostProcessor(),给解析的内容赋值,此时运行才能发现运行结果是个实例对象,不再是null
    • 可以看详细的运行结果,spring容器创建出bean1这个对象之后,解析到了bean2,接着创建了bean2的容器,然后打印了bean2的无参构造

实例1:BeanFactory处理器解析一些注解

package com.spring.demo.demo02;

/**
 * @author 我见青山多妩媚
 * @date Create on 2022/9/27 21:03
 */
public class TestBeanFactory {
    public static void main(String[] args) {
        //1.创建原始的beanFactory
        //BeanFactory的一个重要实现!!! 刚创建时,内部是没有任何bean的,用来模拟最原始的bean
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        //2.将新建的类做加载
        //bean 的定义 (class,scope(单例、多例),初始化,销毁),让BeanFactory根据定义去创建Bean
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();

        //3.加载后的bean注册到beanFactory
        //注册beanDefinition
        beanFactory.registerBeanDefinition("config",beanDefinition);


        //4.给beanFactory添加一些常用的后处理器,默认的BeanFactory是无法解析注解的
        AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

        //5.解析其他bean,除了自己加的config之外,可以解析出自己加的bean1和bean2
        //beanFactory后处理器的主要功能就是对一些bean进行了补充(比如自己写的bean1和bean2)
        beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().stream().forEach(s->{
            s.postProcessBeanFactory(beanFactory);
        });

        for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
    }

    @Configuration
    static class Config{
        @Bean
        public Bean1 bean1(){return new Bean1();}

        @Bean
        public Bean2 bean2(){return new Bean2();}
    }


    @Slf4j
    static class Bean1{
        public Bean1() {
            log.info("构造Bean1()");
        }

        @Autowired
        private Bean2 bean2;

        public Bean2 getBean2(){return bean2;}

    }

    @Slf4j
    static class Bean2{
        public Bean2() {
            log.info("构造Bean2()");
        }
    }
}

运行结果:

config
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
bean1
bean2

实例2:Bean后处理器解析注解

我们bean1中有对bean2的引用,此时我们打印一下

System.out.println(beanFactory.getBean(Bean1.class).getBean2());

他的结果是null,这是为什么呢?因为beanFactory并没有对该注解(@Autowired)的加载,自然无法注入,所以我们需要另一个后处理器来加载该注解

//Bean的后处理器,针对bean生命周期的各个阶段提供的功能,比如解析@Autowired @Resource....      			
beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);
//打印
System.out.println(beanFactory.getBean(Bean1.class).getBean2());

运行结果:

22:17:58.904 [main] INFO com.spring.demo.demo02.TestBeanFactory$Bean1 - 构造Bean1()
22:17:58.914 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean2'   //解析到了@Autowired注解,创建bean2对象
22:17:58.915 [main] INFO com.spring.demo.demo02.TestBeanFactory$Bean2 - 构造Bean2()
com.spring.demo.demo02.TestBeanFactory$Bean2@48f2bd5b

总结:

  • beanFactory不会做到的事:

    • 不会主动调用BeanFactory后处理器

    • 不会主动添加Bean后处理器

    • 不会主动初始化单例

    • 不会解析beanFactory,还不会解析${}#{}

  • bean后处理器会有排序的顺序:详细看例子2

2??例子2:关于@Autowired@Resource

@Autowired

我们对TestBeanFactory对象进行修改,新增bean3、bean4,并在config类中进行配置,main方法内的代码和上面一样

    @Configuration
    static class Config{
        @Bean
        public Bean1 bean1(){return new Bean1();}

        @Bean
        public Bean2 bean2(){return new Bean2();}

        @Bean
        public Bean3 bean3(){return new Bean3();}

        @Bean
        public Bean4 bean4(){return new Bean4();}

    }


    @Slf4j
    static class Bean1{
        public Bean1() {
            log.info("构造Bean1()");
        }

        @Autowired
        private Bean2 bean2;

        public Bean2 getBean2(){return bean2;}

        //注入inter
        @Autowired
        private Inter inter;

        public Inter getInter() {
            return inter;
        }
    }

    @Slf4j
    static class Bean2{
        public Bean2() {
            log.info("构造Bean2()");
        }
    }

    interface Inter {}

    static class Bean3 implements Inter{}

我们在bean1内,注入的是inter,那么我们使用 @Autowired注解,会成功的把inter注入进容器内吗?显然是不行的,因为他有两个实现类,分别是bean3和bean4,他不知道找哪一个,所以自然就找不到,那么我们把

		@Autowired
        private Inter inter;
		//换成
		@Autowired
        private Inter bean3;

会成功注入吗?显然是会的,原因是@Autowired,注解声明对象时,会先根据类型匹配,匹配失败后才会去根据名称匹配,优先匹配到名称相同的

测试:

System.out.println(beanFactory.getBean(Bean1.class).getInter());

结果:

com.spring.demo.demo02.TestBeanFactory$Bean3@30c15d8b

实验结果也验证了我们的猜想

@Resource

类似的,@Resource@Autowired一样,不过@Resource可以指定声明的bean是哪个,并且注解内的优先级比变量名高

		@Resource(name = "bean4")
        private Inter bean3;

运行结果也与上面一样 ,不过结果是bean4的罢了

那么如果两个注解同时加了呢?

@Autowired和@Resource

两个同时加的情况:

		@Autowired
        @Resource(name = "bean4")
        private Inter bean3;

运行结果怎么样呢?

com.spring.demo.demo02.TestBeanFactory$Bean3@3c46e67a

没有报错,而是先注入的bean3,这是为什么呢?

因为后处理器的执行顺序,解析@Autowired的后处理器比解析@Resource的先执行,所以优先级就会比较高,我们可以改变一下main方法里的一段代码来看一下:

 		beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanPostProcessor -> {
            System.out.println(">>>"+beanPostProcessor);
            beanFactory.addBeanPostProcessor(beanPostProcessor);
        });

运行结果:

//解析@Autowried的后处理器
>>>org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@7b9a4292
//解析@Resource的后处理器
>>>org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@4a94ee4

当然也可以改变后处理器的解析顺序,那么@Resource就会比@Autowried先执行

1.2.2ApplicationContext

关于ApplicationContext的四个容器,新建TestApplication类,以下四个经典的类都是在该类下的方法

@Slf4j
public class TestApplication {
    public static void main(String[] args) {

    }

    static class Bean1{}

    static class Bean2{

        private Bean1 bean1;

        public void setBean1(Bean1 bean1) {
            this.bean1 = bean1;
        }

        public Bean1 getBean1() {
            return bean1;
        }
    }
}
  1. 较为经典的容器,基于classpath下xml格式的配置文件来创建
 //1.较为经典的容器,基于classpath下xml格式的配置文件来创建
    private static void testClassPathXmlApplicationContext(){
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("testClassPathXmlApplication.xml");
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }

        System.out.println(context.getBean(Bean2.class).getBean1());
    }

resource路径下创建xml文件,写入

	<!--内部类的注入,类名.内部类-->
	<bean id="bean1" class="com.spring.demo.demo02.TestApplication.Bean1"/>

    <bean id="bean2" class="com.spring.demo.demo02.TestApplication.Bean2">
        <property name="bean1" ref="bean1"/>
    </bean>

测试:

testClassPathXmlApplicationContext();

结果:

bean1
bean2
com.spring.demo.demo02.TestApplication$Bean1@31190526
  1. 基于磁盘路径下xml格式的配置文件来创建
//2.基于磁盘路径下xml格式的配置文件来创建
private static void testFileSystemXmlApplicationContext(){
    //xml使用的是1的xml文件
    FileSystemXmlApplicationContext context = new       FileSystemXmlApplicationContext("E:\\Demo\\JavaDemo\\SpringSourceCodeDemo\\src\\main\\resources\\testClassPathXmlApplication.xml");

    for (String name : context.getBeanDefinitionNames()) {
        System.out.println(name);
    }

    System.out.println(context.getBean(Bean2.class).getBean1());
}

测试:

testFileSystemXmlApplicationContext()

结果:

bean1
bean2
com.spring.demo.demo02.TestApplication$Bean1@6743e411
  1. 较为经典的实现,基于java配置类来创建
    //3.较为经典的实现,基于java配置类来创建
    private static void testAnnotationConfigApplicationContext(){
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(Config.class);

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }

        System.out.println(context.getBean(Bean2.class).getBean1());
    }

    @Configuration
    static class Config{
        @Bean
        public Bean1 bean1(){
            return new Bean1();
        }

        @Bean
        public Bean2 bean2(Bean1 bean1){
            Bean2 bean2 = new Bean2();
            bean2.setBean1(bean1);
            return bean2;
        }
    }

测试:

testAnnotationConfigApplicationContext();

结果:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
testApplication.Config
bean1
bean2
com.spring.demo.demo02.TestApplication$Bean1@f2ff811

给我们加入了后置处理器,不用再手动加了,以前的比如xml方式如果要开启,就在xml内配置<context:annotation-config/>,开启注解模式,实际上就是他给我们自动配置了后处理器

1.2.3内嵌容器、注册DispatcherServlet

  1. 较为经典的容器,基于java配置类来创建,用于web环境
 //4.较为经典的容器,基于java配置类来创建,用于web环境
    private static void testAnnotationConfigServletWebServerApplicationContext(){
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    }

    @Configuration
    static class WebConfig{
        //提供web容器的组件
        @Bean
        public ServletWebServerFactory servletWebServerFactory(){
            return new TomcatServletWebServerFactory();
        }

        //请求的入口:前控制器
        @Bean
        public DispatcherServlet dispatcherServlet(){
            return new DispatcherServlet();
        }

        //tomcat与servlet关联
        @Bean
        public DispatcherServletRegistrationBean registrationBean( DispatcherServlet dispatcherServlet){
            return new DispatcherServletRegistrationBean(dispatcherServlet,"/"); // 所有请求
        }

        //控制器,bean起名如果是/开头,那么就会识别这个路径,
        @Bean("/hello")
        public Controller controller1(){
            return (request,response)->{
                response.getWriter().print("hello");
                return null;
            };
        }

测试:

testAnnotationConfigServletWebServerApplicationContext();

结果:

运行成功之后,打开浏览器,输入localhost:8080/hello,可以看到页面会返回一个hello

image-20220929201840387


web服务的一些说明:

  • 前三个bean是必须的,他帮我们创建spring内置的tomcat容器,得以运行web项目
  • 第四个bean实际上类似web中的@RequestMapping(),当以/开头时,就是指定的路径
  • 实际上,springboot的web应用层内的注解,大多数底层都是这些,只是帮我们封装好了
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-10-08 20:27:03  更:2022-10-08 20:28:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年3日历 -2025/3/10 15:21:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码