欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Boot Web开发

程序员文章站 2022-07-12 20:29:24
...

1、使用Spring Boot:

  • 创建Spring Boot应用,选中我们需要的模块
  • Spring Boot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来
  • 自己编写业务代码

回顾自动配置原理

这个场景Spring Boot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?等等

  • xxxxAutoConfiguration:帮我们给容器中自动配置组件
  • xxxxProperties:配置类来封装配置文件的内容

2、Spring Boot对静态资源的映射规则

@ConfigurationProperties(
    prefix = "spring.resources",
    ignoreUnknownFields = false
)
public class ResourceProperties implements ResourceLoaderAware {
  // 可以设置和静态资源有关的参数,比如缓存时间
  ...
}

2.1 依赖的方式

// WebMvcAutoConfiguration.class文件
public void addResourceHandlers(ResourceHandlerRegistry registry) {
  if (!this.resourceProperties.isAddMappings()) {
    logger.debug("Default resource handling disabled");
  } else {
    Integer cachePeriod = this.resourceProperties.getCachePeriod();
    if (!registry.hasMappingForPattern("/webjars/**")) {
      this.customizeResourceHandlerRegistration(registry.addResourceHandler(
	new String[]{"/webjars/**"}).addResourceLocations(
		new String[]{"classpath:/META-INF/resources/webjars/"}
	).setCachePeriod(cachePeriod)
      );
    }

    String staticPathPattern = this.mvcProperties.getStaticPathPattern();
    if (!registry.hasMappingForPattern(staticPathPattern)) {
      this.customizeResourceHandlerRegistration(registry.addResourceHandler(
	new String[]{staticPathPattern}).addResourceLocations(
		this.resourceProperties.getStaticLocations()
	).setCachePeriod(cachePeriod)
      );
    }

  }
}
  • 所有/webjars/**,都去classpath:/META-INF/resources/webjars/找资源

    webjars:以jar包的方式引入静态资源

webjars官网

Spring Boot Web开发

访问方式:http://localhost:8088/webjars/jquery/3.5.1/jquery.js

<!-- 引入jquery-webjars -->
<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>jquery</artifactId>
  <version>3.5.1</version>
</dependency>

2.2 “/**”访问当前项目的任何资源(静态资源的文件夹)

继续读addResourceHandlers方法,如果处理不了,就去静态staticPathPattern路径去找

public WebMvcProperties() {
    this.localeResolver = WebMvcProperties.LocaleResolver.ACCEPT_HEADER;
    this.dispatchTraceRequest = false;
    this.dispatchOptionsRequest = true;
    this.ignoreDefaultModelOnRedirect = true;
    this.throwExceptionIfNoHandlerFound = false;
    this.logResolvedException = false;
    this.mediaTypes = new LinkedHashMap();
    this.staticPathPattern = "/**";
    this.async = new WebMvcProperties.Async();
    this.servlet = new WebMvcProperties.Servlet();
    this.view = new WebMvcProperties.View();
}

它添加了个位置addResourceLocations,这个里面的resourceProperties有个getStaticLocations()方法,点进去,有个常量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);
}

就找到了以下文件夹

private static final String[] SERVLET_RESOURCE_LOCATIONS = new String[]{"/"}; // 当前项目的根路径
// 和
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{
	"classpath:/META-INF/resources/",
	"classpath:/resources/", 
	"classpath:/static/", 
	"classpath:/public/"
};

src/mian/javasrc/main/resources都是类路径

如果要访问localhost:8088/*.js资源,没有人为处理,那么会自动去上面这些路径,静态资源文件夹里找这些文件

访问方式:http://localhost:8088/asserts/img/childhood_dreams.jpg

Spring Boot Web开发

2.3 欢迎页设置,静态资源文件夹下的所有index.html页面

// WebMvcAutoConfiguration.class文件
// 配置欢迎页映射
@Bean
public WebMvcAutoConfiguration.WelcomePageHandlerMapping welcomePageHandlerMapping
	(ResourceProperties resourceProperties) {
  return new WebMvcAutoConfiguration.WelcomePageHandlerMapping(
	resourceProperties.getWelcomePage(), this.mvcProperties.getStaticPathPattern()
  );
}

private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) {
  if (this.resourceHandlerRegistrationCustomizer != null) {
    this.resourceHandlerRegistrationCustomizer.customize(registration);
  }

}

Spring最顶层的组件,保存每个请求谁来处理

点进getWelcomePage()方法

this.mvcProperties.getStaticPathPattern()还被谁映射,点进去看源码就是/**

public Resource getWelcomePage() {
  String[] var1 = this.getStaticWelcomePageLocations();
  int var2 = var1.length;

  for(int var3 = 0; var3 < var2; ++var3) {
    String location = var1[var3];
    Resource resource = this.resourceLoader.getResource(location);

    try {
      if (resource.exists()) {
        resource.getURL();
        return resource;
      }
    } catch (Exception var7) {
    }
  }

  return null;
}

需要遍历,说明欢迎页还挺多的

点进getStaticWelcomePageLocations()方法

private String[] getStaticWelcomePageLocations() {
  String[] result = new String[this.staticLocations.length];

  for(int i = 0; i < result.length; ++i) {
    String location = this.staticLocations[i];
    if (!location.endsWith("/")) {
      location = location + "/";
    }

    result[i] = location + "index.html";
  }

  return result;
}

还是这个staticLocations静态文件夹

静态文件夹路径都拼接上index.html

访问方式:http://localhost:8088/ 就会找index页面

2.4 网站图标

// WebMvcAutoConfiguration.class文件
// 配置喜欢的图标
@Configuration
@ConditionalOnProperty(
  value = {"spring.mvc.favicon.enabled"},
  matchIfMissing = true
)
public static class FaviconConfiguration {
  private final ResourceProperties resourceProperties;

  public FaviconConfiguration(ResourceProperties resourceProperties) {
    this.resourceProperties = resourceProperties;
  }

  @Bean
  public SimpleUrlHandlerMapping faviconHandlerMapping() {
    SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
    mapping.setOrder(-2147483647);
    mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", 
	this.faviconRequestHandler()));
    return mapping;
  }

  @Bean
  public ResourceHttpRequestHandler faviconRequestHandler() {
    ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
    requestHandler.setLocations(this.resourceProperties.getFaviconLocations());
    return requestHandler;
  }
}

所有的**/favicon.ico都是在静态文件夹下找

