Web开发
简介
- 创建SpringBoot应用选中我们需要的web模块儿
- SpringBoot已经默认将这些场景已经配置好了,只需要指定少量的配置就可以让应用运行起来
- SpringBoot自动配置原理
- 这个场景SpringBoot帮我们配置了什么?能不能修改?
xxxxAutoConfiguration:帮我们给容器中自动配置组件; xxxxProperties:配置类来封装配置文件的内容;
1. SpringBoot对静态资源的映射规则
- 分析WebMvcAutoConfiguration类
- 所有的webjars/**请求都被映射到类路径下的META-INF/resources/webjars下
- staticPathPattern对应的请求都被映射到this.resourceProperties.getStaticLocations()方法的返回值对应的路径下
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Integer cachePeriod = this.resourceProperties.getCachePeriod();
if (!registry.hasMappingForPattern("/webjars/**")) {
//将所有的webjars/**请求都映射到类路径下的META-INF/resources/webjars下
customizeResourceHandlerRegistration(
registry.addResourceHandler("/webjars/**")
.addResourceLocations(
"classpath:/META-INF/resources/webjars/")
.setCachePeriod(cachePeriod));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
//将staticPathPattern对应的请求都映射到this.resourceProperties.getStaticLocations()方法的返回值对应的路径下
customizeResourceHandlerRegistration(
registry.addResourceHandler(staticPathPattern)
.addResourceLocations(
this.resourceProperties.getStaticLocations())
.setCachePeriod(cachePeriod));
}
}
-
分析
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
- 进入getStaticPathPattern()方法
public String getStaticPathPattern() { return this.staticPathPattern; }
- 再继续查看staticPathPattern的值,发现
private String staticPathPattern = "/**";
- 进入getStaticPathPattern()方法
-
分析
registry.addResourceHandler(staticPathPattern).addResourceLocations(this.resourceProperties.getStaticLocations())
-
进入getStaticLocations()方法
public String[] getStaticLocations() { return this.staticLocations; }
-
再继续查看staticLocations对应的值
private String[] staticLocations = RESOURCE_LOCATIONS;
发现staticLocations是一个数组 -
继续查看RESOURCE_LOCATIONS数组
private static final String[] SERVLET_RESOURCE_LOCATIONS = { "/" }; private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" }; private static final String[] RESOURCE_LOCATIONS; static { RESOURCE_LOCATIONS = new String[CLASSPATH_RESOURCE_LOCATIONS.length + SERVLET_RESOURCE_LOCATIONS.length]; System.arraycopy(SERVLET_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, 0, SERVLET_RESOURCE_LOCATIONS.length); System.arraycopy(CLASSPATH_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, SERVLET_RESOURCE_LOCATIONS.length, CLASSPATH_RESOURCE_LOCATIONS.length); }
从这段代码中可以知道,数组RESOURCE_LOCATIONS的值为
则getStaticLocations()方法的返回值就是数组{"/","classpath:/META-INF/resources/","classpath:/resources/","classpath:/static/","classpath:/public/"}
-
-
经过1、2、3对addResourceHandlers()方法的分析,可以总结出静态资源的所有映射规则:
- 所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找资源;
- “/**” 访问当前项目的任何资源,都去(静态资源的文件夹)找映射
- “classpath:/META-INF/resources/”,
- “classpath:/resources/”,
- “classpath:/static/”,
- “classpath:/public/”
- “/”:当前项目的根路径
2. 欢迎页的映射
在WebMvcAutoConfiguration类中有一个welcomePageHandlerMapping方法
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(
ResourceProperties resourceProperties) {
return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
}
static final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping {
private static final Log logger = LogFactory
.getLog(WelcomePageHandlerMapping.class);
private WelcomePageHandlerMapping(Resource welcomePage,
String staticPathPattern) {
//发现所有的index.html页面都被/**映射
if (welcomePage != null && "/**".equals(staticPathPattern)) {
logger.info("Adding welcome page: " + welcomePage);
ParameterizableViewController controller = new ParameterizableViewController();
controller.setViewName("forward:index.html");
setRootHandler(controller);
setOrder(0);
}
}
所以可以总结出:
欢迎页; 静态资源文件夹下的所有index.html页面;被"/**"映射;
3. SpringMvc自动配置原理
1. Spring Mvc auto-configuration
SpringBoot自动帮我们配置好了Spring Mvc
以下是SpringMvc的自动配置:(WebMvcAutoConfiguration)
-
Inclusion of
ContentNegotiatingViewResolver
andBeanNameViewResolver
beans.- 自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染(转发?重定向?))
- ContentNegotiatingViewResolver:组合所有的视图解析器的;
- 如何定制:我们可以自己给容器中添加一个视图解析器;自动的将其组合进来;
-
Support for serving static resources, including support for WebJars (see below).静态资源文件夹路径,webjars
-
Static
index.html
support. 静态首页访问 -
Custom
Favicon
support (see below). favicon.ico -
自动注册了 of
Converter
,GenericConverter
,Formatter
beans.- Converter:转换器; public String hello(User user):类型转换使用Converter
Formatter
格式化器; 2017.12.17===Date;
@Bean
@ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")//在文件中配置日期格式化的规则
public Formatter<Date> dateFormatter() {
return new DateFormatter(this.mvcProperties.getDateFormat());//日期格式化组件
}
自己添加的格式化器转换器,我们只需要放在容器中即可
-
Support for
HttpMessageConverters
(see below).-
HttpMessageConverter:SpringMVC用来转换Http请求和响应的;User—Json;
-
HttpMessageConverters
是从容器中确定;获取所有的HttpMessageConverter;自己给容器中添加HttpMessageConverter,只需要将自己的组件注册容器中(@Bean,@Component)
-
-
Automatic registration of
MessageCodesResolver
(see below).定义错误代码生成规则 -
Automatic use of a
ConfigurableWebBindingInitializer
bean (see below).我们可以配置一个ConfigurableWebBindingInitializer来替换默认的;(添加到容器)
初始化WebDataBinder; 请求数据=====JavaBean;
org.springframework.boot.autoconfigure.web:web的所有自动场景;
If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration (interceptors, formatters, view controllers etc.) you can add your own @Configuration
class of type WebMvcConfigurerAdapter
, but without @EnableWebMvc
. If you wish to provide custom instances of RequestMappingHandlerMapping
, RequestMappingHandlerAdapter
or ExceptionHandlerExceptionResolver
you can declare a WebMvcRegistrationsAdapter
instance providing such components.
If you want to take complete control of Spring MVC, you can add your own @Configuration
annotated with @EnableWebMvc
.
4. 扩展SpringMVC
<mvc:view-controller path="/hello" view-name="success"/>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/hello"/>
<bean></bean>
</mvc:interceptor>
</mvc:interceptors>
编写一个配置类(@Configuration),是WebMvcConfigurerAdapter类型;不能标注@EnableWebMvc;
既保留了所有的自动配置,也能用我们扩展的配置;
//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
//需要什么功能就重写什么方法
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// super.addViewControllers(registry);
//浏览器发送 /atguigu 请求来到 success
registry.addViewController("/atguigu").setViewName("success");
}
}
原理:
1)、WebMvcAutoConfiguration是SpringMVC的自动配置类
2)、在做其他自动配置时会导入;@Import(EnableWebMvcConfiguration.class)
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
//从容器中获取所有的WebMvcConfigurer
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
//一个参考实现;将所有的WebMvcConfigurer相关配置都来一起调用;
@Override
// public void addViewControllers(ViewControllerRegistry registry) {
// for (WebMvcConfigurer delegate : this.delegates) {
// delegate.addViewControllers(registry);
// }
}
}
}
3)、容器中所有的WebMvcConfigurer都会一起起作用;
4)、我们的配置类也会被调用;
效果:SpringMVC的自动配置和我们的扩展配置都会起作用;
5、如何修改SpringBoot的默认配置
模式:
-
SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默认的组合起来;
-
在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置
-
在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置
6. 嵌入式Servlet容器
SpringBoot默认使用Tomcat作为嵌入式的Servlet容器
1)使用嵌入式的Servlet容器,如何定制和修改默认的Servlet容器配置
- 使用配置文件的方式修改和Servlet有关的配置
#修改端口号 server.port=8081 #修改项目访问根路径 server.context-path=/aaa //设置服务器编码 server.tomcat.uri-encoding=UTF-8 //通用的Servlet容器设置 server.xxx //Tomcat的设置 server.tomcat.xxx
- 使用配置类修改Servlet容器的配置
@Configuration public class AppConfig { @Bean public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() { return new EmbeddedServletContainerCustomizer() { @Override public void customize(ConfigurableEmbeddedServletContainer container) { container.setPort(8081); } }; } }
- 注册Servlet三大组件
-
注册Servlet(使用ServletRegistrationBean)
- 编写一个Servlet
public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doPost(req, resp); } }
- 将MyServlet注册到容器中
@Configuration public class AppConfig { @Bean //给容器中注册Servlet public ServletRegistrationBean myServlet() { return new ServletRegistrationBean(new MyServlet(), "/myServlet"); } }
- 编写一个Servlet
-
注册filter(使用FilterRegistrationBean)
-
编写一个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....."); } @Override public void destroy() { } }
-
将MyFilter注册到容器中
@Configuration public class AppConfig { @Bean //给容器中注册Filter public FilterRegistrationBean myFilter() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); filterRegistrationBean.setFilter(new MyFilter()); filterRegistrationBean.setUrlPatterns(Arrays.asList("/myServlet","/hello")); return filterRegistrationBean; } }
-
-
注册listener(使用ServletListenerRegistrationBean)
-
编写一个listener
public class MyListener implements ServletContexListener { @Override public void contextInitialized(ServletContextEvent servletContextEvent) { System.out.println("ContextListener......"); } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { } }
-
将MyListener注册到容器中
@Configuration public class AppConfig { @Bean //给容器中注册listener public ServletListenerRegistrationBean myListener(){ return new ServletListenerRegistrationBean(new MyListener()); } }
-
- Spring Mvc帮我们自动配置的时候,自动注册了前端控制器DispatchServlet,和我们自己注册servlet的方式相同
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public ServletRegistrationBean dispatcherServletRegistration( DispatcherServlet dispatcherServlet) { ServletRegistrationBean registration = new ServletRegistrationBean( dispatcherServlet, this.serverProperties.getServletMapping()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup( this.webMvcProperties.getServlet().getLoadOnStartup()); if (this.multipartConfig != null) { registration.setMultipartConfig(this.multipartConfig); } return registration; }
2)嵌入式Servlet自动配置原理
EmbeddedServletContainerAutoConfiguration类:嵌入式的Servlet容器自动配置
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
/**
* @Import导入BeanPostProcessorsRegistrar,
* BeanPostProcessorsRegistrar的作用:给容器中导入一些组件,
* 导入了EmbeddedServletContainerCustomizerBeanPostProcessor(后置处理器),
* 后置处理器:bean初始化前后(创建完对象,还没赋值赋值)执行初始化工作
*/
public class EmbeddedServletContainerAutoConfiguration {
//当前应用导入了哪个容器的依赖就创建哪个容器的工厂
/**
* Nested configuration if Tomcat is being used.
*/
@Configuration
//判断当前是否引入了tomcat依赖
@ConditionalOnClass({ Servlet.class, Tomcat.class })
//判断当前容器没有用户自己定义EmbeddedServletContainerFactory:嵌入式的Servlet容器工厂;作用:创建嵌入式的Servlet容器
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
/**
* Nested configuration if Jetty is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
WebAppContext.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Bean
public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
return new JettyEmbeddedServletContainerFactory();
}
}
/**
* Nested configuration if Undertow is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {
@Bean
public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
return new UndertowEmbeddedServletContainerFactory();
}
}
- EmbeddedServletContainerFactory:嵌入式servlet容器工厂
public interface EmbeddedServletContainerFactory {
//获取嵌入式的Servlet容器
EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers);
}
它的实现类有TomcatEmbeddedServletContainerFactory、JettyEmbeddedServletContainerFactory、UndertowEmbeddedServletContainerFactory
-
EmbeddedServletContainer:嵌入式Servlet容器
-
以TomcatEmbeddedServletContainerFactory为例进行分析
public class TomcatEmbeddedServletContainerFactory
extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
//创建一个tomcat容器对象
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
//添加tomcat容器的基本配置
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
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);
//将配置好的tomcat传入,返回一个EmbeddedServletContainer,并启动tomcat服务器
return getTomcatEmbeddedServletContainer(tomcat);
}
- 我们对嵌入式Servlet容器的配置怎么生效
-
通过EmbeddedServletContainerCustomizer:如我们向容器中添加我们自己的EmbeddedServletContainerCustomizer组件来更改servlet配置
@Configuration public class AppConfig { @Bean public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() { return new EmbeddedServletContainerCustomizer() { @Override public void customize(ConfigurableEmbeddedServletContainer container) { container.setPort(8081); } }; } }
-
通过配置文件的方式(实际上这种方式也是通过EmbeddedServletContainerCustomizer完成的)
server: port: 8080
- ServerProperties类是和配置文件绑定的
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) public class ServerProperties implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered { private Integer port; private InetAddress address;
- ServerProperties类是和配置文件绑定的
这里我们发现ServerProperties类实现了EmbeddedServletContainerCustomizer接口,所以实际上两种更改嵌入式Servlet容器配置的方式是一样的
- EmbeddedServletContainerCustomizer定制器帮我们修改Servlet容器配置的原理
而BeanPostProcessorsRegistrar给容器中添加了EmbeddedServletContainerCustomizerBeanPostProcessor
EmbeddedServletContainerCustomizerBeanPostProcessor类:
public class EmbeddedServletContainerCustomizerBeanPostProcessor
implements BeanPostProcessor, BeanFactoryAware {
//初始化之前
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
//如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件
if (bean instanceof ConfigurableEmbeddedServletContainer) {
postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
//获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值;
private void postProcessBeforeInitialization(
ConfigurableEmbeddedServletContainer bean) {
for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
customizer.customize(bean);
}
}
private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
if (this.customizers == null) {
// Look up does not include the parent context
this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
this.beanFactory
//从容器中获取所有这葛类型的组件:EmbeddedServletContainerCustomizer
//定制Servlet容器,给容器中可以添加一个EmbeddedServletContainerCustomizer类型的组件
.getBeansOfType(EmbeddedServletContainerCustomizer.class,
false, false)
.values());
Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
}
所以根据以上源码配置,可总结出如下步骤:
1)、SpringBoot根据导入的依赖情况,给容器中添加相应的EmbeddedServletContaine>rFactory【TomcatEmbeddedServletContainerFactory】
2)、容器中某个组件要创建对象就会触发后置处理器;EmbeddedServletContainerCus>tomizerBeanPostProcessor;
只要是嵌入式的Servlet容器工厂,后置处理器就工作;
3)、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,>调用定制器的定制方法
7. 使用外置的Servlet容器
- 必须创建一个war项目
- 创建好项目后,idea中默认不是标准的web应用结构,需自行创建webapp文件夹,以及web.xml文件
-
创建webapp文件夹
-
创建web.xml文件
最后点击应用即可
- 需要在项目中配置我们自己的tomcat服务器