spring boot--自动化注入组件原理、内嵌tomcat-1

news2024/9/24 1:16:10

前言

我们知道开发spring boot项目,在启动类上添加注解@SpringBootApplication ,然后引入要自动注入的组件依赖,然后现application.properties中加上相应配置就可以自动注入这个组件,那么下面看看自动注入组件是如何实现的

一、@SpringBootApplication 注解

1、查看SpringBootApplication 类如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
}

2、查看@EnableAutoConfiguration类

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

这个类又通过@Import({AutoConfigurationImportSelector.class}) 导入了
3、AutoConfigurationImportSelector这个bean,查看这个bean

public class AutoConfigurationImportSelector
		implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
		BeanFactoryAware, EnvironmentAware, Ordered {
		}

4、这个AutoConfigurationImportSelector类继承了DeferredImportSelector最终继承了ImportSelector,重写这个类的selectImports方法可以快速导入一个bean,查看selectImports方法

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata,
				attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return StringUtils.toStringArray(configurations);
	}

5、查看List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
这个方法

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
				getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
		Assert.notEmpty(configurations,
				"No auto configuration classes found in META-INF/spring.factories. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

6、这个方法最终会调用loadSpringFactories方法,这个方法把META-INF/spring.factories定义的类全部读到出来
在这里插入图片描述

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));
                        result.addAll((String)entry.getKey(), factoryClassNames);
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var9) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9);
            }
        }
    }

7、最终spring会根据这些组件中定义的注入条件将这些组件自动注入,org.springframework.boot.autoconfigure下放了所有自动注入的组件,以aop这个组件为例:
在这里插入图片描述

@Configuration
//条件注入,当有 `EnableAspectJAutoProxy.class, Aspect.class, Advice.class,`这些class存在时才注入,也就是说当引入相关依赖包时注入
		AnnotatedElement.class
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
		AnnotatedElement.class })
//当配置文件中有spring.aop 配置时才注入
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = false)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
	public static class JdkDynamicAutoProxyConfiguration {

	}

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = true)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
	public static class CglibAutoProxyConfiguration {

	}

}

二、spring boot内嵌tomcat

最简单的tomcat集成
1、添加pom文件

<dependencies>

        <!--Java语言操作tomcat -->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>8.5.16</version>
        </dependency>

        <!-- tomcat对jsp支持 -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jasper</artifactId>
            <version>8.5.16</version>
        </dependency>

    </dependencies>

2、新建一个servlet文件

public class IndexServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().print("this is index... tomcat");
    }

}

3、新建一个启动类

public class DTomcat {

    private static int PORT = 8080;
    private static String CONTEX_PATH = "/clock";
    private static String SERVLET_NAME = "indexServlet";

    public static void main(String[] args) throws LifecycleException, InterruptedException {

        // 创建tomcat服务器
        Tomcat tomcatServer = new Tomcat();
        // 指定端口号
        tomcatServer.setPort(PORT);
        // 是否设置自动部署
        tomcatServer.getHost().setAutoDeploy(false);
        // 创建上下文
        StandardContext standardContex = new StandardContext();
        standardContex.setPath(CONTEX_PATH);
        // 监听上下文
        standardContex.addLifecycleListener(new Tomcat.FixContextListener());
        // tomcat容器添加standardContex
        tomcatServer.getHost().addChild(standardContex);

        // 创建Servlet
        tomcatServer.addServlet(CONTEX_PATH, SERVLET_NAME, new IndexServlet());
        // servleturl映射
        standardContex.addServletMappingDecoded("/index", SERVLET_NAME);
        tomcatServer.start();
        System.out.println("tomcat服务器启动成功..");
        // 异步进行接收请求
        tomcatServer.getServer().await();

    }
}

4、运行main,在浏览器输入:
http://localhost:8080/clock/index

spring boot内嵌tomcat
1、启动一个spring boot项目,查看控制台最下的日志:
可以看出spring boot在启动的时候,启动一个tomcat,实际上它启动的方式也是上面那么启动方式
在这里插入图片描述
2、tomcat加载流程
tomcat也是一个组件,那么它的引入方式也是通过spring.factories文件注入的
在这里插入图片描述
3、查看ServletWebServerFactoryAutoConfiguration这个类
ServletWebServerFactoryAutoConfiguration这个类用@import快速导入了EmbeddedTomcat类
在这里插入图片描述
4、查看EmbeddedTomcat类
这个类注入了TomcatServletWebServerFactory这个bean

@Configuration
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedTomcat {

		@Bean
		public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
			return new TomcatServletWebServerFactory();
		}

	}

4、查看TomcatServletWebServerFactory类
这个类有一个getWebServer方法如下:
这个方法启动了一个tomcat,那么这个方法是在哪个地方调用的?可以在这个方法上打上断点,查看它的调用链

public WebServer getWebServer(ServletContextInitializer... initializers) {
        Tomcat tomcat = new Tomcat();
        File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        tomcat.getService().addConnector(connector);
        this.customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        this.configureEngine(tomcat.getEngine());
        Iterator var5 = this.additionalTomcatConnectors.iterator();

        while(var5.hasNext()) {
            Connector additionalConnector = (Connector)var5.next();
            tomcat.getService().addConnector(additionalConnector);
        }

        this.prepareContext(tomcat.getHost(), initializers);
        return this.getTomcatWebServer(tomcat);
    }