2.5 自定义路径

spring.resources.static-locations=classpath:/hello/,classpath:initializr/

多路径用都好隔开,一旦启用自定义路径,那么默认的静态资源路径就不能使用了

3、模板引擎

JSP、Velocity、Freemarker、Thymeleaf

Spring Boot Web开发

Spring Boot推荐的Thymeleaf:语法更简单,功能更强大

3.1 引入Thymeleaf,引入starter:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

查看引入的库,发现引入的2.1.6版本的,太低了

Thymeleaf的版本发布

Spring官网Thymeleaf 3的使用

切换版本:

<properties>
  ...
  <!-- Thymeleaf主程序 -->
  <thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
  <!-- Thymeleaf布局支持程序 Thymeleaf3要求layout2以上版本 -->
  <thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version>
  <!-- Thymeleaf2和layout1适配 -->
</properties>

properties里面的会属性覆盖Spring Boot默认的版本号

thymeleaf-layout-dialect的版本发布

3.2 Thymeleaf使用及语法

还是在spring-boot-autoconfigure自动配置包里:添加组件

@Configuration
@ConditionalOnClass(
  name = {"org.thymeleaf.templatemode.TemplateMode"}
)
static class Thymeleaf3Configuration {
  Thymeleaf3Configuration() {
  }

