当前位置: 首页 > news >正文

01.简单梳理模拟SpringBoot自动装配的原理(代码测试代码)

目标:

  • 模拟SpringBoot启动过程
  • 模拟SpringBoot条件注解功能
  • 模拟SpringBoot 自动配置功能
  • SpringBoot如何整合多个WebServer 服务进行启动

一、SpringBoot如何选择WebServer容器

SpringBoot 依赖Spring 注解开发。也就是我们的服务抽象到SpringIOC容器中,使用AOP进行封装。

1.@SpringBootApplication注解标注的类是一个配置类

1.1 创建一个容器,存放Bean对象,需要外部传入 @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 {
	......
}

----------------------------进一步封装----------------------------------------------------------
    
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;

}

@EnableAutoConfiguration / @Configuration /@CompontScan
所以扫描当前类所在的包路径

@SpringBootApplication
public class SimulationApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(SimulationApplication.class, args);

    }
}

SimulationApplication 相当于是一个配置类,使用SpringBoot注解,底层使用的是Spring容器去加载Bean到容器中。

1.2 启动Tomcat容器

package com.tomdd.simulation;

import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

/**
 * @author zx
 * @date 2022年09月24日 11:26
 */
public class TomSpringApplication {
    public static void run(Class<?> clazz) {
        //web 上下文
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        //配置类进行注册
        context.register(clazz);
        //刷新容器---走得是SpringIOC容器的Bean的生命周期
        context.refresh();
        //启动Tomcat服务
        startTomcat(context);
    }

    private static void startTomcat(WebApplicationContext applicationContext) {
        Tomcat tomcat = new Tomcat();

        Server server = tomcat.getServer();
        Service service = server.findService("Tomcat");

        Connector connector = new Connector();
        connector.setPort(8081);

        Engine engine = new StandardEngine();
        engine.setDefaultHost("localhost");

        Host host = new StandardHost();
        host.setName("localhost");

        String contextPath = "";
        Context context = new StandardContext();
        context.setPath(contextPath);

        context.addLifecycleListener(new Tomcat.FixContextListener());

        host.addChild(context);
        engine.addChild(host);

        service.setContainer(engine);
        service.addConnector(connector);

        tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(applicationContext));
        context.addServletMappingDecoded("/*", "dispatcher");
        try {
            tomcat.start();
        } catch (LifecycleException e) {
            throw new RuntimeException(e);
        }
    }
}

run方法传入的类是一个配置类!!!!

1.3 SpringBoot如何选择依赖的webServer

SpringBoot默认支持Tomcat ,那么SpringBoot如何根据Maven 依赖选择:WebServer(Tom /Jetty /underTow)
SpringBoot启动WebServer的时候需要判断用户依赖的那种WebServer。**条件注解: **@Condition 条件注解. / @ConditionOnClass注解

1.3.1 定义webServer接口

启动服务的具体逻辑由子类自己去实现

package com.tomdd.simulation;

import org.springframework.web.context.WebApplicationContext;

/**
 * web服务
 *
 * @author zx
 * @date 2022年09月24日 11:48
 */
public interface WebServer {
    void startServer(WebApplicationContext applicationContext);
}
package com.tomdd.simulation;

import org.springframework.web.context.WebApplicationContext;

/**
 * tomcat webserver
 *
 * @author zx
 * @date 2022年09月24日 11:49
 */
public class TomCatWebServer implements WebServer{
    @Override
    public void startServer(WebApplicationContext applicationContext) {
        System.out.println("tomcat web server");
    }
}

package com.tomdd.simulation;

import org.springframework.web.context.WebApplicationContext;

/**
 * Jetty webserver
 *
 * @author zx
 * @date 2022年09月24日 11:49
 */
public class JettyWebServer implements WebServer{
    @Override
    public void startServer(WebApplicationContext applicationContext) {
        System.out.println("jetty web server");
    }
}

1.3.2 SpringBoot启动服务的时候根据webServer进行启动

使用Java多态机制,这里需要考虑如何判断启动那个服务???

package com.tomdd.simulation;

import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import java.util.Map;

/**
 * @author zx
 * @date 2022年09月24日 11:26
 */
public class TomSpringApplication {
    public static void run(Class<?> clazz) {
        //web 上下文
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        //配置类进行注册
        context.register(clazz);
        //刷新容器---走得是SpringIOC容器的Bean的生命周期
        context.refresh();
        //启动Tomcat服务
        //startTomcat(context);

        //获取webserver: tomcat /jetty /undertow
        WebServer webserver = getWebServer(context);
        webserver.startServer(context);
    }

