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 {
}