5、在getWebServer()方法,打断点,然后启动spring boot的main方法,查看调用链如下:
在这里插入图片描述
6、启动流程分析
查看main里面的run方法,
这个方法主要new 了一个SpringApplication对象,然后执行了run方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return (new SpringApplication(primarySources)).run(args);
 }

SpringApplication结构方法:
加载了相关类,没有执行

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		//保存主类
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		//判断当前是什么类型项目
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
	//从类路径下找到META-INF/spring.factories配置的所有ApplicationContextInitializer
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
	//从类路径下找到META-INF/spring.factories配置的所有ApplicationListener
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
}

run方法:

public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	configureHeadlessProperty();
	//从类路径下META‐INF/spring.factories,取得SpringApplicationRunListeners;
	SpringApplicationRunListeners listeners = getRunListeners(args);
//回调所有的获取SpringApplicationRunListener.starting()方法
	listeners.starting();
	try {
	    //封装命令行参数
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(
				args);
	    //准备环境
		ConfigurableEnvironment environment = prepareEnvironment(listeners,
				applicationArguments);
		configureIgnoreBeanInfo(environment);
           //创回调SpringApplicationRunListener.environmentPrepared();
//表示环境准备完成

		//打印Banner 
		Banner printedBanner = printBanner(environment);
           //根据环境创建context
		context = createApplicationContext();
	    //错误的异常报表
		exceptionReporters = getSpringFactoriesInstances(
				SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
            //准备上下文环境;
           //将environment保存到ioc中;
           //applyInitializers()调用所有的ApplicationContextInitializer的initialize方法
//调用所有的SpringApplicationRunListener的contextPrepared();
		prepareContext(context, environment, listeners, applicationArguments,
				printedBanner);
//SpringApplicationRunListener的contextLoaded
//刷新容器
//扫描,创建,加载所有组件;
		refreshContext(context);
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass)
					.logStarted(getApplicationLog(), stopWatch);
		}
		//所有的SpringApplicationRunListener回调started方法
		listeners.started(context);
		//获取所有的ApplicationRunner和CommandLineRunner进行调用
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}

	try {
		//所有的SpringApplicationRunListener的running();
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	return context;
}

三、spring boot内嵌tomcat,修改web容器

从spring boot启动日志看,我们知道spring boot内嵌的web容器是tomcat,那么如果我们不想用tomcat 也可以换别的web容器
1、修改pom
排除tomcat,引入undertow容器

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>
    </dependencies>

这个再启动spring boot项目用的就是undertow容器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/797292.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Idea 结合docker-compose 发布项目

Idea 结合docker-compose 发布项目 这里写目录标题 Idea 结合docker-compose 发布项目Docker 开启远程访问功能 添加相应端口配置IDEA 链接Docker配置项目 docker-compose.yml本地还需要安装 dockerwin11 安装本地Docker 可能存在问题 Linux内核不是最新 Docker 开启远程访问功…

回文链表——力扣234