    private static WebServer getWebServer(AnnotationConfigWebApplicationContext context) {
        //获取webserver: tomcat /jetty /undertow
        //判断容器中有那个一个webserver ,然后进行返回 如果找到2个抛出异常
        //WebServer webServer = context.getBean(WebServer.class);

        Map<String, WebServer> webServerMap = context.getBeansOfType(WebServer.class);
        if (webServerMap.isEmpty()) {
            throw new NullPointerException("没有web 服务,请引入tomcat | jetty | undertow");
        }
        if (webServerMap.size() > 1) {
            throw new IllegalStateException("有多个web 服务容器");
        }

        return webServerMap.values().stream().findFirst().get();
    }


}

1.3.3 定义条件判断逻辑

package com.tomdd.simulation.tomcondition;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

import java.util.Map;
import java.util.Objects;

/**
 * <h1>条件注解</h1>
 *
 * @author zx
 * @date 2022年09月24日 11:56
 */
public class TomCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //这里写判断逻辑,true 符合、fasle 不符合
        Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ConditionalOnClass.class.getName());
        assert annotationAttributes != null;
        String className = (String) annotationAttributes.get("value");

        try {
            //加载的className存在返回true,不存在抛出异常,返回false
            Objects.requireNonNull(context.getClassLoader()).loadClass(className);
            return true;
        } catch (ClassNotFoundException e) {

            return false;
        }

    }
}

1.3.4 创建配置类使用@ConditionalOnClass 注解指定服务类

package com.tomdd.simulation.config;

import com.tomdd.simulation.JettyWebServer;
import com.tomdd.simulation.TomCatWebServer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author zx
 * @date 2022年09月24日 11:54
 */
@Configuration
public class WebServerAutoConfiguration {

    //如果有2个.如何处理?????

    @Bean
    @ConditionalOnClass(name = {"org.apache.catalina.startup.Tomcat"})
    public TomCatWebServer tomCatWebServer() {
        return new TomCatWebServer();
    }

    @Bean
    @ConditionalOnClass(name = "org.eclipse.jetty.server.Server")
    public JettyWebServer jettyWebServer() {
        return new JettyWebServer();
    }
}

1.3.5 SpringBootApplication注解上导入改配置类

package com.tomdd.simulation.anno;

import com.tomdd.simulation.config.WebServerAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@ComponentScan
@Import(WebServerAutoConfiguration.class)
public @interface TomSpringBootApplicaion {
}

二、SpringBoot如何自动配置

自动配置,SpringBoot 给我们创建Bean.比如事务管理器、AOP.在配置类上加上了开启事务管理器、开启aop代理。我们再使用的时候都不用去开启的注解了。
多个配置类,SpringBoot如何加入到SpringIOC容器中的。SpringBoot如何解决?

  • 配置类引入静态内部类作为配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(Advice.class)
	static class AspectJAutoProxyingConfiguration {

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

		}

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

		}

	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
			matchIfMissing = true)
	static class ClassProxyingConfiguration {

		ClassProxyingConfiguration(BeanFactory beanFactory) {
			if (beanFactory instanceof BeanDefinitionRegistry) {
				BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
				AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}
		}

	}

}

SPI 机制 实现自动配置生效

2.1 创建自动配置类顶层接口

package com.tomdd.simulation.config;

/**
 * 自动配置类接口
 *
 * @author zx
 * @date 2022年09月24日 12:56
 */
public interface AutoConfiguration {
}

2.2 在resoruce 目录下创建META-INF

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JFDAUFE2-1663997733225)(https://note.youdao.com/yws/public/resource/e9a003de9106cb8f061997cc813b2af1/xmlnote/7FE5239660C24193BE5F92A6D85E2913/6068)]

文件名称都是为配置类接口的包名+类名

com.tomdd.simulation.config.WebServerAutoConfiguration
com.tomdd.simulation.config.TransactionAutoConfiguration
com.tomdd.simulation.config.AopAutoConfiguration

自己创建的配置类实现AutoConfiguration接口(改接口是一个空接口都可以的)

2.3 基于SPI加载这些实现这个接口的类

package com.tomdd.simulation.tomimport;

import com.tomdd.simulation.config.AutoConfiguration;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata;

import java.util.ArrayList;
import java.util.ServiceLoader;

/**
 * <h1>批量导入配置类</h1>
 * 这个 @ImportSelect 也是导入功能
 * <p>
 * SpringBoot spring.factory  SPI机制
 *
 * @author zx
 * @date 2022年09月24日 12:54
 */
public class TomBatchImport implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //基于java SPI 进行加载
        ServiceLoader<AutoConfiguration> serviceLoader = ServiceLoader.load(AutoConfiguration.class);
        ArrayList<String> list = new ArrayList<>();
        for (AutoConfiguration autoConfiguration : serviceLoader) {
            list.add(autoConfiguration.getClass().getName());
        }
        return list.toArray(new String[0]);
    }
}

