一、定制和修改Servlet容器的相关配置
1、修改和server有关的配置
ServerProperties;
server.port=8081
server.context-path=/crud
server.tomcat.uri-encoding=UTF-8
//通用的Servlet容器设置
server.xxx
//Tomcat的设置
server.tomcat.xxx
2、编写一个EmbeddedServletContainerCustomizer的Servlet容器的定制器,并将这个定制器加入到容器中,来修改Servlet容器的配置
@Configuration
public class MyServletContainerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
factory.setPort(8082);
}
@Bean
public WebServerFactoryCustomizer webServerFactoryCustomizer(){
return new MyServletContainerFactoryCustomizer();
}
注意:当两者同时存在时,自定义定制器的优先级高于配置文件,而且会与配置文件形成互补配置。例如:1+2两个步骤最后的记过就是
localhost:8082/crud
二、Servlet三大组件
注册三大组件:ServletRegistrationBean,FilterRegistrationBean,ServletListenerRegistrationBean
1、Servlet
(1)自定义servlet
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello");
}
}
(2)通过ServletRegistrationBean注入容器
@Bean
public ServletRegistrationBean servletRegistrationBean() {
return new ServletRegistrationBean(new MyServlet(), "/520");
}
2、Filter
(1)自定义filter
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("拦截了.....");
filterChain.doFilter(servletRequest,servletResponse);
}
(2)通过FilterRegistrationBean注入容器
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean<MyFilter> myFilterFilterRegistrationBean = new FilterRegistrationBean<>();
myFilterFilterRegistrationBean.setFilter(new MyFilter());
myFilterFilterRegistrationBean.addUrlPatterns("/666");
return myFilterFilterRegistrationBean;
}
3、listener
(1)自定义listener
public class MyListenner implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("毁灭了");
}
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("产生了");
}
}
(2)通过ServletListenerRegistrationBean注入容器
@Bean
public ServletListenerRegistrationBean registrationBean() {
ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean();
servletListenerRegistrationBean.setListener(new MyListenner());
return servletListenerRegistrationBean;
}
三、使用外置的Servlet容器
1、嵌入式和外置Servlet比较
(1)、嵌入式Servlet容器:应用打成可执行的jar。简单,不用配置就可以直接使用。
缺点:
默认不支持JSP、优化定制比较复杂(使用定制器【ServerProperties、自定义EmbeddedServletContainerCustomizer】,自己编写嵌入式Servlet容器的创建工厂【EmbeddedServletContainerFactory】);
(2、)外置的Servlet容器:外面安装Tomcat—应用war包的方式打包,可以使用jsp
步骤:
(a)、必须创建一个war项目
(b)、将嵌入式的Tomcat指定为provided
即只在编译期使用嵌入式tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
(c)、编写一个SpringBootServletInitializer的子类,并调用configure方法(必须)
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//传入SpringBoot应用的主程序
return application.sources(SpringBoot04WebJspApplication.class);
}
}
2、原理
jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器;
war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer
】,启动ioc容器;
(1)根据servlet3.0文档:
(a)、服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例:
(b)、ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名
(c)、还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;
(2)启动流程
(a)启动Tomcat
(b)寻找META-INF\services中的ServletContainerInitializer
,创建当前web应用里面每一个jar包里面ServletContainerInitializer
实例
(c)由于使用了@HandlesTypes({WebApplicationInitializer.class})
注解,所以将会同时将WebApplicationInitializer.class
传入onstartup方法来创建WebApplicationInitializer
的实例。在这个方法的最后,会循环遍历WebApplicationInitializer
实例,并调用其initializer.onStartup(servletContext);
方法。
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
传入WebApplicationInitializer
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = Collections.emptyList();
Iterator var4;
if (webAppInitializerClasses != null) {
initializers = new ArrayList(webAppInitializerClasses.size());
var4 = webAppInitializerClasses.iterator();
while(var4.hasNext()) {
Class<?> waiClass = (Class)var4.next();
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
((List)initializers).add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
} catch (Throwable var7) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
}
}
}
}
if (((List)initializers).isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
} else {
servletContext.log(((List)initializers).size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort((List)initializers);
var4 = ((List)initializers).iterator();
while(var4.hasNext()) {
WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
initializer.onStartup(servletContext);
}
}
}
(d)WebApplicationInitializer
是一个接口,调用其方法其实就是调用实现类的方法。
(e)即SpringBootServletInitializer
的类会被创建对象,并执行重写后的onStartup方法。而WebApplicationContext rootApplicationContext = this.createRootApplicationContext(servletContext);
就创建了Servlet容器。
public void onStartup(ServletContext servletContext) throws ServletException {
this.logger = LogFactory.getLog(this.getClass());
WebApplicationContext rootApplicationContext = this.createRootApplicationContext(servletContext);
if (rootApplicationContext != null) {
servletContext.addListener(new SpringBootServletInitializer.SpringBootContextLoaderListener(rootApplicationContext, servletContext));
} else {
this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
}
}
(f)createRootApplicationContext
中首先创建了SpringApplicationBuilder
构建器,一直执行到builder = configure(builder);
时,由于SpringBootServletInitializer
子类重写了configure方法,此处会调用重写后的方法, 并返回application.sources(SpringBoot04WebJspApplication.class);
将SpringBoot的主程序类传入了进来,并通过SpringApplication application = builder.build();
创建了SpringApplication 主程序。最后进入run方法创建IOC容器。【ioc创建流程详见:https://blog.youkuaiyun.com/TMHJHZTMGB/article/details/110262738】
protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
//1、创建SpringApplicationBuilder
SpringApplicationBuilder builder = createSpringApplicationBuilder();
StandardServletEnvironment environment = new StandardServletEnvironment();
environment.initPropertySources(servletContext, null);
builder.environment(environment);
builder.main(getClass());
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));
builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
//调用configure方法,子类重写了这个方法,
builder = configure(builder);
//使用builder创建一个Spring应用
SpringApplication application = builder.build();
if (application.getSources().isEmpty() && AnnotationUtils
.findAnnotation(getClass(), Configuration.class) != null) {
application.getSources().add(getClass());
}
Assert.state(!application.getSources().isEmpty(),
"No SpringApplication sources have been defined. Either override the "
+ "configure method or add an @Configuration annotation");
// Ensure error pages are registered
if (this.registerErrorPageFilter) {
application.getSources().add(ErrorPageFilterConfiguration.class);
}
//启动Spring应用
return run(application);
}