文章目录 题目描述法一 将值复制到数组中后用双指针法法二 快慢指针 题目描述 法一 将值复制到数组中后用双指针法 class Solution { public:bool isPalindrome(ListNode* head) {vector<int> v;while(head!NULL){v.emplace_back(head->val);head head->next;}for…

同步、异步、阻塞、非阻塞

一、概念 同步与异步&#xff08;线程间调用&#xff09;的区别&#xff1a;关注的是调用方与被调用方之间的交互方式。同步调用会等待被调用方的结果返回&#xff0c;而异步调用则不会等待结果立即返回&#xff0c;可以通过回调或其他方式获取结果。 阻塞非阻塞&#xff08;…

Nautilus Chain 即将治理通证 NAUT ,生态发展进程加速

独特且优势明显的Nautilus Chain 目前&#xff0c;行业内首个模块化底层Nautilus Chain已经上线主网&#xff0c;并且即将有超过70个应用原生部署在Nautilus Chain上。Nautilus Chain本身是一个以Layer3为定位的区块链系统&#xff0c;其通过Celestia模块化底层来获得DA支持以…

网络安全 HVV蓝队实战之溯源

一、前言 对于攻防演练蓝军的伙伴们来说&#xff0c;最难的技术难题可能就是溯源&#xff0c;尤其在今天代理横行的时代更加难以去溯源攻击者。这里我就举两个溯源来帮助大家梳理溯源过程&#xff0c;一个是只溯源到公司&#xff0c;一个是溯源到个人。 二、溯源实例 2.1IP …

逻辑运算符和短路求值

要了解短路求值就必须先了解什么是逻辑运算符。 逻辑运算符 在了解运算符之前我们必须先知道再JAVA中逻辑运算符的结果是Boolean类型的值 逻辑与“&&” 表达式1 && 表达式2 逻辑与就是只有运算符两边的表达式都为真&#xff0c;结果才为真。 表达式1表达式…

2023潮玩盲盒小程序盲盒商城源码(开源+微信登录+支付对接)

潮玩盲盒星尘潮玩盲盒小程序2023潮玩盲盒小程序盲盒商城源码(开源微信登录支付对接)

209. 长度最小的子数组 中等 1.8K

209. 长度最小的子数组 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a; 原题链接&#xff1a; 209. 长度最小的子数组 https://leetcode.cn/problems/minimum-size-subarray-sum/description/ 完成情况&#xff1a; 解题思路&#xff1…

单表操作、查询

十四、单表的增删改查&#xff08;DML语句&#xff09; CRUD&#xff08;增删改查&#xff09; 为空要用is null is not null &#xff0c;不能写null <>为等于&#xff0c;也可以为is <>不等于 十五、单表复杂查询 select语句及关系运算符 除了数字&#x…

ROS学习篇之传感器(三)激光雷达

文章目录 一.确定雷达的型号二.安装驱动1.新建一个工作空间"lidar_ws"&#xff08;随便一个你存放代码的地方&#xff09;2.clone下驱动的源代码&#xff08;在lidar_ws/src目录下&#xff09;3.尝试编译一下&#xff08;在lidar_ws目录下&#xff09; 四.在RVIZ中的…

redis的四种模式优缺点

redis简介 Redis是一个完全开源的内存数据结构存储工具&#xff0c;它支持多种数据结构&#xff0c;以及多种功能。Redis还提供了持久化功能&#xff0c;可以将数据存储到磁盘上&#xff0c;以便在重启后恢复数据。由于其高性能、可靠性和灵活性&#xff0c;Redis被广泛应用于…

JavaSwing+MySQL的医药销售管理系统

点击以下链接获取源码&#xff1a; https://download.csdn.net/download/qq_64505944/88108217?spm1001.2014.3001.5503 JDK1.8 MySQL5.7 功能&#xff1a;用户管理&#xff0c;药品库存管理&#xff0c;进销管理&#xff0c;营销管理

产品解读|有了JMeter,为什么还需要MeterSphere?

提起JMeter&#xff0c;相信大部分的测试人员应该都很熟悉。JMeter因其小巧轻量、开源&#xff0c;加上支持多种协议的接口和性能测试&#xff0c;在测试领域拥有广泛的用户群体。一方面&#xff0c;测试人员会将其安装在个人的PC上&#xff0c;用以满足日常测试工作的需要&…

【Claude2体验】继ChatGPT,文心一言,Bing等大模型后,初次对话Claude2的体验

文章目录 &#x1f33a;注意事项&#x1f916;什么是Claude2⭐与之前版本的进步&#x1f6f8;官网的讲解&#x1f354;功能介绍&#x1f384;使用体验&#x1f386;查看不知道如何才能打开的文档 的内容&#x1f386;日常需求✨Claude✨ChatGPT3.5 &#x1f916;总结 &#x1f…

CC1310F128系列 超低功耗低于1GHz射频 微控制器芯片

CC1310F128 是一款经济高效型超低功耗低于1GHz射频器件&#xff0c;凭借极低的有源射频和MCU电流消耗以及灵活的低功耗模式&#xff0c;CC1310F128可确保卓越的电池寿命&#xff0c;并能够在小型纽扣电池供电的情况下以及在能量采集应用中实现远距离工作。 改芯片有三个后缀&am…

redis-cluster 创建及监控

集群命令 cluster info&#xff1a;打印集群的信息。 cluster nodes&#xff1a;列出集群当前已知的所有节点&#xff08;node&#xff09;的相关信息。 cluster meet <ip> <port>&#xff1a;将ip和port所指定的节点添加到集群当中。 cluster addslots <slot…

小程序商城免费搭建之java商城 java电子商务Spring Cloud+Spring Boot+mybatis+MQ+VR全景+b2b2c

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…

libhv之hloop_process_ios源码分析

上一篇文章对hloop源码大概逻辑和思想进行了一个简单的分析&#xff0c;其中主要涉及三类&#xff08;timer>io>idles&#xff09;事件处理。下面将对hloop_process_ios事件做一下简单的分析。 int hloop_process_ios(hloop_t* loop, int timeout) {// That is to call …

vr禁毒毒驾模拟体验从源头拒绝毒品,预防毒品

俗话说&#xff0c;一念天堂&#xff0c;一念地狱。吸毒一口&#xff0c;掉入虎口。吸毒对人体的危害非常大&#xff0c;普通人吸毒会导致家破人亡&#xff0c;明星吸毒会毁掉自己的大好星途。没有感同身受&#xff0c;何来悲喜相通&#xff0c;毒品危害认知VR模拟情景体验是VR…

助力工业物联网,工业大数据之油站事实指标需求分析【十九】

文章目录 01&#xff1a;油站事实指标需求分析02&#xff1a;油站事实指标构建 01&#xff1a;油站事实指标需求分析 目标&#xff1a;掌握DWB层油站事实指标表的需求分析 路径 step1&#xff1a;目标需求step2&#xff1a;数据来源 实施 目标需求&#xff1a;基于油站信息及…