@ImportSelector 和@DeferredImportSelector的区别

  • DeferredImportSelector 改注解 有一个先后顺序,会先把所有配置类加载后,在执行实现这个接口,也就是说先把我们程序员写的一些配置类加载完毕后,再执行这个导入 ,这个很多好的结合条件注解的
  • ImportSelector 是没有这个功能,直接导入,没有延迟的效果。

2.4 改造TomSpringBootApplication注解

package com.tomdd.simulation.anno;

import com.tomdd.simulation.tomimport.TomBatchImport;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@ComponentScan
@Import(TomBatchImport.class)
public @interface TomSpringBootApplicaion {
}

相关文章:

  • 谷粒商城 高级篇 (二十三) --------- 订单业务
  • (十)DDRC架构组成、效率Efficiency及功能实现
  • spring整体脉络
  • 路由多视图单页应用router-link相关属性
  • ICCV 2021 Oral | CoaT: Co-Scale Conv-Attentional Image Transformers
  • js单行代码------数组
  • 量化C++国产框架千星+ WonderTrader
  • Java基于ssm+vue+nodejs的私人牙科门诊预约系统element
  • 微服务项目:尚融宝(56)(核心业务流程:投资列表展示(1))
  • 【文档资料】Linux、Vi/Vim常用命令
  • javascript入门经典 第6版
  • Pr 视频效果:键控
  • 【JavaScript】五个常用功能/案例:高精度乘法 | 批量修改对象属性 | 属性遍历 | 判断是否包含数字 | 检查重复字符串
  • idea运行项目错误 Error running ‘XXXXXXXApplication‘: Command line is too long
  • 计算机/信息类保研er--不同档次学校问的问题类型
  • @jsonView过滤属性
  • [译] 怎样写一个基础的编译器
  • 【腾讯Bugly干货分享】从0到1打造直播 App
  • 【跃迁之路】【735天】程序员高效学习方法论探索系列(实验阶段492-2019.2.25)...
  • 4月23日世界读书日 网络营销论坛推荐《正在爆发的营销革命》
  • CentOS7 安装JDK
  • css系列之关于字体的事
  • JavaScript异步流程控制的前世今生
  • Laravel Mix运行时关于es2015报错解决方案
  • node学习系列之简单文件上传
  • Protobuf3语言指南
  • ReactNative开发常用的三方模块
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 包装类对象
  • 当SetTimeout遇到了字符串
  • 二维平面内的碰撞检测【一】
  • 构建二叉树进行数值数组的去重及优化
  • 记一次用 NodeJs 实现模拟登录的思路
  • 学习Vue.js的五个小例子
  • 赢得Docker挑战最佳实践
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • elasticsearch-head插件安装
  • (51单片机)第五章-A/D和D/A工作原理-A/D
  • (day 2)JavaScript学习笔记(基础之变量、常量和注释)
  • (二开)Flink 修改源码拓展 SQL 语法
  • (附源码)计算机毕业设计SSM基于java的云顶博客系统
  • (强烈推荐)移动端音视频从零到上手(上)
  • (亲测成功)在centos7.5上安装kvm,通过VNC远程连接并创建多台ubuntu虚拟机(ubuntu server版本)...
  • (四)鸿鹄云架构一服务注册中心
  • (淘宝无限适配)手机端rem布局详解(转载非原创)
  • (译)计算距离、方位和更多经纬度之间的点
  • (原创)Stanford Machine Learning (by Andrew NG) --- (week 9) Anomaly DetectionRecommender Systems...
  • * 论文笔记 【Wide Deep Learning for Recommender Systems】
  • .bat批处理(一):@echo off
  • .NET Micro Framework初体验(二)
  • .NET 材料检测系统崩溃分析
  • .NET 反射 Reflect
  • .Net 知识杂记
  • .NET设计模式(8):适配器模式(Adapter Pattern)
  • .Net转前端开发-启航篇,如何定制博客园主题