  @Configuration
  @ConditionalOnClass({Servlet.class})
  @ConditionalOnWebApplication
  static class Thymeleaf3ViewResolverConfiguration extends 
	AbstractThymeleafViewResolverConfiguration {
    Thymeleaf3ViewResolverConfiguration
	(ThymeleafProperties properties, SpringTemplateEngine templateEngine) {
      super(properties, templateEngine);
    }

    protected void configureTemplateEngine
	(ThymeleafViewResolver resolver, SpringTemplateEngine templateEngine) {
      Method setTemplateEngine;
      try {
        setTemplateEngine = ReflectionUtils.findMethod(
		resolver.getClass(), 
		"setTemplateEngine", 
		new Class[]{Class.forName(
					"org.thymeleaf.ITemplateEngine", 
					true, 
					resolver.getClass().getClassLoader()
				)
			}
		);
      } catch (ClassNotFoundException var5) {
        throw new IllegalStateException(var5);
      }

      ReflectionUtils.invokeMethod(setTemplateEngine, resolver, new Object[]{templateEngine});
    }
  }

  @Configuration
  @ConditionalOnMissingBean(
    name = {"defaultTemplateResolver"}
  )
  static class DefaultTemplateResolverConfiguration extends 
	AbstractTemplateResolverConfiguration {

    DefaultTemplateResolverConfiguration(ThymeleafProperties properties, 
	  ApplicationContext applicationContext) {
      super(properties, applicationContext);
    }

    @Bean
    public SpringResourceTemplateResolver defaultTemplateResolver() {
      SpringResourceTemplateResolver resolver = super.defaultTemplateResolver();
      Method setCheckExistence = ReflectionUtils.findMethod(
		resolver.getClass(), 
		"setCheckExistence", 
		new Class[]{Boolean.TYPE}
	);
      ReflectionUtils.invokeMethod(
		setCheckExistence, 
		resolver, 
		new Object[]{this.getProperties().isCheckTemplate()}
	);
      return resolver;
    }
  }
}

因为我们引入的是3+版本,所以只有3+版本生效;

配置了哪些属性?在文件ThymeleafProperties中:

@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8");
    private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html");
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML5";
    private Charset encoding;
    private MimeType contentType;
    private boolean cache;
    private Integer templateResolverOrder;
    private String[] viewNames;
    private String[] excludedViewNames;
    private boolean enabled;
}

只要我们把HTML页面放在classpath:/templates/,Thymeleaf就能自动渲染

Thymeleaf语法还是参照官方网官方文档

3.3 代码示例

  • 导入Thymeleaf的名称空间
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  • 使用Thymeleaf语法
<div th:text="${text}">这里是div信息</div>

3.4 语法规则

th:text:改变当前元素里面的文本内容;

th:任意html属性;替换原生属性的值,例:th:class覆盖class

Feature Detail Attribute
Fragment inclusion 片段包含:jsp:include th:insert
th:replace
Fragment iteration 遍历:c:forEach th:each
Conditional evaluation 条件判断:c:if th:if
th:unless
th:switch
th:case
Local variable definition 声明变量:c:set th:object
th:with
General attribute modification 任意属性修改支持prepend, append th:attr
th:attrprepend
th:attrappend
Specific attribute modification 修改制定属性默认值 th:value
th:href
th:src
Text (tag body modification) 修改标签体内容 th:text(转义特殊字符)
th:utext(不转义特殊字符)
Fragment specification 声明片段 th:fragment
Fragment removal th:remove

