SpringBoot(五)Web开发

Web开发

简介

  1. 创建SpringBoot应用选中我们需要的web模块儿
  2. SpringBoot已经默认将这些场景已经配置好了,只需要指定少量的配置就可以让应用运行起来
  3. SpringBoot自动配置原理
  • 这个场景SpringBoot帮我们配置了什么?能不能修改?
    xxxxAutoConfiguration:帮我们给容器中自动配置组件;
    xxxxProperties:配置类来封装配置文件的内容;
    

1. SpringBoot对静态资源的映射规则

  1. 分析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));
	}
}
  1. 分析String staticPathPattern = this.mvcProperties.getStaticPathPattern();

    • 进入getStaticPathPattern()方法
      public String getStaticPathPattern() {
      		return this.staticPathPattern;
      	}
      
    • 再继续查看staticPathPattern的值,发现
      private String staticPathPattern = "/**";
  2. 分析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/"}

  3. 经过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 and BeanNameViewResolver 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的默认配置

模式:

  1. SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默认的组合起来;

  2. 在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置

  3. 在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置

6. 嵌入式Servlet容器

SpringBoot默认使用Tomcat作为嵌入式的Servlet容器
在这里插入图片描述

1)使用嵌入式的Servlet容器,如何定制和修改默认的Servlet容器配置

  1. 使用配置文件的方式修改和Servlet有关的配置
    #修改端口号
    server.port=8081
    #修改项目访问根路径
    server.context-path=/aaa
    //设置服务器编码
    server.tomcat.uri-encoding=UTF-8
    
    //通用的Servlet容器设置
    server.xxx
    //Tomcat的设置
    server.tomcat.xxx
    
  2. 使用配置类修改Servlet容器的配置
    @Configuration
    public class AppConfig {
        @Bean
        public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {
            return new EmbeddedServletContainerCustomizer() {
                @Override
                public void customize(ConfigurableEmbeddedServletContainer container) {
                    container.setPort(8081);
                }
            };
        }
    
    }
    
  3. 注册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");
          }
      }
      
  • 注册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());
          }
      }
      
  1. 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();
		}

	}
  1. EmbeddedServletContainerFactory:嵌入式servlet容器工厂
public interface EmbeddedServletContainerFactory {
    //获取嵌入式的Servlet容器
	EmbeddedServletContainer getEmbeddedServletContainer(
			ServletContextInitializer... initializers);

}

在这里插入图片描述

它的实现类有TomcatEmbeddedServletContainerFactory、JettyEmbeddedServletContainerFactory、UndertowEmbeddedServletContainerFactory

  1. EmbeddedServletContainer:嵌入式Servlet容器
    在这里插入图片描述

  2. 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);
	}
  1. 我们对嵌入式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类实现了EmbeddedServletContainerCustomizer接口,所以实际上两种更改嵌入式Servlet容器配置的方式是一样的

  1. 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容器

  1. 必须创建一个war项目
    在这里插入图片描述
  2. 创建好项目后,idea中默认不是标准的web应用结构,需自行创建webapp文件夹,以及web.xml文件
  • 创建webapp文件夹
    在这里插入图片描述

  • 创建web.xml文件
    在这里插入图片描述

    最后点击应用即可

  1. 需要在项目中配置我们自己的tomcat服务器
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值