(6) Error handling mechanism
1. SpringBoot’s default error handling mechanism
1) The browser returns to the default error screen with some display information
The header from which the browser sends the request
2) Using Postman to request access, will return json array
Postman Specifies the request header that sends the request
How the default processing mechanism works:
ErrorMvcAutoConfiguration configured in the Mvc the wrong way, is the information of error handling in the Mvc SpringBoot automatic configuration, through to the IoC container to add components to complete the configuration.
Automatic configuration adds the following components to the container:
① TerrorAttributes: Error message handling by default
② BasicErrorController: Handles default /error requests
③ ErrorPageCustomizer: Indicates the error page
Terrorviewresolver: Set a default error view
Specific functions of these components:
① TerrorAttributes: Used for error message handling
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes implements ErrorAttributes.HandlerExceptionResolver.Ordered {...@Override
@Deprecated
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
// Store the timestamp to
errorAttributes.put("timestamp".new Date());
addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
addPath(errorAttributes, webRequest);
return errorAttributes;
}
// Retrieve the error status code, error message, etc
private void addStatus(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
Integer status = getAttribute(requestAttributes, RequestDispatcher.ERROR_STATUS_CODE);
if (status == null) {
errorAttributes.put("status".999);
errorAttributes.put("error"."None");
return;
}
errorAttributes.put("status", status);
try {
errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
}
catch (Exception ex) {
// Unable to obtain a reason
errorAttributes.put("error"."Http Status "+ status); }}// Displays detailed error information, including exceptions, obtaining exception paths, and obtaining exception information
private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest,
boolean includeStackTrace) {
Throwable error = getError(webRequest);
if(error ! =null) {
while (error instanceofServletException && error.getCause() ! =null) {
error = error.getCause();
}
errorAttributes.put("exception", error.getClass().getName());
if(includeStackTrace) { addStackTrace(errorAttributes, error); } } addErrorMessage(errorAttributes, webRequest, error); }... }Copy the code
② BasicErrorController: Handles default /error requests
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {...// Generate htML-type data; Requests sent by the browser come to this method for processing
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections
.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
// Specify the error page and include the address and content of the error page
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return(modelAndView ! =null)? modelAndView :new ModelAndView("error", model);
}
// Generates JSON data, and requests from other clients come to this method for processing
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
return newResponseEntity<>(body, status); }... }Copy the code
The ModelAndView method calls the resolveErrorView method for the page response.
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map
model)
,> {
// Iterate through all ErrorViewResolver to get ModelAndView
for (ErrorViewResolver resolver : this.errorViewResolvers) {
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if(modelAndView ! =null) {
returnmodelAndView; }}return null;
}
Copy the code
The specific page pointing is resolved by the DefaulViewResolver class, which is explained below.
③ ErrorPageCustomizer: Indicates that the configuration error page is redirected
private final DispatcherServletPath dispatcherServletPath;
protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {
this.properties = properties;
this.dispatcherServletPath = dispatcherServletPath;
}
Copy the code
The ErrorPageCustomizer class assigns a value to dispatcherServletPath in the class associated with registering error messages
@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage(
this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
errorPageRegistry.addErrorPages(errorPage);
}
Copy the code
The resulting request path is /error
@Value("${error.path:/error}")
private String path = "/error";// The system sends an error request after an error occurs, which is equivalent to the error rule registered in web.xml
Copy the code
Terrorviewresolver: Configure default error view display
Return in ErrorMvcAutoConfiguration DefaultErrorViewResolver
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver(a) {
return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
}
Copy the code
Page information that defines the response in the DefaulViewResolver class
public class DefaultErrorViewResolver implements ErrorViewResolver.Ordered {
private static final Map<Series, String> SERIES_VIEWS;
// Define the response code
static {
Map<Series, String> views = new EnumMap<>(Series.class);
views.put(Series.CLIENT_ERROR, "4xx");
views.put(Series.SERVER_ERROR, "5xx"); SERIES_VIEWS = Collections.unmodifiableMap(views); }...@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
// SpringBoot finds a page by default? Error/pages named with status codes (e.g. Error /404)
String errorViewName = "error/" + viewName;
// If the template engine can parse the page address, use the template engine to parse it
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
if(provider ! =null) {
// Return the view address specified by errorViewName if the template engine is available
return new ModelAndView(errorViewName, model);
}
// If the template engine is not available, look for the page corresponding to errorViewName in the static resources folder. Such as: the error / 404 HTML
returnresolveResource(errorViewName, model); }}Copy the code
An error message after ErrorMvcAutoConfiguration processing steps:
- When a 4XX or 5XX error occurs in the system;
ErrorPageCustomizer
The effect (bean used to customize the wrong response rule) is set to send/error
Requests;/error
The request will beBasicErrorController
Process and respond to HTML pages or JSON data;- Response to HTML page or JSON data by
DefaultErrorViewResolver
Return after configuration.
2. Customize the error response
2.1 How can I Customize the Error Page
1) With a template engine
Error/status code: The error page is named as the error status code. HTML is stored in the error folder in the template engine folder. When an error occurs, the corresponding page is displayed.
We can also use 4xx and 5xx as the file name of the error page to match all errors of this type, precisely locate the first (that is, find the page corresponding to the status code first, and use this page only when it cannot be found).
Information available on the page:
The retrieved page information is placed in the Shared Page Information Defaulattributes class
- Timestamp: indicates the timestamp
- Status: indicates the status code
- Error: indicates an error message
- Exception: exception object
- Message: JSR303 data validation errors are here
2) Without a template engine
If the error page is not found in the template engine, look for it in the static resources folder
3) Error pages with none of the above
The default SpringBoot error page is used
2.2 How can I Customize Incorrect Data
1) Custom exception handling & return custom JSON data
// There is no adaptive effect...
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(UserNotExistException.class)
public Map<String,Object> handleException(Exception e){
Map<String,Object> map = new HashMap<>();
map.put("code"."user.notexist");
map.put("message",e.getMessage());
returnmap; }}Copy the code
2) Forward to /error for adaptive response effect processing
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(UserNotExistException.class)
public String handleException(Exception e, HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
// Pass the custom error status code 4xx 5xx
request.setAttribute("javax.servlet.error.status_code".500);
map.put("code"."user.notexist");
map.put("message"."User error");
request.setAttribute("ext", map);
// Forward to the /error page, where the browser and other clients respond differently, handled by the BasicErrorController
return "forward:/error"; }}Copy the code
2.3 Pass on customized data
Spring Boot handling procedures when exceptions occur:
- When a page exception occurs, a /error request is sent;
- The sent /error request will be sent
BasicErrorController
Class to handle; - Response out can obtain the data by
getErrorAttributes
Access to (ErrorController
The implementation of the classAbstractErrorController
In)
So the way we pass data:
Write an implementation class for ErrorController (or subclass AbstractErrorController) and inject it into the container.
The data that is available on the page, or the data that is returned from json is retrieved using terrorAttributes’ getErrorAttributes method. Terrorattributes in the container does data processing by default. Therefore, we can customize ErrorAttributes to achieve error message transmission.
Code examples:
// Inject our custom ErrorAttributes into the container
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
// The map of the return value is the field that the page and JSON can get
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> map = super.getErrorAttributes(webRequest, options);
map.put("author"."Bruce");
// We exception the data carried by the handler
Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", RequestAttributes.SCOPE_REQUEST);
map.put("ext", ext);
returnmap; }}Copy the code
End result: The response is adaptive, and you can customize the ErrorAttributes to change what needs to be returned
(7) Servlet container related
SpringBoot uses an embedded servlet container by default (with Tomcat embedded as the servlet container)
1. Embedded Servlet container
1.1 Modify and customize the configuration of the embedded Servlet container
(1) Modify the server configuration through the configuration file (specified in the ServerProperties class)
# server. XXX is a server-specific configuration
server.port=9083
server.servlet.context-path=/servlet
# server.tomcat. XXX is a Tomcat related configuration
server.tomcat.uri-encoding=utf-8
Copy the code
(2) by implementing WebServerFactoryCustomizer interface, modified WebServerFactoryCustomizer is embedded servlet container custom, can be used to modify the servlet container configuration
@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
@Override
public void customize(ConfigurableWebServerFactory factory) {
factory.setPort(9000); }}Copy the code
(3) through ConfigurableServletWebServerFactory subclasses of custom configuration
@Configuration
public class MyServerConfig {
/** * You can also create examples of Tomcat customizers for more configuration *@return* /
@Bean
public ConfigurableServletWebServerFactory webServerFactory(a) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.setPort(9090);
returnfactory; }}Copy the code
It is found that the Spring Boot configuration execution priority @Component > application.properties > @bean is not clear whether it is accurate or not. Further research on the principle of automatic configuration is needed to determine.
Configuration of Embedded Servlet containers in SpringBoot: Embedded Servlet Container Support
1.2 Register three Servlet components [Servlet, Filter, Listener]
By default, SpringBoot starts the embedded Servlet container as a JAR package to start SpringBoot’s Web reference, since there is no web.xml
File, so the registration three components are configured using @beans.
① Register the Servlet component and use the ServletRegistrationBean
A custom Servlet
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("this is my servlet"); }}Copy the code
A configuration Bean that writes a ServletRegistrationBean type in the configuration class registers the Servlet component
@Bean
public ServletRegistrationBean myServlet(a) {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new MyServlet(), "/myServlet");
servletRegistrationBean.setLoadOnStartup(1);
return servletRegistrationBean;
}
Copy the code
② Register the Filter component
A custom filter
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 is running ......");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy(a) {}}Copy the code
The configuration Bean that writes type FilterRegistrationBean in the configuration class registers the Filter component
@Bean
public FilterRegistrationBean myFilter(a) {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new MyFilter());
filterRegistrationBean.setUrlPatterns(Arrays.asList("/test"."/myServlet"));
return filterRegistrationBean;
}
Copy the code
③ Register the Listener component
Custom Listener
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ContextInitialized: The Web application is started...");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ContextDestroyed Web application destruction..."); }}Copy the code
Writing ServletListenerRegistrationBean configuration types in the configuration class registered Bean Listener component
@Bean
public ServletListenerRegistrationBean myListener(a) {
ServletListenerRegistrationBean<MyListener> servletListenerRegistrationBean = new ServletListenerRegistrationBean<>(new MyListener());
return servletListenerRegistrationBean;
}
Copy the code
SpringBoot will automatically register SpringMVC front-end controller DispatcherServlet when SpringMVC is automatically configured. Through DispatcherServletAutoConfiguration class loading DispatcherServlet
@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider
multipartConfig)
{
// Intercepts by default: /, that is, all requests, including static resources, but not JSP requests. /* Intercepts JSPS
// You can also change the default SpringMVC front-end controller intercept request path by setting spring.vcv.servlet. path in the configuration file
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
returnregistration; }}Copy the code
1.3 Using other embedded Servlet containers
By default, SpringBoot uses Tomcat as the built-in Servlet container. We can also switch to other Servlet containers such as Jetty (suitable for long connections), Undertow (does not support JSP).
Servlet container diagram supported by Spring Boot by default
Built-in Servlet containers supported by default in SpringBoot:
① Tomcat container (default)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Copy the code
② Use Jetty as a Servlet 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>
<! Use Jetty as a Servlet container -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
Copy the code
③ Use Undertow as the Servlet 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>
<! -- Using undertow as Servlet container -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
Copy the code
Tomcat Jetty Undertow Comparison of three Servlet containers: Undertow,Tomcat and Jetty server configuration details and performance tests
1.4 Principles of automatic configuration of embedded Servlet containers
EmbeddedWebServerFactoryCustomizerAutoConfiguration SpringBoot embedded servlet container automatically configured in the class, has displayed in the automatic configuration SpringBoot support servlet container: Tomcat, Jetty, Undertow, Netty, Tomcat is used by default. Determine which Servlet container to use based on the Starter that the POM file imports.
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
/** * Nested configuration if Tomcat is being used. */
@Configuration(proxyBeanMethods = false)
// Check whether Tomcat dependencies are imported
@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
public static class TomcatWebServerFactoryCustomizerConfiguration {
@Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
return newTomcatWebServerFactoryCustomizer(environment, serverProperties); }}/** * Nested configuration if Jetty is being used. */
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Server.class, Loader.class, WebAppContext.class })
public static class JettyWebServerFactoryCustomizerConfiguration {
@Bean
public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
return newJettyWebServerFactoryCustomizer(environment, serverProperties); }}/** * Nested configuration if Undertow is being used. */
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Undertow.class, SslClientAuthMode.class })
public static class UndertowWebServerFactoryCustomizerConfiguration {
@Bean
public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
return newUndertowWebServerFactoryCustomizer(environment, serverProperties); }}/** * Nested configuration if Netty is being used. */
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HttpServer.class)
public static class NettyWebServerFactoryCustomizerConfiguration {
@Bean
public NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
return newNettyWebServerFactoryCustomizer(environment, serverProperties); }}}Copy the code
Servlet container auto-configuration principle:
1) ServletWebServerFactory is a Web server customizer (Servlet customization Factory) : Create Servlet containers and create corresponding Servlet containers according to different categories
@FunctionalInterface
public interface ServletWebServerFactory {
/**
* Gets a new fully configured but paused {@link WebServer} instance. Clients should
* not be able to connect to the returned server until {@link WebServer#start()} is
* called (which happens when the {@code ApplicationContext} has been fully
* refreshed).
* @param initializers {@link ServletContextInitializer}s that should be applied as
* the server starts
* @return a fully configured and started {@link WebServer}
* @see WebServer#stop()
*/
WebServer getWebServer(ServletContextInitializer... initializers);
}
Copy the code
Inheritance diagram
2) WebServer is the parent of the Servlet container class
public interface WebServer {
/**
* Starts the web server. Calling this method on an already started server has no
* effect.
* @throws WebServerException if the server cannot be started
*/
void start(a) throws WebServerException;
/**
* Stops the web server. Calling this method on an already stopped server has no
* effect.
* @throws WebServerException if the server cannot be stopped
*/
void stop(a) throws WebServerException;
/**
* Return the port this server is listening on.
* @return the port (or -1 if none)
*/
int getPort(a);
/**
* Initiates a graceful shutdown of the web server. Handling of new requests is
* prevented and the given {@code callback} is invoked at the end of the attempt. The
* attempt can be explicitly ended by invoking {@link #stop}. The default
* implementation invokes the callback immediately with
* {@link GracefulShutdownResult#IMMEDIATE}, i.e. no attempt is made at a graceful
* shutdown.
* @param callback the callback to invoke when the graceful shutdown completes
* @since2.3.0 * /
default void shutDownGracefully(GracefulShutdownCallback callback) { callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE); }}Copy the code
Inheritance diagram
In order toTomcatServletWebServerFactory
The automatic configuration of the Servlet container is described as follows:
1) create a Tomcat container factory TomcatServletWebServerFactory
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
implements ConfigurableTomcatWebServerFactory.ResourceLoaderAware {...@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
// Create a Tomcat
Tomcat tomcat = new Tomcat();
// Configure basic Tomcat content
File baseDir = (this.baseDirectory ! =null)?this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
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);
// Pass the configured Tomcat into the getTomcatWebServer method, return the Tomcat container, and start the container
returngetTomcatWebServer(tomcat); }...// Pass configured Tomcat to TomcatWebServer, create Tomcat container and return
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown()); }... }Copy the code
② The constructor of TomcatWebServer is used to initialize Tomcat container
public class TomcatWebServer implements WebServer {.../**
* Create a new {@link TomcatWebServer} instance.
* @param tomcat the underlying Tomcat server
* @param autoStart if the server should be started
* @param shutdown type of shutdown supported by the server
* @since2.3.0 * /
// Initialize the Tomcat container through the constructor
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
initialize();
}
// Initialize the Tomcat container
private void initialize(a) throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.removeServiceConnectors(); }});// Start the server to trigger initialization listeners
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), 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) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex); }}}... }Copy the code
3) Modify the configuration of embedded Servlet containers
① Modify the configuration file, that is, configure the configuration information in ServerProperties. Configure the Servlet container by reading the configuration information
(2) by implementing WebServerFactoryCustomizer interface, customize the Servlet container
How to modify configuration principles for the Servlet container
4) Spring ServletWebServerFactoryAutoConfiguration class for automatic configuration in the Boot
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
// Import the backend handler to import the configuration related to the embedded Servlet container and the configuration classes related to the Servlet container
public class ServletWebServerFactoryAutoConfiguration {
@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
return new ServletWebServerFactoryCustomizer(serverProperties);
}
@Bean
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer( ServerProperties serverProperties) {
return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}
@Bean
@ConditionalOnMissingFilterBean(ForwardedHeaderFilter.class)
@ConditionalOnProperty(value = "server.forward-headers-strategy", havingValue = "framework")
public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter(a) {
ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
FilterRegistrationBean<ForwardedHeaderFilter> registration = new FilterRegistrationBean<>(filter);
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR);
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}
/**
* Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via
* {@link ImportBeanDefinitionRegistrar} for early registration.
*/
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; }}/ / registered registerBeanDefinitions, rear call WebServerFactoryCustomizerBeanPostProcessor processor
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
WebServerFactoryCustomizerBeanPostProcessor.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); }}}}Copy the code
In WebServerFactoryCustomizerBeanPostProcessor web service factory custom components post processor, responsible for the bean to perform initialization work before initialization. Obtained from the IoC container type for WebServerFactoryCustomizer types of components.
Post-handler: before and after bean initialization (after object creation, no assignment, initialization)
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor.BeanFactoryAware {
private ListableBeanFactory beanFactory;
privateList<WebServerFactoryCustomizer<? >> customizers;@Override
public void setBeanFactory(BeanFactory beanFactory) {
Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
"WebServerCustomizerBeanPostProcessor can only be used with a ListableBeanFactory");
this.beanFactory = (ListableBeanFactory) beanFactory;
}
// Before initializing the Bean
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// If a component of type WebServerFactory is currently initialized
if (bean instanceof WebServerFactory) {
postProcessBeforeInitialization((WebServerFactory) bean);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
// Get all the customizers and call each one's customize method to assign attributes to the Servlet container;
@SuppressWarnings("unchecked")
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
.invoke((customizer) -> customizer.customize(webServerFactory));
}
//
privateCollection<WebServerFactoryCustomizer<? >> getCustomizers() {if (this.customizers == null) {
// Look up does not include the parent context
this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
// Get the components of the custom Servlet container
@SuppressWarnings({ "unchecked", "rawtypes" })
privateCollection<WebServerFactoryCustomizer<? >> getWebServerFactoryCustomizerBeans() {return (Collection) this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false.false).values(); }}Copy the code
For example, is obtained by post processor for TomcatServletWebServerFactoryCustomizer calls to customize () custom method, obtain the Servlet container related configuration class serverProperties for automatic configuration
public class TomcatServletWebServerFactoryCustomizer
implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>, Ordered {
private final ServerProperties serverProperties;
public TomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
this.serverProperties = serverProperties;
}
@Override
public int getOrder(a) {
return 0;
}
// Customize the Servlet container with this method
@Override
public void customize(TomcatServletWebServerFactory factory) {
// Get the configuration information in the configuration class serverProperties
ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
if(! ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) { factory.getTldSkipPatterns().addAll(tomcatProperties.getAdditionalTldSkipPatterns()); }if(tomcatProperties.getRedirectContextRoot() ! =null) { customizeRedirectContextRoot(factory, tomcatProperties.getRedirectContextRoot()); } customizeUseRelativeRedirects(factory, tomcatProperties.isUseRelativeRedirects()); factory.setDisableMBeanRegistry(! tomcatProperties.getMbeanregistry().isEnabled()); }private void customizeRedirectContextRoot(ConfigurableTomcatWebServerFactory factory, boolean redirectContextRoot) {
factory.addContextCustomizers((context) -> context.setMapperContextRootRedirectEnabled(redirectContextRoot));
}
private void customizeUseRelativeRedirects(ConfigurableTomcatWebServerFactory factory,
boolean useRelativeRedirects) { factory.addContextCustomizers((context) -> context.setUseRelativeRedirects(useRelativeRedirects)); }}Copy the code
Summary of embedded Servlet container automatic configuration principles:
1) SpringBoot, according to the dependence on import situation automatically add the corresponding to the container WebServerFactoryCustomizer components (web service customization)
A component to create objects (2) when the container will trigger WebServerFactoryCustomizerBeanPostProcessor post processor
(3) WebServerFactoryCustomizerBeanPostProcessor custom components (Web factory rear processor) to get all the services the factory custom components (i.e. WebServerFactoryCustomizer interface, Custom custom components) call the Customize custom interface and customize the Servlet container configuration;
④ The embedded Servlet container factory creates the Tomcat container, initializes and starts the Servlet container
1.5 Startup principle of the embedded Servlet Container
When the embedded Servlet container factory is created and when to get the embedded Servlet container and start Tomcat
Get the embedded Servlet container factory:
1) Start the Spring Boot application through the main method entrance and run the run method;
2) Call the run method of the Application class, and call the refreshContext method to refresh the container. If it is a web application is created AnnotationConfigServletWebServerApplicationContext container, or create the default IoC container AnnotationConfigApplicationContext container
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// Create IoC containers. Create different IoC containers for different environments
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// Refresh the container, that is, create container objects and initialize them to create individual component objects in the container
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
Copy the code
There is much more to the IoC container in Spring, which will be covered in more detail in a later blog post.
3) call the refresh method to refresh the container, after a series of calls to AbstractApplicationContext class the refresh method.
@Override
public void refresh(a) throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
Refresh the IoC container here
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...resetCommonCaches(); }}}Copy the code
4) AbstractApplicationContext onRefresh method in the class of refresh the IoC container, including web IoC container rewrite the onRefresh method, called polymorphic calls through the parent class.
5) call ServletWebServerApplication createWebServer method in the class, let the web IoC create embedded Servlet container
private void createWebServer(a) {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// Get the embedded Servlet factory
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
getBeanFactory().registerSingleton("webServerGracefulShutdown".new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop".new WebServerStartStopLifecycle(this.this.webServer));
}
else if(servletContext ! =null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
Copy the code
6) call ServletWebServerApplication class getWebServerFactory create factory method to create the Servlet
protected ServletWebServerFactory getWebServerFactory(a) {
// Use bean names so that we don't consider the hierarchy
// Get the ServletWebServerFactory component from the IoC container via the getBean method
String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
+ "ServletWebServerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
Copy the code
GetWebServerFactory method through getBeanFactory (.) getBeanNamesForType (ServletWebServerFactory. Class); Get the ServletWebServerFactory component from the IoC container. We can see through the debug debugging for tomcatServletWebServerFactory components in the IoC container. We’re SpringBoot automatic configuration class: ServletWebServerFactoryAutoConfiguration class, injected tomcatServletWebServerFactory components
After we get tomcatServletWebServerFactory components, Enter the tomcatServletWebServerFactory class to create the Servlet container object will trigger the post processor WebServerFactoryCustomizerBeanPostProcessor at this time, The post-processor gets all the customizers to customize the configuration of the Servlet container class.
7) through the container factory for embedded Servlet container, namely through tomcatServletWebServerFactory create tomcatWebServer class;
8) Create an embedded Servlet container and start the container. The Tomcat WebServer class contains the configuration and startup methods for the Tomcat container.
The embedded Servlet container is first started and then the other components in the IoC container are created, that is, the Servlet container is created when the IoC container is started.
The IoC container is not covered in detail in this chapter and will be covered in a future blog post.
2. External Servlet containers
- Embedded Servlet container: The application is packaged as an executable JAR package, and the application is launched by executing the JAR package.
- Advantages: Easy to start, easy to switch between different embedded Servlet containers, and no external Servlet container installation
- Disadvantages: No JSP support, more complex optimization customization, configuration through the configuration file or custom Servlet to create factories
WebServerFactoryCustomizer
- External Servlet container: Package the application into a WAR package. Use the external Servlet container, for example, run the war package using Tomcat installed and configured
2.1 Usage of the external Servlet container
① Set the package mode of the project to WAR package.
② Set the project not to use the built-in Servlet container;
(3) you must write SpringBootServletInitializer implementation class, and implement the configure method;
④ Start the WAR package through the external Servlet container.
Specific operation:
① Set the package mode of the project to WAR package. (Note the directory structure used to create webApp)
Specify, or modify the project’s POM.xml file when using the SpringBoot project creation tool
<packaging>war</packaging>
Copy the code
② Set the project not to use the built-in Servlet container;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
Copy the code
(3) you must write SpringBootServletInitializer implementation class, and implement the configure method;
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
// Call the configure method, passing in the SpringBoot application main program
returnapplication.sources(Springboot04JspApplication.class); }}Copy the code
④ Start the WAR package through the external Servlet container.
For details on how to configure the Tomcat server in IDEA, see the blog post: Change the SpringBoot project to external Tomcat startup
2.2 Startup principle of the external Servlet container
Execution steps for different packaging methods:
- Jar package: Execute main method of SpringBoot main class, create and start IoC container, create embedded Servlet container;
- War file: start the server, application server startup SpringBoot SpringBootServletInitializer 】 【, create and start the IoC container;
The Servlet 3.1Shared libraries / runtimes pluggability
Specification:
- Each jar in the current Web application is created when the server starts (when the Web application starts)
ServletContainerInitializer
Instance; ServletContainerInitializer
The implementation class must be placed in the JAR packageMETA - INF/services
The folder contains a file namedjavax.servlet.ServletContainerInitializer
File, the content isServletContainerInitializer
The full class name of the implementation class of- You can also use
@HandlesTypes
Annotations to load the classes we’re interested in when we start the application;
Process:
① Start the Tomcat server.
(2) org \ springframework \ spring – web \ 5.2.10 RELEASE \ spring – web – 5.2.10. The jar! \ meta-inf \ services \ javax.mail servlet. ServletContainerInitializer Spring web module contains the file, the contents of the file: Org. Springframework. Web. SpringServletContainerInitializer, i.e., application startup need to create this class;
(3) SpringServletContainerInitializer will @ HandlesTypes (WebApplicationInitializer. Class) all WebApplicationInitializer annotations Types of classes are passed to the onStartup method, for these WebApplicationInitializer type of class instance is created;
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@NullableSet<Class<? >> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if(webAppInitializerClasses ! =null) {
for(Class<? > waiClass : webAppInitializerClasses) {// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if(! waiClass.isInterface() && ! Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {try {
/ / for these WebApplicationInitializer type of class instance is created
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); }}}}if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
/ / every WebApplicationInitializer type of class call their ` onStartup ` method;initializer.onStartup(servletContext); }}}Copy the code
(4) each WebApplicationInitializer type of class call their onStartup method;
WebApplicationInitializer
An inheritance diagram of an interface
(5) is equivalent to our SpringBootServletInitializer implementation class object is created, and perform the onStartup method;
6 SpringBootServletInitializer instance execution onStartup method called when createRootApplicationContext method to create the IoC container;
All landowners createRootApplicationContext method invokes the run method start SpringBoot application and create the IoC container
SpringBootServletInitializer
Source:
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {...// Execute the onStartup method
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Logger initialization is deferred in case an ordered
// LogServletContextInitializer is being used
this.logger = LogFactory.getLog(getClass());
// Create an IoC container
WebApplicationContext rootApplicationContext = createRootApplicationContext(servletContext);
if(rootApplicationContext ! =null) {
servletContext.addListener(new SpringBootContextLoaderListener(rootApplicationContext, servletContext));
}
else {
this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not "
+ "return an application context"); }}...// The method to create an IoC container
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
/ / create SpringApplicationBuilder
SpringApplicationBuilder builder = createSpringApplicationBuilder();
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(AnnotationConfigServletWebServerApplicationContext.class);
// Call the configure method, which subclasses override to pass in the main SpringBoot program
builder = configure(builder);
builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
Call the build method to create a Spring application
SpringApplication application = builder.build();
if(application.getAllSources().isEmpty() && MergedAnnotations.from(getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) { application.addPrimarySources(Collections.singleton(getClass())); } Assert.state(! application.getAllSources().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.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
}
application.setRegisterShutdownHook(false);
// Call the run method to start the SpringBoot application
returnrun(application); }...// configure method, subclass implementation after the master boot class
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
returnbuilder; }... }Copy the code
Therefore, the process of starting the SpringBoot application through the external Servlet container is as follows: Start the external Servlet container first, and then start the SpringBoot application.
There will be a more detailed article about the IoC container in Spring. ヾ(≧▽≦*)o