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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值