SpringBoot8
SpringBoot默認使用Tomcat作爲嵌入式的Servlet容器
那麼問題來了,我們以前做項目,使用的是外部的tomcat,如果想要修改tomcat,知道本地文件夾中的sever.xml就能修改配置,那麼,在使用嵌入式Servlet容器的情況下:
- 如何定製和修改Servlet容器的相關配置;
- SpringBoot能不能支持其他的servlet容器。
1. 定製和修改Servlet容器的相關配置
1.1 在application.properties配置文件中修改
在application.properties配置文件中可以修改與server和tomcat有關的配置
//通用的Servlet容器設置
server.xxx=yyy
server.port=8080
server.context‐path=/crud
//Tomcat的設置
server.tomcat.xxx=yyy
server.tomcat.uri‐encoding=UTF‐8
1.2 在配置類中添加嵌入式的Servlet容器的定製器
在 Spring Boot 1.x 中 ,我們通過 EmbeddedServletContainerCustomizer 接口調優 Tomcat 自定義配置。 在Spring Boot 2.x 中,通過 WebServerFactoryCustomizer 接口定製。
我使用的是SpringBoot2.0以上版本,所以可以編寫一個WebServerFactoryCustomizer:嵌入式的Servlet容器的定製器;來修改Servlet容器的配置 ,在MyMvcConfig中添加如下代碼
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> webServerFactoryCustomizer(){
return new WebServerFactoryCustomizer<TomcatServletWebServerFactory>() {
@Override
public void customize(TomcatServletWebServerFactory factory) {
factory.setPort(8080);
factory.setUriEncoding(Charset.forName("UTF-8"));
}
};
}
2. 註冊servlet三大組件
首先新建一個MyServerConfig配置類,用來放置有關server的配置,將前面1.2節中的WebServerFactoryCustomizer添加進來
-
註冊servlet
創建MyServlet
public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("Hello MyServlet"); } }
添加進MyServerConfig,當訪問
/myServlet
請求時,頁面顯示"Hello MyServlet"@Bean public ServletRegistrationBean myServlet(){ ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet"); return registrationBean; }
-
註冊filter
創建MyFilter
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..."); } @Override public void destroy() { } }
添加進MyServerConfig,攔截到
/hello
和/myServlet
請求時,控制檯打印"MyFilter process…"@Bean public FilterRegistrationBean myFilter(){ FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(new MyFilter()); registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet")); return registrationBean; }
-
註冊listener
創建MyListener
public class MyListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { System.out.println("contextInitialized...web應用啓動"); } @Override public void contextDestroyed(ServletContextEvent sce) { System.out.println("contextDestroyed...當前web項目銷燬"); } }
添加進MyServerConfig,當項目啓動和銷燬時,控制檯顯示不同的輸出。
@Bean public ServletListenerRegistrationBean myListener(){ ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener()); return registrationBean; }
-
SpringBoot幫我們配置SpringMVC的時候,自動註冊SpringMVC的前端控制器:dispatcherServlet。
在DispatcherServletAutoConfiguration中
@Configuration( proxyBeanMethods = false ) @Conditional({DispatcherServletAutoConfiguration.DispatcherServletRegistrationCondition.class}) @ConditionalOnClass({ServletRegistration.class}) @EnableConfigurationProperties({WebMvcProperties.class}) @Import({DispatcherServletAutoConfiguration.DispatcherServletConfiguration.class}) protected static class DispatcherServletRegistrationConfiguration { protected DispatcherServletRegistrationConfiguration() { } @Bean( name = {"dispatcherServletRegistration"} ) @ConditionalOnBean( value = {DispatcherServlet.class}, name = {"dispatcherServlet"} ) public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) { DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath()); registration.setName("dispatcherServlet"); registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); multipartConfig.ifAvailable(registration::setMultipartConfig); return registration; } }
其中
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath());
爲註冊的dispatcherServlet。查看webMvcProperties中的方法,可以得到默認攔截的路徑爲
/
,表明會攔截所有請求;包靜態資源,但是不攔截jsp請求 。如果攔截路徑爲/*
的話,會攔截jsp請求。public class WebMvcProperties { private final WebMvcProperties.Servlet servlet; public WebMvcProperties.Servlet getServlet() { return this.servlet; } ... public static class Servlet { private String path = "/"; private int loadOnStartup = -1; public String getPath() { return this.path; } ... }
3. 切換其他嵌入式Servlet容器
在SpringBoot中,有三種嵌入式Servlet容器,分別爲:tomcat、jetty、undertow。
tomcat爲默認方式;jetty適用於長連接,比如說聊天場景;undertow不支持jsp
要切換其他容器,只需要在pom文件中,把tomcat排除,添加要使用的容器的依賴即可
<!‐‐排除tomcat容器‐‐>
<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>
<!‐‐引入其他的Servlet容器,以jetty爲例‐‐>
<dependency>
<artifactId>spring‐boot‐starter‐jetty</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
4. 嵌入式Servlet容器自動配置原理
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration自動配置嵌入式servlet容器,其詳細代碼如下所示:
@Configuration(
proxyBeanMethods = false
)
//運行在web應用中,條件成立
@ConditionalOnWebApplication
@EnableConfigurationProperties({ServerProperties.class})
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
public EmbeddedWebServerFactoryCustomizerAutoConfiguration() {
}
//配置Netty
@Configuration(
proxyBeanMethods = false
)
//當類HttpServer.class存在時,條件成立。引入依賴,這個類就會存在
@ConditionalOnClass({HttpServer.class})
public static class NettyWebServerFactoryCustomizerConfiguration {
public NettyWebServerFactoryCustomizerConfiguration() {
}
@Bean
public NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
return new NettyWebServerFactoryCustomizer(environment, serverProperties);
}
}
//配置Undertow
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({Undertow.class, SslClientAuthMode.class})
public static class UndertowWebServerFactoryCustomizerConfiguration {
public UndertowWebServerFactoryCustomizerConfiguration() {
}
@Bean
public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
}
}
//配置Jetty
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({Server.class, Loader.class, WebAppContext.class})
public static class JettyWebServerFactoryCustomizerConfiguration {
public JettyWebServerFactoryCustomizerConfiguration() {
}
@Bean
public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
return new JettyWebServerFactoryCustomizer(environment, serverProperties);
}
}
//配置tomcat
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({Tomcat.class, UpgradeProtocol.class})
public static class TomcatWebServerFactoryCustomizerConfiguration {
public TomcatWebServerFactoryCustomizerConfiguration() {
}
@Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
}
}
}
-
ServletWebServerFactory:容器工廠,其代碼如下所示
public interface ServletWebServerFactory { //獲取嵌入式的servlet容器 WebServer getWebServer(ServletContextInitializer... initializers); }
-
嵌入式servlet容器
-
以TomcatServletWebServerFactory爲例
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware { ... public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { Registry.disableRegistry(); } //創建一個tomcat Tomcat tomcat = new Tomcat(); //配置tomcat的基本環境 File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); connector.setThrowOnFailure(true); tomcat.getService().addConnector(connector); this.customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); this.configureEngine(tomcat.getEngine()); Iterator var5 = this.additionalTomcatConnectors.iterator(); while(var5.hasNext()) { Connector additionalConnector = (Connector)var5.next(); tomcat.getService().addConnector(additionalConnector); } this.prepareContext(tomcat.getHost(), initializers); //將配置好的tomcat傳進去 return this.getTomcatWebServer(tomcat); } //返回一個TomcatWebServer protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { return new TomcatWebServer(tomcat, this.getPort() >= 0); } }
//並啓動tomcat服務器
public TomcatWebServer(Tomcat tomcat, boolean autoStart) { this.monitor = new Object(); this.serviceConnectors = new HashMap(); Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; this.initialize(); }
-
我們對嵌入式容器的配置修改是怎麼生效
有兩種方式:
- 在application.properties中修改
- 添加WebServerFactoryCustomizer到配置類
這兩種方式歸根結底,其實都是修改的
ServerProperties
中的值@ConfigurationProperties( prefix = "server", ignoreUnknownFields = true ) public class ServerProperties { private Integer port; private InetAddress address; ... }
從上述代碼可以看出,在application.properties中,以
server
開頭的配置,是對ServerProperties的屬性進行了修改而TomcatServletWebServerFactoryCustomizer中的所有方法也都是圍繞它的唯一屬性ServerProperties來展開的。
public class TomcatServletWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>, Ordered { private final ServerProperties serverProperties; public TomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) { this.serverProperties = serverProperties; } ... }
-
容器配置修改的原理:
容器中導入了 WebServerFactoryCustomizerBeanPostProcessor
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { private ListableBeanFactory beanFactory; private List<WebServerFactoryCustomizer<?>> customizers; public WebServerFactoryCustomizerBeanPostProcessor() { } public void setBeanFactory(BeanFactory beanFactory) { Assert.isInstanceOf(ListableBeanFactory.class, beanFactory, "WebServerCustomizerBeanPostProcessor can only be used with a ListableBeanFactory"); this.beanFactory = (ListableBeanFactory)beanFactory; } public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { //如果當前初始化的是一個WebServerFactory類型的組件 if (bean instanceof WebServerFactory) { this.postProcessBeforeInitialization((WebServerFactory)bean); } return bean; } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } private void postProcessBeforeInitialization(WebServerFactory webServerFactory) { //獲取所有的定製器,調用每一個定製器的customize方法來給Servlet容器進行屬性賦值 ((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> { customizer.customize(webServerFactory); }); } private Collection<WebServerFactoryCustomizer<?>> getCustomizers() { if (this.customizers == null) { this.customizers = new ArrayList(this.getWebServerFactoryCustomizerBeans()); this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE); this.customizers = Collections.unmodifiableList(this.customizers); } return this.customizers; } private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() { return this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values(); } }
定製器會修改ServerProperties
-
嵌入式Servlet容器自動配置的順序
- SpringBoot根據導入的依賴情況,給容器中添加相應的ServletWebServerFactory,如TomcatServletWebServerFactory
- 容器中某個組件要創建對象就會驚動後置處理器WebServerFactoryCustomizerBeanPostProcessor,只要是嵌入式的Servlet容器工廠,後置處理器就工作
- 後置處理器,從容器中獲取所有的WebServerFactoryCustomizer,調用定製器的定製方法
5. 使用外部servlet容器
嵌入式Servlet容器:將應用打成可執行的jar包
- 優點:簡單、便捷
- 缺點:默認不支持JSP、優化定製比較複雜(使用定製器、自己編寫嵌入式Servlet容器的創建工廠 )
外置的Servlet容器:可以將應用以war包的方式打包
步驟:
-
必須創建一個war項目(利用IDEA創建好目錄結構),詳細步驟在後面
-
在pom文件中將嵌入式的Tomcat指定爲provided
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency>
-
必須編寫一個SpringBootServletInitializer的子類,並調用configure方法
-
啓動服務器就可以使用
創建war項目且實現jsp功能的詳細步驟
創建項目以後,打開project settings使用idea自動生成webapp目錄
將web.xml文件放入src\main\webapp\WEB-INF文件夾中
目錄結構如下
添加本地tomcat服務器
把要部署的項目添加進來
上述步驟完成之後,在webapp下創建hello.jsp,啓動tomat服務器,地址欄輸入localhost:8080/hello.jsp就能正常訪問了。jsp功能實現了。
下面實現sprngmvc功能
在webapp\WEB-INF目錄下創建success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<h1>success</h1>
<h3>${msg}</h3>
</body>
</html>
創建controller.HelloController
@Controller
public class HelloController {
@GetMapping("/abc")
public String hello(Model model){
model.addAttribute("msg","你好");
return "success";
}
}
在application.properties文件中配置前綴和後綴
spring.mvc.view.prefix=/WEB-INF/
spring.mvc.view.suffix=.jsp
重新啓動服務器,在地址欄輸入localhost:8080/abc即可訪問success頁面,並且也能獲取到msg的值