SpringBoot中使用了内嵌的Tomcat服务器,在SSM中是通过外部配置来将项目部署的。如何在SpringBoot中集成Tomcat容器
模拟启动Tomcat
public class SpringApp {
public static ApplicationContext run(Class clazz){
WebApplicationContext context = new AnnotationConfigServletWebApplicationContext(clazz);
startServer(context);
return context;
}
private static void startServer(WebApplicationContext applicationContext) {
Tomcat tomcat = new Tomcat();
Server server = tomcat.getServer();
Service service = server.findService("Tomcat");
Connector connector = new Connector();
connector.setPort(8500);
Engine engine = new StandardEngine();
engine.setDefaultHost("localhost");
String contextPath = "";//项目的context-path
Context context = new StandardContext();
context.setPath(contextPath);
context.addLifecycleListener(new Tomcat.FixContextListener());
Host host = new StandardHost();
host.setName("localhost");
host.addChild(context);
engine.addChild(host);
service.setContainer(engine);
//为tomcat容器添加servlet映射
tomcat.addServlet(contextPath,"dispatcherServlet",new DispatcherServlet(applicationContext));
context.addServletMappingDecoded( "/*","dispatcherServlet");
try {
tomcat.start();
} catch (LifecycleException e) {
e.printStackTrace();
}
}
}
如果是以嵌入式servlet容器,像SpringBoot的这种方式在启动服务器之前,需要为服务器添加Servlet,且指定这个Servlet能处理哪个请求,来代替web.xml的方式。在创建好整个容器以后,交给dispatcherServlet。这样整个web应用就创建好了。
主启动类
@SpringApp
public class App {
public static void main(String[] args) {
com.tomcat.run.SpringApp.run(App.class);
}
}
在SpringBoot的启动过程中,如何根据引入的jar包启动不同的服务器?这就和SpringBoot的自动配置有关了,在SpringBoot启动过程中,会扫描所有classpath下的jar包的META-INF/spring.factories,其中这个文件通过org.springframework.boot.autoconfigure.EnableAutoConfiguration指定了一个配置类,但是这些配置类不是一般的配置类,是我们常说的自动配置类,通过加载这些类,往往又会加载这些类绑定的Properties配置文件,然后创建好Bean,返回到容器中。
但是呢,这些自动配置类并不是都会生效的,通常会搭配大量的@Condition注解使用。例如ServletWebServerFactoryAutoConfiguration
@Configuration(
proxyBeanMethods = false
)
@AutoConfigureOrder(-2147483648)
@ConditionalOnClass({ServletRequest.class})
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@EnableConfigurationProperties({ServerProperties.class})
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {}
@ConditionalOnClass({ServletRequest.class}),当你的类路径下有ServletRequest这个类时,这个自动配置类才会生效,类路径是什么?其实就是判断当前项目是否引入了ServletRequest的jar包。而ServletRequest是存在于servlet包下的,也就是说需要引入servlet的相关依赖,这个自动配置类才会生效。点开jetty或undertow的依赖,发现这两个依赖中就引入了servlet的依赖。
也就是说当引入了jetty或undertow或tomcat的依赖时,这个自动配置类就生效了。
一旦自动配置类生效,就会导入
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
而在导入这些类时,依然有条件判断
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({Servlet.class, Undertow.class, SslClientAuthMode.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
static class EmbeddedUndertow {
EmbeddedUndertow() {
}
@Bean
UndertowServletWebServerFactory undertowServletWebServerFactory(ObjectProvider<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers, ObjectProvider<UndertowBuilderCustomizer> builderCustomizers) {
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
factory.getDeploymentInfoCustomizers().addAll((Collection)deploymentInfoCustomizers.orderedStream().collect(Collectors.toList()));
factory.getBuilderCustomizers().addAll((Collection)builderCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
@Bean
UndertowServletWebServerFactoryCustomizer undertowServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
return new UndertowServletWebServerFactoryCustomizer(serverProperties);
}
}
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({Servlet.class, Server.class, Loader.class, WebAppContext.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
static class EmbeddedJetty {
EmbeddedJetty() {
}
@Bean
JettyServletWebServerFactory JettyServletWebServerFactory(ObjectProvider<JettyServerCustomizer> serverCustomizers) {
JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
factory.getServerCustomizers().addAll((Collection)serverCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
static class EmbeddedTomcat {
EmbeddedTomcat() {
}
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers, ObjectProvider<TomcatContextCustomizer> contextCustomizers, ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers().addAll((Collection)connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers().addAll((Collection)contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers().addAll((Collection)protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
只有引入每个servlet容器自己的依赖时,相对应的配置类才会生效,例如拿jetty来说
@ConditionalOnClass({Servlet.class, Server.class, Loader.class, WebAppContext.class})
类路径下有以上四个类时才会生效,而Loader这个类是属于jetty包的,所以只有引入了jetty相关依赖这个配置类就生效的。一旦这个配置类生效,就会给容器中配置一个
@Bean
JettyServletWebServerFactory JettyServletWebServerFactory(ObjectProvider<JettyServerCustomizer> serverCustomizers) {
JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
factory.getServerCustomizers().addAll((Collection)serverCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
Spring容器就可以获取到ServletWebServerFactory,从而启动服务器,将dispatherServlet交给服务器处理请求。
- springbootjar包问题
在看springboot源码时,看到了大量的不属于springboot包的类,例如Undertow等,为何在启动项目时不会报错?因为引入的是已经编译好的class文件,而不是java文件,在启动项目时,只需将项目中的java文件编译即可,对于引入的第三方jar包,通常都是通过 -classpath参数指定jar包在磁盘上的路径。
- springboot依赖问题
springboot在编写时,依赖了大量的其他第三方框架,但是在编译时,将这些第三方框架的依赖排除了,只保留了springboot的源码的class文件,而在编译后,class文件就是一些静态文件了,并不会再被编译一遍。
- 如何判断类路径下有没有某个类?
当springboot加载到某个配置类时,如果这个配置类使用了条件注解,使用try … catch(ClassNotFoundException e);的方式来判断类路径下是否存在这个类,从而觉得该配置类是否生效。
- asm解析class文件
在springboot中,默认是从主启动类所在的包开始扫描,但是大部分文件是没有标注@Component注解的,更多的还是普通的文件,如果是挨个用Class.forName的方式拿到一个class对象,然后再去判断一个class对象是否标注了@Component注解,那么这种方式无疑会造成大量的class被加载到内存中,造成内存资源浪费,所以Spring采取asm技术直接去取编译好的class文件,判断class文件中是否标注了相关注解,再来决定是否加载这个类。这样就不用所有类都加载一遍,节省了内存空间。