SpringBoot8

SpringBoot8

SpringBoot默認使用Tomcat作爲嵌入式的Servlet容器
在這裏插入圖片描述
那麼問題來了,我們以前做項目,使用的是外部的tomcat,如果想要修改tomcat,知道本地文件夾中的sever.xml就能修改配置,那麼,在使用嵌入式Servlet容器的情況下:

  1. 如何定製和修改Servlet容器的相關配置;
  2. 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添加進來

  1. 註冊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;
    }
    
  2. 註冊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;
    }
    
  3. 註冊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;
    }
    
  4. 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);
        }
    }
}
  1. ServletWebServerFactory:容器工廠,其代碼如下所示

    public interface ServletWebServerFactory {
        //獲取嵌入式的servlet容器
        WebServer getWebServer(ServletContextInitializer... initializers);
    }
    

    在這裏插入圖片描述

  2. 嵌入式servlet容器
    在這裏插入圖片描述

  3. 以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();
    }
    
  4. 我們對嵌入式容器的配置修改是怎麼生效

    有兩種方式:

    • 在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;
        }
        ...
    }
    
  5. 容器配置修改的原理:

    容器中導入了 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

  6. 嵌入式Servlet容器自動配置的順序

    • SpringBoot根據導入的依賴情況,給容器中添加相應的ServletWebServerFactory,如TomcatServletWebServerFactory
    • 容器中某個組件要創建對象就會驚動後置處理器WebServerFactoryCustomizerBeanPostProcessor,只要是嵌入式的Servlet容器工廠,後置處理器就工作
    • 後置處理器,從容器中獲取所有的WebServerFactoryCustomizer,調用定製器的定製方法

5. 使用外部servlet容器

嵌入式Servlet容器:將應用打成可執行的jar包

  • 優點:簡單、便捷
  • 缺點:默認不支持JSP、優化定製比較複雜(使用定製器、自己編寫嵌入式Servlet容器的創建工廠 )

外置的Servlet容器:可以將應用以war包的方式打包

步驟:

  1. 必須創建一個war項目(利用IDEA創建好目錄結構),詳細步驟在後面

  2. 在pom文件中將嵌入式的Tomcat指定爲provided

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    
  3. 必須編寫一個SpringBootServletInitializer的子類,並調用configure方法
    在這裏插入圖片描述

  4. 啓動服務器就可以使用

創建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的值

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章