3.5 表达式

  • Simple expressions: (表达式语法)

    • Variable Expressions: ${…}: 获取变量值; OGNL;
    1) 获取对象的属性、调用方法
    2) 使用内置的基本对象
      #ctx : the context object.
      #vars: the context variables.
      #locale : the context locale.
      #request : (only in Web Contexts) the HttpServletRequest object.
      #response : (only in Web Contexts) the HttpServletResponse object.
      #session : (only in Web Contexts) the HttpSession object.
      #servletContext : (only in Web Contexts) the ServletContext object.
    
      ${session.foo} // Retrieves the session atttribute 'foo'
      # 附录都有演示
    3) 内置的一些工具对象
      #execInfo : information about the template being processed.
      #messages : methods for obtaining externalized messages inside variables expressions, 
      # in the same way as they would be obtained using #{…} syntax.
      #uris : methods for escaping parts of URLs/URIs
      #conversions : methods for executing the configured conversion service (if any).
      #dates : methods for java.util.Date objects: formatting, component extraction, etc.
      #calendars : analogous to #dates , but for java.util.Calendar objects.
      #numbers : methods for formatting numeric objects.
      #strings : methods for String objects: contains, startsWith, prepending/appending, etc.
      #objects : methods for objects in general.
      #bools : methods for boolean evaluation.
      #arrays : methods for arrays.
      #lists : methods for lists.
      #sets : methods for sets.
      #maps : methods for maps.
      #aggregates : methods for creating aggregates on arrays or collections.
      #ids : methods for dealing with id attributes that might be repeated 
      # (for example, as a result of an iteration).
    
    • Selection Variable Expressions: *{…}: 选择表达式, 和${}在功能上是一样的
    补充: 配合th:object="${session.user}"进行使用
    <div th:object="${session.user}">
    	<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
    	<p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
    	<p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
    </div>
    
    • Message Expressions: #{…}: 获取国际化内容
    • Link URL Expressions: @{…}: 定义URL
    @{/order/process(execId=${execId},execType='FAST')}
    
    • Fragment Expressions: ~{…}: 片段引用表达式
    # 插入片段文档
    <div th:insert="~{commons :: main}">...</div>
    
  • Literals(字面量)

Text literals: 'one text' , 'Another one!' ,…
Number literals: 0 , 34 , 3.0 , 12.3 ,…
Boolean literals: true , false
Null literal: null
Literal tokens: one , sometext , main ,…
  • Text operations:(文本操作)
String concatenation: +
Literal substitutions: |The name is ${name}|
  • Arithmetic operations:(数学运算)
Binary operators: + , - , * , / , %
Minus sign (unary operator): -
  • Boolean operations:(布尔运算)
Binary operators: and , or
Boolean negation (unary operator): ! , not
  • Comparisons and equality:(比较运算)
Comparators: > , < , >= , <= ( gt , lt , ge , le )
Equality operators: == , != ( eq , ne )
  • Conditional operators:(条件运算, 也支持3元运算符)
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
  • Special tokens:(特殊符号)
No-Operation: _

4、SpringMVC自动配置

可以参阅官方文档Developing web applications

4.1 Spring Boot为Spring MVC提供了自动配置,可与大多数应用程序完美配合。

自动配置会在Spring的默认设置之上添加以下功能:

  • 包含ContentNegotiatingViewResolverBeanNameViewResolver Bean

    • 自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(就是View对象),视图对象决定如何渲染(转发?重定向?等))

    • ContentNegotiatingViewResolver:组合所有的视图解析器的

    • 如何定制:我们可以自己给容器中添加一个视图解析器,自动将其组合进来

    • Ctrl+N输入DispatchServlet.class在它的doDispatch方法前面设置断点,Debug运行,浏览器访问页面,查看控制台。看DispatchServlet里用到的视图解析器是什么?得到如图:

    Spring Boot Web开发

  • 支持提供静态资源,包括对WebJars的支持(请参见下文)。

  • 自动注册ConverterGenericConverterFormatter bean。

    • Converter:转换器,类型转换使用。例如前端文本转后台Integer
    • Formatter:格式化器,2020.08.13===Date
    // 配置文件中配置日期格式化的规则
    @Bean
    @ConditionalOnProperty(
      prefix = "spring.mvc",
      name = {"date-format"}
    )
    public Formatter<Date> dateFormatter() {
      return new DateFormatter(this.mvcProperties.getDateFormat()); // 日期格式化组件
    }
    
    • 自己添加的格式化器,我们只需要放在容器中即可
  • 支持HttpMessageConverters(请参见下文)。

    • HttpMessageConverter:SpringMVC中用来转换HTTP请求和响应的;例:User对象以JSON形式写出
    • HttpMessageConverters是从容器中确定的,获取所有的HttpMessageConverter
    • 自己给容器中添加HttpMessageConverter,只需将自己的组件注册在容器中(@Bean, @Component)
  • 自动注册MessageCodesResolver(请参见下文)。

    • 定义错误代码生成规则(例:JSR303校验时)
  • 静态index.html支持。

  • 自定义Favicon支持(请参阅下文)。

  • 自动使用ConfigurableWebBindingInitializer bean(请参见下文)。

    • 我们可以配置一个ConfigurableWebBindingInitializer来替换默认的;(添加到容器中)
    • 初始化WebDataBinder(web数据绑定器);
    • web数据绑定器的功能:请求数据=====绑定到JavaBean中

