SpringBoot 第四章WEB(2)
资料
server.error.include-exception=true
thymeleaf公共页面元素抽取
代码
1、抽取公共片段 《div th:fragment="copy"> © 2011 The Good Thymes Virtual Grocery 《/div> 2、引入公共片段 《div th:insert="~{footer :: copy}">《/div> ~{templatename::selector}:模板名::选择器 ~{templatename::fragmentname}:模板名::片段名 3、默认效果: insert的公共片段在div标签中 如果使用th:insert等属性进行引入,可以不用写~{}: 行内写法可以加上:[[~{}]];[(~{})];
三种引入公共片段的th属性:
th:insert:将公共片段整个插入到声明引入的元素中
th:replace:将声明引入的元素替换为公共片段
th:include:将被引入的片段的内容包含进这个标签中《footer th:fragment="copy"> © 2011 The Good Thymes Virtual Grocery 《/footer> 引入方式 《div th:insert="footer :: copy">《/div> 《div th:replace="footer :: copy">《/div> #copy id选择器 《div th:include="footer :: copy">《/div> 效果 《div> 《footer> © 2011 The Good Thymes Virtual Grocery 《/footer> 《/div> 《footer> © 2011 The Good Thymes Virtual Grocery 《/footer> 《div> © 2011 The Good Thymes Virtual Grocery 《/div>
引入片段的时候传入参数: 将同个变量的html片段放在同一个html
《nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar"> 《div class="sidebar-sticky"> 《ul class="nav flex-column"> 《li class="nav-item"> 《a class="nav-link active" th:class="${activeUri=='main.html'?'nav-link active':'nav-link'}" href="#" th:href="@{/main.html}"> 《svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home"> 《path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z">《/path> 《polyline points="9 22 9 12 15 12 15 22">《/polyline> 《/svg> Dashboard 《span class="sr-only">(current)《/span> 《/a> 《/li> 《!--引入侧边栏;传入参数--> 《div th:replace="commons/bar::#sidebar(activeUri='emps')">《/div>
高亮效果 可以使用th:class 来进行判断
错误处理机制
springboot默认处理机制
返回一个默认的错误页面 浏览器访问 在ErrorMvcAutoConfiguration中的render方法进行编写
客户端的数据格式是JSON 也就是响应json数据
原理代码
public class ErrorMvcAutoConfiguration { } || \/ public class ErrorMvcAutoConfiguration {}
DefaultErrorAttributes 帮我们共享页面数据
timestamp:时间戳 status:状态码 [[${...}]] error:错误提示 exception:异常对象 message:异常消息 errors:JSR303数据校验的错误都在这里 @Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException()); }
errorPageCustomizer:底层发起/error 请求 ,到BasicErrorController
@Bean public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) { return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath); } || \/ @Override public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { ErrorPage errorPage = new ErrorPage( this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath())); errorPageRegistry.addErrorPages(errorPage); } || \/ 响应路径 通过上面的path调用,系统出现错误以后来到error 请求进行处理;(类似于之前在web.xml注册的错误页面规则) 也就是发起/error请求 public class ErrorProperties { /** * Path of the error controller. */ @Value("${error.path:/error}") private String path = "/error"; }
BasicErrorController :处理默认的/error请求 --->去哪个页面由DefaultErrorViewResolver 决定
@Bean @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) { return new BasicErrorController(errorAttributes, this.serverProperties.getError(), errorViewResolvers.orderedStream().collect(Collectors.toList())); } || \/ @Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController extends AbstractErrorController { //产生html @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, isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); //ModelAndView 要去的页面地址 ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); } //产生json数据效果,其他请求 @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, isIncludeStackTrace(request, MediaType.ALL)); return new ResponseEntity<>(body, status); } } || \/ modelAndView响应页面 protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { //拿到所有的异常视图得到modelAndView--->errorViewResolvers //刚好我们有一个默认的errorViewResolver(DefaultErrorViewResolver ) for (ErrorViewResolver resolver : this.errorViewResolvers) { ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); if (modelAndView != null) { return modelAndView; } } return null; }
DefaultErrorViewResolver
@Bean @ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean(ErrorViewResolver.class) DefaultErrorViewResolver conventionErrorViewResolver() { return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties); } || \/ public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { private static final Map<Series, String> SERIES_VIEWS; //static 先放入4xx 5xx 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); }
浏览器在请求的请求头中优先接收text/html
客户端在请求的请求头中是/*
步骤:
一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被BasicErrorController处理;
1)响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的;
2)有模板引擎的情况下;error/状态码;** 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);页面能获取的信息;
我们可以自己配置一个404.html 手动不用配置到springboot中 springboot已经帮我们配置好了 直接在templates/error/404.html 或者4xx可以使用所有的页面
3)没有模板引擎(模板引擎找不到这个错误页面),静态资源static文件夹下找;
4)以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;相当于我们没有配置
响应一串自己定制错误的json数据
自定义一个Exception类
public class UserException extends RuntimeException{ public UserException() { super("not uesr----->my Exception"); } }
该json数据都放在了request中 我们可以通过页面来获取status:[[${status}]] br> exception:[[${exception}]] br> message:[[${message}]] br>
自定义json数据
@ControllerAdvice public class MyErrorHandler{ @ResponseBody @ExceptionHandler(UserException.class) public Map<String ,Object> handlerException(Exception e){ Map<String ,Object> map = new HashMap<>(); map.put("myCode","user.NotExist"); map.put("myMessage",e.getMessage()); return map; } }
没经过任何处理 连页面都是json数据串
自适应 效果 需要联想到 springboot 内部已配置的自适应效果
通过转发到/error
但响应状态码的情况是:status_code没改protected HttpStatus getStatus(HttpServletRequest request) { Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); if (statusCode == null) { return HttpStatus.INTERNAL_SERVER_ERROR; } try { return HttpStatus.valueOf(statusCode); } catch (Exception ex) { return HttpStatus.INTERNAL_SERVER_ERROR; } }
将我们的定制数据携带出去 map
出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法);
- 完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;【麻烦】
- 页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;
容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;
//给容器中加入我们自定义的ErrorAttributes @Component public class MyErrorAttributes extends DefaultErrorAttributes { // 自定义ErrorAttributes 改变默认行为 @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace); //异常处理器 Map<String, Object> exc = (Map<String, Object>) webRequest.getAttribute("exc", 0);//参数0 从请求域中获取 map.put("exc",exc); return map; } } @ControllerAdvice class MyErrorHandler{ @ExceptionHandler(UserException.class) public String handlerException(Exception e, HttpServletRequest request){ //通过分析 System.out.println("MyHandlerException........"); Map<String ,Object> map = new HashMap<>(); request.setAttribute("javax.servlet.error.status_code","500"); map.put("myCode","user.NotExist"); map.put("myMessage",e.getMessage()); request.setAttribute("exc",map); //---> request ---> DefaultErrorAttributes return "forward:/error"; } } 浏览器直接从exc中去即可
配置嵌入式Servlet容器
修改和server有关的配置(ServerProperties【底层也是EmbeddedServletContainerCustomizer】);
server.port=8081 server.context-path=/crud server.tomcat.uri-encoding=UTF-8 //通用的Servlet容器设置 server.xxx //Tomcat的设置 server.tomcat.xxx
编写一个EmbeddedServletContainerCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置
在配置类中编写@Bean //一定要将这个定制器加入到容器中 public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){ return new EmbeddedServletContainerCustomizer() { //定制嵌入式的Servlet容器相关的规则 @Override public void customize(ConfigurableEmbeddedServletContainer container) { container.setPort(8083); } }; }
xxxxCustomizer 可以帮助我们进行定制化配置
注册Servlet三大组件【Servlet、Filter、Listener】
原先注册这些组件是web.xml 来注册
Servlet
@Bean public ServletRegistrationBean servletRegistrationBean(){ MyServlet servlet = new MyServlet(); ServletRegistrationBean registrationBean = new ServletRegistrationBean(servlet,"/myServlet"); return registrationBean; } 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,httpServlet"); } }
Filter
@Bean public FilterRegistrationBean filterRegistrationBean(){ FilterRegistrationBean registrationBean = new FilterRegistrationBean(new MyFilter()); registrationBean.setUrlPatterns(Arrays.asList("/myServlet")); return registrationBean; } public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("初始化"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("doFilter...."); filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { System.out.println("销毁"); } }
Listener
@Bean public ServletListenerRegistrationBean servletListenerRegistrationBean(){ ServletListenerRegistrationBean registrationBean = new ServletListenerRegistrationBean(); registrationBean.setListener(new MyListener()); return registrationBean; } public class MyListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { System.out.println("init"); } @Override public void contextDestroyed(ServletContextEvent sce) { System.out.println("destroy"); } }
SpringBoot帮我们自动SpringMVC的时候,自动的注册SpringMVC的前端控制器;DIspatcherServlet;
DispatcherServletAutoConfiguration中:public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration"; @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<MultipartConfigElement> multipartConfig) { DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); // 在配置路径找到对应的配置文件 默认拦截 private String path = "/"; //默认拦截: / 所有请求;包静态资源,但是不拦截jsp请求; /*会拦截jsp //可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径 registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); multipartConfig.ifAvailable(registration::setMultipartConfig); return registration; } }
切换其他的servlet容器 我们默认是tomcat
Tomcat(默认使用)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> 引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器; </dependency>
Jetty
<!-- 引入web模块 --> <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容器--> <dependency> <artifactId>spring-boot-starter-jetty</artifactId> <groupId>org.springframework.boot</groupId> </dependency>
Undertow
<!-- 引入web模块 --> <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容器--> <dependency> <artifactId>spring-boot-starter-undertow</artifactId> <groupId>org.springframework.boot</groupId> </dependency>
三个容器在EmbeddedWebServerFactoryCustomizerAutoConfiguration中进行注册
以tomcat为例子public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { Registry.disableRegistry(); } //在这里new一个tomcat Tomcat tomcat = new 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); return this.getTomcatWebServer(tomcat); } || \/ return new TomcatWebServer(tomcat, this.getPort() >= 0);大于等于0调用方法 || \/ 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(); } || \/ //初始化 条件大于等于0 private void initialize() throws WebServerException { logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false)); synchronized(this.monitor) { try { this.addInstanceIdToEngineName(); Context context = this.findContext(); context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && "start".equals(event.getType())) { this.removeServiceConnectors(); } }); this.tomcat.start(); this.rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader()); } catch (NamingException var5) { } this.startDaemonAwaitThread(); } catch (Exception var6) { this.stopSilently(); this.destroySilently(); throw new WebServerException("Unable to start embedded Tomcat", var6); } } }
EmbeddedWebServerFactoryCustomizerAutoConfiguration:定制器帮我们修改了Servlet容器的配置?
怎么修改的原理?
- 容器中导入了WebServerFactoryCustomizerBeanPostProcessor 控制处理器
//初始化之前 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { ////如果当前初始化的是一个WebServerFactory类型的组件 if (bean instanceof WebServerFactory) { this.postProcessBeforeInitialization((WebServerFactory)bean); } return bean; } || \/ private void postProcessBeforeInitialization(WebServerFactory webServerFactory) { ((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; }
- 步骤:[推荐用配置文件]
1、SpringBoot根据导入的依赖情况,给容器中添加相应的EmbeddedWebServerFactoryCustomizerAutoConfiguration【TomcatWebServerFactoryCustomizer】
2、容器中某个组件要创建对象就会惊动后置处理器;WebServerFactoryCustomizerBeanPostProcessor;
只要是嵌入式的Servlet容器工厂,后置处理器就工作;
3、后置处理器,从容器中获取所有的TomcatWebServerFactoryCustomizer,调用定制器的定制方法
使用外置的Servlet容器
嵌入式Servlet容器:应用打成可执行的jar
1. 优点:简单、便携;
2. 缺点:默认不支持JSP、优化定制比较复杂(使用定制器【ServerProperties、自定义EmbeddedServletContainerCustomizer】,自己编写嵌入式Servlet容器的创建工厂【EmbeddedServletContainerFactory】);
外置的Servlet容器:外面安装Tomcat---应用war包的方式打包;
步骤
必须创建一个war项目;(利用idea创建好目录结构)
将嵌入式的Tomcat指定为provided;
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency>
必须编写一个SpringBootServletInitializer的子类,并调用configure方法【固定写法】
public class ServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { //传入SpringBoot应用的主程序SpringBootWebJspApplicationxxx return application.sources(SpringBootWebJspApplicationxxx.class); } }
添加一个tomcat服务器
启动服务器就可以使用;
原理
jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器;
war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器;
servlet3.0(Spring注解版):
规则:
- 服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例:
- ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名
- 还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;
流程:
启动Tomcat
\META-INF\services\javax.servlet.ServletContainerInitializer,Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer
@HandlesTypes({WebApplicationInitializer.class}) public class SpringServletContainerInitializer implements ServletContainerInitializer { public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {} }
SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set<Class<?>>;为这些WebApplicationInitializer类型的类创建实例;
每一个WebApplicationInitializer都调用自己的onStartup
public interface WebApplicationInitializer { void onStartup(ServletContext var1) throws ServletException; }
相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法
SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器
public void onStartup(ServletContext servletContext) throws ServletException { this.logger = LogFactory.getLog(this.getClass()); WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext); if (rootAppContext != null) { servletContext.addListener(new ContextLoaderListener(rootAppContext) { public void contextInitialized(ServletContextEvent event) { } }); } else { this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context"); } }
Spring的应用就启动并且创建IOC容器
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) { SpringApplicationBuilder builder = this.createSpringApplicationBuilder(); builder.main(this.getClass()); ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext); if (parent != null) { this.logger.info("Root context already created (using as parent)."); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null); builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)}); } builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)}); builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class); //调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来 builder = this.configure(builder); builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)}); //使用builder创建一个Spring应用 SpringApplication application = builder.build(); if (application.getAllSources().isEmpty() && MergedAnnotations.from(this.getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) { application.addPrimarySources(Collections.singleton(this.getClass())); } Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation"); if (this.registerErrorPageFilter) { application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class)); } return this.run(application); <-=run 自定义的容器过程 }
启动Servlet容器,再启动SpringBoot应用