springboot使用的是嵌入式的servlet容器
因为引入了spring-boot-starter-web 启动器 改启动器会自动引入tomcat相关的jar包,所以springboot默认的使用的是tomcat作为servlet容器。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
问题:如何修改和定制servlet容器的相关配置?
如何切换servlet容器呢,如何支持其他容器的?
如何修改和定制servlet容器的相关配置?
springboot1和springboot2实现方式的差别还是蛮大的,但是一些基本的思路和使用方式没有改变,接下来我先采用springboot1系列的版本,以及会谈及一下中间遇到的问题和感悟。
基于springboot的版本:1.5.10.RELEASE
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springboot.servletContainer.version1</groupId>
<artifactId>embedded-container</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>embedded-container</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
在1.5.10.RELEASE版本中修改servlet容器的一些配置可以在应用的主配置文件中application.properties直接修改
server.port=8086
server.context-path=/portal
通过server.xxx 这个server前缀 可以将配置的属性配置到容器上
通过启动日志也可以发现 配置生效
那么为什么通过在配置文件中 配置server.xxxx 前缀的内容可以修改容器的配置呢?
了解下注解@ConfigurationProperties
注入配置文件属性方式一
加在类上,需要和@Component注解,结合使用
@ConfigurationProperties(prefix = "person")
@Component
public class Person {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
注入配置文件属性方式二
通过@Bean的方式进行声明
@Configuration
public class PersonConfig {
@Bean
@ConfigurationProperties(prefix = "person")
public Person person(){
return new Person();
}
}
注入配置文件属性方式三
结合这个注解@EnableConfigurationProperties(Person.class)
@Configuration
@EnableConfigurationProperties(Person.class)
public class PersonConfig {
}
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Car car;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
}
注意:加入下面的依赖 配置属性的时候会生成提示
you can easily generate your own configuration meta-data file from items annotated with @ConfigurationProperties
by using the spring-boot-configuration-processor
jar. The jar includes a Java annotation processor which is invoked as your project is compiled. To use the processor, simply include spring-boot-configuration-processor
as an optional dependency, for example with Maven you would add:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
也就是说 加入这个依赖会为你生成自己的元数据文件spring-configuration-metadata.json
{
"hints": [],
"groups": [
{
"sourceType": "com.atguigu.entity.Person",
"name": "person",
"type": "com.atguigu.entity.Person"
},
{
"sourceType": "com.atguigu.entity.Person",
"name": "person.auth-info",
"sourceMethod": "getAuthInfo()",
"type": "com.atguigu.entity.Person$AuthInfo"
},
{
"sourceType": "com.atguigu.entity.Person",
"name": "person.car",
"sourceMethod": "getCar()",
"type": "com.atguigu.entity.Car"
}
],
"properties": [
{
"sourceType": "com.atguigu.entity.Person",
"name": "person.age",
"type": "java.lang.Integer"
},
{
"sourceType": "com.atguigu.entity.Person$AuthInfo",
"name": "person.auth-info.password",
"type": "java.lang.String"
},
{
"sourceType": "com.atguigu.entity.Person$AuthInfo",
"name": "person.auth-info.username",
"type": "java.lang.String"
},
{
"sourceType": "com.atguigu.entity.Car",
"name": "person.car.car-color",
"type": "java.lang.String"
},
{
"sourceType": "com.atguigu.entity.Car",
"name": "person.car.car-name",
"type": "java.lang.String"
},
{
"sourceType": "com.atguigu.entity.Person",
"name": "person.name",
"type": "java.lang.String"
}
]
}
生成的路径
Configuration meta-data files are located inside jars under META-INF/spring-configuration-metadata.json
Nested properties:嵌套属性
//The annotation processor will automatically consider inner classes as nested properties.
For example, the following class:
@ConfigurationProperties(prefix="server")
public class ServerProperties {
private String name;
private Host host;
// ... getter and setters
private static class Host {
private String ip;
private int port;
// ... getter and setters
}
}
Will produce meta-data information for server.name
, server.host.ip
and server.host.port
properties. You can use the @NestedConfigurationProperty
annotation on a field to indicate that a regular (non-inner) class should be treated as if it were nested.
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
@NestedConfigurationProperty
private Car car;
private AuthInfo authInfo;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
public void setAuthInfo(AuthInfo authInfo) {
this.authInfo = authInfo;
}
public AuthInfo getAuthInfo() {
return authInfo;
}
public static class AuthInfo {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
}
向上面这样也会把Car这个普通常规类当成嵌套属性一样
上面插入了一段题外话,说明的是springboot配置元数据的一些相关知识,接下来步入整体,如何修改内置的servlet容器相关的配置呢?**
通过配置文件的方式修改servlet容器属性
和容器相关的配置均映射到ServerProperties类上,通过@ConfigurationProperties注解
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties
implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
/**
* Server HTTP port.
*/
private Integer port;
/**
* Network address to which the server should bind to.
*/
private InetAddress address;
/**
* Context path of the application.
*/
private String contextPath;
/**
* Display name of the application.
*/
private String displayName = "application";
....
}
通过一个定制类来修改servlet容器的相关配置
public interface EmbeddedServletContainerCustomizer {
/**
* Customize the specified {@link ConfigurableEmbeddedServletContainer}.
* @param container the container to customize
*/
void customize(ConfigurableEmbeddedServletContainer container);
}
在容器中配置一个servlet容器定制器EmbeddedServletContainerCustomizer
@Configuration
public class MvcConfig {
@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
return new EmbeddedServletContainerCustomizer() {
//定制嵌入式servelt容器相关的规则
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(8090);
container.setDisplayName("baidu");
}
};
}
}
总结:
通过配置文件和通过定制器修改servlet容器的表现方式是不一样的,一个是通过修改配置文件,一个是根据java代码方式,但是殊途同归,最终都采用的是定制器的方式,因为修改配置文件的server.xxx属性来修改容器的配置,会自动把server前缀的配置自动绑定到serverProperties类上,而这个类也是定制器的实现类,重写了customize方法,将配置文件的属性,又绑定到ConfigurableEmbeddedServletContainer上
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties
implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
/**
* Server HTTP port.
*/
private Integer port;
/**
* Network address to which the server should bind to.
*/
private InetAddress address;
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (getPort() != null) {
container.setPort(getPort());
}
if (getAddress() != null) {
container.setAddress(getAddress());
}
if (getContextPath() != null) {
container.setContextPath(getContextPath());
}
if (getDisplayName() != null) {
container.setDisplayName(getDisplayName());
}
if (getSession().getTimeout() != null) {
container.setSessionTimeout(getSession().getTimeout());
}
container.setPersistSession(getSession().isPersistent());
container.setSessionStoreDir(getSession().getStoreDir());
if (getSsl() != null) {
container.setSsl(getSsl());
}
if (getJspServlet() != null) {
container.setJspServlet(getJspServlet());
}
if (getCompression() != null) {
container.setCompression(getCompression());
}
container.setServerHeader(getServerHeader());
if (container instanceof TomcatEmbeddedServletContainerFactory) {
getTomcat().customizeTomcat(this,
(TomcatEmbeddedServletContainerFactory) container);
}
if (container instanceof JettyEmbeddedServletContainerFactory) {
getJetty().customizeJetty(this,
(JettyEmbeddedServletContainerFactory) container);
}
if (container instanceof UndertowEmbeddedServletContainerFactory) {
getUndertow().customizeUndertow(this,
(UndertowEmbeddedServletContainerFactory) container);
}
container.addInitializers(new SessionConfiguringInitializer(this.session));
container.addInitializers(new InitParameterConfiguringServletContextInitializer(
getContextParameters()));
}
}
注册servlet filter listener 三大组件
在没有web.xml的情况下如何注册三大组件
注册servlet
如何将如下三个组件注册到这个容器中呢?
public class MyServlet extends HttpServlet {
//处理get请求
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
//处理post请求
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello myServlet");
}
}
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("myfilter process....");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("servletContext initialize");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("servletContext destroyed");
}
}
使用如下的方式注入三大组件
@Configuration
public class MvcConfig {
//注册servlet
@Bean
public ServletRegistrationBean myservlet(){
ServletRegistrationBean servletRegistrationBean=new ServletRegistrationBean(new MyServlet(),"/myServlet");
return servletRegistrationBean;
}
//注册filter
@Bean
public FilterRegistrationBean myfilter(){
FilterRegistrationBean filterRegistrationBean=new FilterRegistrationBean();
filterRegistrationBean.setFilter(new MyFilter());
filterRegistrationBean.setUrlPatterns(Arrays.asList("/myServlet"));
return filterRegistrationBean;
}
//注册listener
@Bean
public ServletListenerRegistrationBean myListener(){
ServletListenerRegistrationBean<MyListener> servletListenerRegistrationBean=new ServletListenerRegistrationBean<>(new MyListener());
return servletListenerRegistrationBean;
}
}
具体为什么用这三个registrationBean就可以实现注册 ,我在下一个专栏为大家讲解。。。
其实我们会发现springboot底层大量注册的方式 使用了如上的方式:DispatcherServletAutoConfiguration
@Configuration
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
private final ServerProperties serverProperties;
private final WebMvcProperties webMvcProperties;
private final MultipartConfigElement multipartConfig;
public DispatcherServletRegistrationConfiguration(
ServerProperties serverProperties, WebMvcProperties webMvcProperties,
ObjectProvider<MultipartConfigElement> multipartConfigProvider) {
this.serverProperties = serverProperties;
this.webMvcProperties = webMvcProperties;
this.multipartConfig = multipartConfigProvider.getIfAvailable();
}
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean(
dispatcherServlet, this.serverProperties.getServletMapping());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(
this.webMvcProperties.getServlet().getLoadOnStartup());
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
}
上面涉及到一点知识 就是注解方式注入bean,采用构造器的形式注入,这里稍微的进行一下讲解
@bean注入的参数问题
@Configuration
public class ProjectTaskConfig {
@Bean
public ProjectTask projectTask(Car car){
return new ProjectTask();
}
}
//注意:如果容器中没有Car类型的bean 那么是无法启动项目的,会报错
//Parameter 0 of method projectTask in com.atguigu.config.ProjectTaskConfig required a bean of type 'com.atguigu.entity.Car' that could not be found.
//Consider defining a bean of type 'com.atguigu.entity.Car' in your configuration.
//也就是说会有强依赖性
###objectFactory
同理下面的方法也是:
@Configuration
public class ProjectTaskConfig {
@Bean
public ProjectTask projectTask(ObjectFactory<Materail> objectFactory){
Materail materail = objectFactory.getObject();
Assert.notNull(materail,"car is not exists");
return new ProjectTask();
}
}
public class BeanAnnotationApplicationTests {
@Autowired
Person p;
@Autowired
Materail materail;
@Autowired
private ObjectFactory<Materail> objectFactory;
以上两种方式均可以注入容器中已经存在的Materail,但是在调用它的getObject方法的时候一定要确保
1.容器中含有该类型的bean
No qualifying bean of type ‘com.atguigu.entity.Materail’ available: expected at least 1 bean which qualifies as autowire candidate
2.容器中只能还有一个该类型的bean可以匹配 ,否则报错
public interface ObjectFactory<T> {
/**
* Return an instance (possibly shared or independent)
* of the object managed by this factory.
* @return an instance of the bean (should never be {@code null})
* @throws BeansException in case of creation errors
*/
T getObject() throws BeansException;
}
###ObjectProvider
从spring4.3后开始引进的新的方式
/**
* A variant of {@link ObjectFactory} designed specifically for injection points,
* allowing for programmatic optionality and lenient not-unique handling.
*
* @author Juergen Hoeller
* @since 4.3
*/
public interface ObjectProvider<T> extends ObjectFactory<T> {
/**
* Return an instance (possibly shared or independent) of the object
* managed by this factory.
* <p>Allows for specifying explicit construction arguments, along the
* lines of {@link BeanFactory#getBean(String, Object...)}.
* @param args arguments to use when creating a corresponding instance
* @return an instance of the bean
* @throws BeansException in case of creation errors
* @see #getObject()
*/
T getObject(Object... args) throws BeansException;
/**
* Return an instance (possibly shared or independent) of the object
* managed by this factory.
* @return an instance of the bean, or {@code null} if not available
* @throws BeansException in case of creation errors
* @see #getObject()
*/
T getIfAvailable() throws BeansException;
/**
* Return an instance (possibly shared or independent) of the object
* managed by this factory.
* @return an instance of the bean, or {@code null} if not available or
* not unique (i.e. multiple candidates found with none marked as primary)
* @throws BeansException in case of creation errors
* @see #getObject()
*/
T getIfUnique() throws BeansException;
}
因为ObjectProvider继承至ObjectFactory,所以会有getObject()方法,调用这个方法的时候,要确保容器中只有一个工厂方法在创建这个对象 什么叫工厂方法 实现factoryBean的类 和@bean标注方法的类 都可以叫做工厂类,调用getObject(),所以也就是在调用对应工厂类中的方法,
public class BeanAnnotationApplicationTests {
@Autowired
Person p;
@Autowired
Materail materail;
@Autowired
private ObjectFactory<Materail> objectFactory;
@Autowired
private ObjectProvider<House> objectProvider;
//用autowired注入 说明容器中只能有一个该类型的bean
House hh = objectProvider.getObject();
//org.springframework.beans.factory.NoUniqueBeanDefinitionException:
// No qualifying bean of type 'com.atguigu.entity.House' available:
// expected single matching bean but found 2: h1,h2
House sy = objectProvider.getObject("沈阳");
House ifAvailable = objectProvider.getIfAvailable();
House ifUnique = objectProvider.getIfUnique();
System.out.println(sy);
System.out.println(ifAvailable);
System.out.println(ifUnique);
我配置House类时
@Bean
@Lazy
public House h1(String msg){
this.msg=msg;
return new House();
}
@Bean
@Lazy
public House h2(){
return new House();
}
所以调用
House hh = objectProvider.getObject();时报错
错误如下:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.atguigu.entity.House’ available: expected single matching bean but found 2: h1,h2
可以将容器中所有该类型的bean注入
@Bean
public House h1(Materail materail){
this.materail=materail;
return new House();
}
@Bean
public House h2(){
return new House();
}
@Autowired
private ObjectProvider<List<House>> objectProvider;
List<House> ifAvailable = objectProvider.getIfAvailable();//全部注入 上面两个
System.out.println(ifAvailable);
同样如果注入的方式是这样
@Autowired
private ObjectProvider> objectProvider;
然后调用
List
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘java.util.List
使用的demo
@Bean
@ConditionalOnMissingBean(MeterRegistry.class)
public CompositeMeterRegistry compositeMeterRegistry(
MetricsProperties config,
ObjectProvider<List<MeterRegistryConfigurer>> configurers,
ObjectProvider<Collection<MetricsExporter>> exportersProvider) {
CompositeMeterRegistry composite =
config.isUseGlobalRegistry() ? Metrics.globalRegistry : new CompositeMeterRegistry();
if (exportersProvider.getIfAvailable() != null) {
exportersProvider.getIfAvailable().forEach(exporter -> {
final MeterRegistry childRegistry = exporter.registry();
if (composite == childRegistry) {
throw new IllegalStateException("cannot add a CompositeMeterRegistry to itself");
}
composite.add(childRegistry);
});
}
if (configurers.getIfAvailable() != null) {
configurers.getIfAvailable().forEach(conf -> conf.configureRegistry(composite));
}
return composite;
}
如何切换servlet容器呢
从前面的讲解 我们知道可以通过servlet容器定制器的方式进行容器的属性设置
public interface EmbeddedServletContainerCustomizer {
/**
* Customize the specified {@link ConfigurableEmbeddedServletContainer}.
* @param container the container to customize
*/
void customize(ConfigurableEmbeddedServletContainer container);
}
查看继承树
切换容器的方式
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
排除tomcat依赖 并且加入jetty依赖
底层servlet容器自动配置的原理是如何的?为什么会自动配置的tomcat
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)//这个注解的作用我们细讲
public class EmbeddedServletContainerAutoConfiguration {
/**
* Nested configuration if Tomcat is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
}
如果这个内部配置类生效就会在当前容器中注入TomcatEmbeddedServletContainerFactory 嵌入式的tomcat容器工厂类 那么这个类要做什么呢?
public interface EmbeddedServletContainerFactory {
/**
* Gets a new fully configured but paused {@link EmbeddedServletContainer} instance.
* Clients should not be able to connect to the returned server until
* {@link EmbeddedServletContainer#start()} is called (which happens when the
* {@link ApplicationContext} has been fully refreshed).
* @param initializers {@link ServletContextInitializer}s that should be applied as
* the container starts
* @return a fully configured and started {@link EmbeddedServletContainer}
* @see EmbeddedServletContainer#stop()
*/
EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers);
}
接下来就是进入主题的时候了
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {
/**
* Nested configuration if Tomcat is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
/**
* Nested configuration if Jetty is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
WebAppContext.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Bean
public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
return new JettyEmbeddedServletContainerFactory();
}
}
/**
* Nested configuration if Undertow is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {
@Bean
public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
return new UndertowEmbeddedServletContainerFactory();
}
}
spring会根据导入的jar包的情况,自动的向容器中添加对应的容器工厂类,例如tomcat的jar包引入后,就会配置
TomcatEmbeddedServletContainerFactory 容器工厂。
TomcatEmbeddedServletContainerFactory实现了EmbeddedServletContainerFactory接口,并重写了接口的getEmbeddedServletContainer()方法获取一个容器
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
//将创建的内置tomcat包装一下
return getTomcatEmbeddedServletContainer(tomcat);
}
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
// 用TomcatEmbeddedServletContainer类包装创建的tomcat对象
return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
}
public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer {
private final Tomcat tomcat;
private final boolean autoStart;
//调用这个方法包装tomcat
public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
//在initialize()方法中启动内置的tomcat
private void initialize() throws EmbeddedServletContainerException {
TomcatEmbeddedServletContainer.logger
.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
try {
// Remove service connectors to that protocol binding doesn't happen
// yet
removeServiceConnectors();
// Start the server to trigger initialization listeners
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
Context context = findContext();
try {
ContextBindings.bindClassLoader(context, getNamingToken(context),
getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
}
catch (Exception ex) {
containerCounter.decrementAndGet();
throw ex;
}
}
catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to start embedded Tomcat", ex);
}
}
}
}
现在剩下的问题就是
1.EmbeddedServletContainerFactory这个工厂接口会在什么时候调用它的获取容器方法来创建一个servlet容器呢?
2.@Import(BeanPostProcessorsRegistrar.class)//这个注解的作用我们细讲 这个注解的作用?
public static class BeanPostProcessorsRegistrar
implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableListableBeanFactory) {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
registerSyntheticBeanIfMissing(registry,
"embeddedServletContainerCustomizerBeanPostProcessor",
EmbeddedServletContainerCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry,
"errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
}
private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry,
String name, Class<?> beanClass) {
if (ObjectUtils.isEmpty(
this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
beanDefinition.setSynthetic(true);
registry.registerBeanDefinition(name, beanDefinition);
}
}
}
BeanPostProcessorsRegistrar负责向容器中注入EmbeddedServletContainerCustomizerBeanPostProcessor后置处理器,在bean初始化前后做一些拦截工作
看一下这个bean后置处理器做了写什么
public class EmbeddedServletContainerCustomizerBeanPostProcessor
implements BeanPostProcessor, BeanFactoryAware {
private ListableBeanFactory beanFactory;
private List<EmbeddedServletContainerCustomizer> customizers;
@Override
public void setBeanFactory(BeanFactory beanFactory) {
Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
"EmbeddedServletContainerCustomizerBeanPostProcessor can only be used "
+ "with a ListableBeanFactory");
this.beanFactory = (ListableBeanFactory) beanFactory;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
//只对ConfigurableEmbeddedServletContainer类型进行拦截
if (bean instanceof ConfigurableEmbeddedServletContainer) {
postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
private void postProcessBeforeInitialization(
ConfigurableEmbeddedServletContainer bean) {
for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
//获取所用的定制器 进行对该servletContainer的定制
customizer.customize(bean);
}
}
private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
if (this.customizers == null) {
// Look up does not include the parent context
this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
this.beanFactory
.getBeansOfType(EmbeddedServletContainerCustomizer.class,
false, false)
.values());
Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
}
补充:这里有一些好的编程方式 借鉴spring的 ,有机会会细细的学一下
Assert:断言的工具类 spring提供的
Collections:java提供的工具类
总结:
1,首先springboot根据我们的导包情况 会在容器中增加TomcatEmbeddedServletContainerFactory 容器工厂类
2.接下来后置处理器只会拦截ConfigurableEmbeddedServletContainer类型的bean的创建
恰好我们的TomcatEmbeddedServletContainerFactory就是这个类型的
容器启动的时候会调用refresh方法刷新容器:
protected void onRefresh() throws BeansException {
// For subclasses: do nothing by default.
}
这个刷新 恰好是留个子类实现的,创建context的时候创建的是
/**
* The class name of application context that will be used by default for non-web
* environments.
*/
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
/**
* The class name of application context that will be used by default for web
* environments.
*/
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
contextClass = Class.forName(this.webEnvironment
? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}
因为是web环境 所以创建的AnnotationConfigEmbeddedWebApplicationContext,它的refresh方法
@Override
protected void onRefresh() {
super.onRefresh();
try {
//创建嵌入式的容器
createEmbeddedServletContainer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start embedded container",
ex);
}
}
private void createEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
ServletContext localServletContext = getServletContext();
if (localContainer == null && localServletContext == null) {
//获取容器工厂
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
//调用容器工厂的getEmbeddedServletContainer方法 把配置的参数设置给tomcat 并启动tomcat
this.embeddedServletContainer = containerFactory
.getEmbeddedServletContainer(getSelfInitializer());
}
else if (localServletContext != null) {
try {
getSelfInitializer().onStartup(localServletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
//只能有一个容器工厂
protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
// Use bean names so that we don't consider the hierarchy
String[] beanNames = getBeanFactory()
.getBeanNamesForType(EmbeddedServletContainerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException(
"Unable to start EmbeddedWebApplicationContext due to missing "
+ "EmbeddedServletContainerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException(
"Unable to start EmbeddedWebApplicationContext due to multiple "
+ "EmbeddedServletContainerFactory beans : "
+ StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0],
EmbeddedServletContainerFactory.class);
}
以上是自动配置的servlet容器的过程 是springboot1.5.10的版本 ,有时间会探究下2.0系列的版本