org.springframework.boot.autoconfigure.web:web的所有自动配置场景。

如果您想保留Spring Boot MVC功能,而只想添加其他MVC配置(拦截器,格式化程序,视图控制器等),则可以添加自己的类型为WebMvcConfigurerAdapter@Configuration类,但无需@EnableWebMvc。如果希望提供RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver的自定义实例,则可以声明一个提供此类组件的WebMvcRegistrationsAdapter实例。

如果要完全控制Spring MVC,则可以添加用@EnableWebMvc注释的自己的@Configuration

4.2 扩展SpringMVC

<mvc:view-controller path="/hello" view-name="success"/>
<mvc:interceptors>
  <mvc:interceptor>
    <mvc:mapping path="/hello"/>
    <bean></bean>
  </mvc:interceptor>
</mvc:interceptors>

要实现以上Spring配置文件的内容,编写一个配置类(@Configuration),是WebMvcConfigurerAdapter类型;不能标注@EnableWebMvc

// 使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
@Configuration
public class SpringMVCConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // super.addViewControllers(registry);
        //浏览器发送 /addViewController 请求来到 thymeleaf
        registry.addViewController("/addViewController").setViewName("thymeleaf");
    }
}

既保留了所有的自动配置,也能用我们扩展的配置;

原理:

  • WebMvcAutoConfiguration是SpringMVC的自动配置类
  • 在做其他自动配置时会导入;@Import(EnableWebMvcConfiguration.class)
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
  ...
}

点进其父类DelegatingWebMvcConfiguration查看源码:

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

    public DelegatingWebMvcConfiguration() {
    }

    //自动装配, 从容器中获取所有的WebMvcConfigurer
    @Autowired(
        required = false
    )
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
            // 一个参考实现; 将所有的WebMvcConfigurer相关配置都来一起调用;
        }

    }
}

WebMvcConfigurerComposite类中的一个实现

public void addViewControllers(ViewControllerRegistry registry) {
  Iterator var2 = this.delegates.iterator();

  while(var2.hasNext()) {
    WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
    delegate.addViewControllers(registry);
  }

}
  • 容器中所有的WebMvcConfigurer都会一起起作用(包括我们自己写的);
  • 我们的配置类也会被调用;
    • 效果:SpringMVC的自动配置和我们的扩展配置都会起作用;

4.3 全面接管SpringMVC

SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己配置;所有的SpringMVC的自动配置都失效了(静态资源也不能访问了)

我们需要在配置类中添加@EnableWebMvc即可;

原理:为什么@EnableWebMvc自动配置就失效了?

  • @EnableWebMvc的核心
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
  • 查看DelegatingWebMvcConfiguration类的源码
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
  ...
}
  • 在找到WebMvcAutoConfiguration类,查看签名
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, 
	ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {
  ...
}

@ConditionalOnMissingBean():容器中没有这个组件的时候,这个自动配置类才生效

  • @EnableWebMvc将WebMvcConfigurationSupport组件导入进来;
  • 导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能。

5、如何修改Spring Boot的默认配置

模式:

  • Spring Boot在自动配置很多组件时,先看容器中有没有用户自己配置的(@Bean, @Component),如果有就用用户配置的,如果没有才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默认的组合起来;
  • 在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置
  • 在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置