四、SpringBoot的Web开发

作者: 木石前盟Caychen | 来源:发表于2019-04-18 16:02 被阅读2次

    1、简介

    使用Spring Boot:

    (1)、创建Spring Boot应用,添加需要的模块;

    (2)、Spring Boot对于支持自动配置的模块已经加载完毕,只需要在配置文件中指定少量配置信息即可;

    (3)、编写业务逻辑代码。

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

    ResourceProperties:Spring Boot静态资源配置类

    @ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
    public class ResourceProperties implements ResourceLoaderAware, InitializingBean {
        //可以设置和静态资源有关的参数,缓存时间等。。。
        
        
        private static final String[] SERVLET_RESOURCE_LOCATIONS = { "/" };
    
        private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
                "classpath:/META-INF/resources/", "classpath:/resources/",
                "classpath:/static/", "classpath:/public/" };
        
        //staticLocations即为上面连个变量的合集
        private String[] staticLocations = 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);
        }
        
        //other code...
        
    }
    

    WebMvcProperties:WebMvc配置类

    @ConfigurationProperties(prefix = "spring.mvc")
    public class WebMvcProperties {
        //静态资源的路径pattern为/**
        private String staticPathPattern = "/**";
        
        //other code...
    }
    

    MVC的自动配置都在WebMvcAutoConfiguration

    @Configuration
    @ConditionalOnWebApplication
    @ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
            WebMvcConfigurerAdapter.class })
    @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
    @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
            ValidationAutoConfiguration.class })
    public class WebMvcAutoConfiguration {
        //other code...
    }
    

    先来看看静态资源文件的映射规则:

    WebMvcAutoConfiguration#addResourceHandlers方法:

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        if (!this.resourceProperties.isAddMappings()) {
            logger.debug("Default resource handling disabled");
            return;
        }
        Integer cachePeriod = this.resourceProperties.getCachePeriod();
        //如果是/webjars/**请求,则将其映射到classpath:/META-INF/resources/webjars/目录下
        if (!registry.hasMappingForPattern("/webjars/**")) {
            customizeResourceHandlerRegistration(registry
                                                 .addResourceHandler("/webjars/**")
                                                 .addResourceLocations("classpath:/META-INF/resources/webjars/")
                                                 .setCachePeriod(cachePeriod));
        }
        //如果是请求/**,将其映射到上述staticLocations指定的值
        String staticPathPattern = this.mvcProperties.getStaticPathPattern();
        if (!registry.hasMappingForPattern(staticPathPattern)) {
            customizeResourceHandlerRegistration(
                registry.addResourceHandler(staticPathPattern)
                .addResourceLocations(
                    this.resourceProperties.getStaticLocations())
                .setCachePeriod(cachePeriod));
        }
    }
    

    1)、所有/webjars/**资源映射请求都去classpath:/META-INF/resources/webjars/找资源

    Integer cachePeriod = this.resourceProperties.getCachePeriod();
    if (!registry.hasMappingForPattern("/webjars/**")) {
        customizeResourceHandlerRegistration(registry
                                             .addResourceHandler("/webjars/**")
                                             .addResourceLocations("classpath:/META-INF/resources/webjars/")
                                             .setCachePeriod(cachePeriod));
    }
    

    ​ webjars:以jar包的方式引入的静态资源,http://www.webjars.org/

    以jQuery为例:引入jQuery的jar包

    <!-- 引入webjars-jQuery -->
    <dependency>
        <groupId>org.webjars</groupId>
        <artifactId>jquery</artifactId>
        <version>3.3.1-1</version>
    </dependency>
    
    以jar包引入的静态资源映射图.png

    请求jQuery正确的路径为:http://localhost:8080/webjars/jquery/3.3.1-1/jquery.js

    __总结:所有以/webjars/**的资源映射请求会去classpath:/META-INF/resources/webjars/找资源。 __

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

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

    this.mvcProperties.getStaticPathPattern()返回/**

    this.resourceProperties.getStaticLocations()返回

    "/",
    "classpath:/META-INF/resources/", 
    "classpath:/resources/",
    "classpath:/static/", 
    "classpath:/public/"
    

    __总结:任何访问/**的请求,就会去静态资源文件夹下查找对应的资源名。 __

    3)、欢迎页(首页)的资源映射

    @Bean
    public WelcomePageHandlerMapping welcomePageHandlerMapping(
        ResourceProperties resourceProperties) {
        return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),
                                             this.mvcProperties.getStaticPathPattern());
    }
    

    ResourceProperties#getWelcomePage方法:

    public Resource getWelcomePage() {
        for (String location : getStaticWelcomePageLocations()) {
            Resource resource = this.resourceLoader.getResource(location);
            try {
                if (resource.exists()) {
                    resource.getURL();
                    return resource;
                }
            }
            catch (Exception ex) {
                // Ignore
            }
        }
        return null;
    }
    

    ResourceProperties#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;
    }
    

    __总结:从源码来看,静态资源文件夹下的所有index.html页面会被/**映射。 __

    4)、所有的**/favicon.ico 都会在静态资源文件夹下查找:

    @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(Ordered.HIGHEST_PRECEDENCE + 1);
            mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
                                                       faviconRequestHandler()));
            return mapping;
        }
    
        @Bean
        public ResourceHttpRequestHandler faviconRequestHandler() {
            ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
            requestHandler
                .setLocations(this.resourceProperties.getFaviconLocations());
            return requestHandler;
        }
    }
    

    ResourceProperties#getFaviconLocations方法:

    //ResourceProperties#getFaviconLocations()
    List<Resource> getFaviconLocations() {
        List<Resource> locations = new ArrayList<Resource>(
            this.staticLocations.length + 1);
        if (this.resourceLoader != null) {
            for (String location : this.staticLocations) {
                locations.add(this.resourceLoader.getResource(location));
            }
        }
        locations.add(new ClassPathResource("/"));
        return Collections.unmodifiableList(locations);
    }
    

    __总结:任何路径下请求**/favicon.ico,都会去静态资源文件下查找。 __

    3、模版引擎

    JSP,Velocity,Freemarker,Thymeleaf


    模版引擎.png

    Spring Boot推荐的模版引擎:Thymeleaf

    3.1、引入Thymeleaf依赖

    <!-- 修改Spring Boot的默认版本 -->
    <thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
    <!-- 布局功能的支持程序
          thymeleaf3 对应layout2版本
          thymeleaf2 对应layout1版本
       -->
    <thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
    
    <!-- thymeleaf模版引擎 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    

    3.2、Thymeleaf的使用&语法

    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 = DEFAULT_PREFIX;
    
        private String suffix = DEFAULT_SUFFIX;
    
        private String mode = "HTML5";
    
        private Charset encoding = DEFAULT_ENCODING;
    
        private MimeType contentType = DEFAULT_CONTENT_TYPE;
        //缓存
        private boolean cache = true;
    
        private Integer templateResolverOrder;
    
        private String[] viewNames;
    
        private String[] excludedViewNames;
    
        private boolean enabled = true;
        
        //other code...
    }
    

    只要把模版html放置在classpath:/templates/目录下,thymeleaf就会自动渲染。

    使用Thymeleaf:

    ​ (1)、导入thymeleaf的名称空间:

    <html lang="en" xmlns:th="http://www.thymeleaf.org"></html>
    

    ​ (2)、thymeleaf语法:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <div th:text="${hello}">这里是原来的文本!</div>
    </body>
    </html>
    

    ​ (3)、语法规则:

    ​ i)、th:text:改变当前元素里面的文本内容

    th:任意html属性:用来替换原生html属性的值,例如id换成th:id, class换成th:class等等。

    thymeleaf语法标签.png

    ​ ii)、表达式:

    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.
             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进行使用
                <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: ~{...}:片段引用表达式
            
    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:
        If-then: (if) ? (then)
        If-then-else: (if) ? (then) : (else)
        Default: (value) ?: (defaultvalue)
        
    Special tokens:
        No-Operation: _
    

    4、SpringMVC自动配置

    https://docs.spring.io/spring-boot/docs/1.5.12.RELEASE/reference/htmlsingle/#boot-features-developing-web-applications

    4.1、Spring MVC auto-configuration

    Spring Boot 提供了大多数SpringMVC应用常用的自动配置项。

    以下是Spring Boot对SpringMVC的默认配置(来自官网,自行翻译):

    • 自动配置了 ContentNegotiatingViewResolverBeanNameViewResolver 的Beans.
      • 给容器自定义添加一个视图解析器,该ContentNegotiatingViewResolver 的bean会自动组合进来。
      • 自动配置了ViewResolver:ContentNegotiatingViewResolver是组合了所有的视图解析器
    public class WebMvcAutoConfiguration {
        //other code...
        
        @Bean
        @ConditionalOnBean(ViewResolver.class)
        @ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
        public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
            ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
            resolver.setContentNegotiationManager(
                beanFactory.getBean(ContentNegotiationManager.class));
            // ContentNegotiatingViewResolver uses all the other view resolvers to locate
            // a view so it should have a high precedence
            resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
            return resolver;
        }
    }
    

    其中ContentNegotiatintViewResolver类实现ViewResoler接口:

    public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
            implements ViewResolver, Ordered, InitializingBean {
        //other code...
        
        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
            Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
            //获取所有的MediaType,例如application/json,text/html等等。。。
            List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
            if (requestedMediaTypes != null) {
                //获取所有的视图
                List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
                //根据请求获取最适合的视图
                View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
                if (bestView != null) {
                    return bestView;
                }
            }
            //other code...
        }
        
        private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
                throws Exception {
    
            List<View> candidateViews = new ArrayList<View>();
            for (ViewResolver viewResolver : this.viewResolvers) {
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    candidateViews.add(view);
                }
                for (MediaType requestedMediaType : requestedMediaTypes) {
                    List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
                    for (String extension : extensions) {
                        String viewNameWithExtension = viewName + '.' + extension;
                        view = viewResolver.resolveViewName(viewNameWithExtension, locale);
                        if (view != null) {
                            candidateViews.add(view);
                        }
                    }
                }
            }
            //other code...
            
            return candidateViews;
        }
    }
    
    • 支持静态资源文件夹和webjars
    • 静态首页的访问。
    • 自定义favicon图标
    • 自动注册了 Converter, GenericConverter, Formatter Beans.
      • Converter:转换器,用于类型转换
      • Formatter:格式化器
    @Bean
    //如果配置了spring.mvc.date-format,则自动注册Formatter<Date>的Bean
    @ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")
    public Formatter<Date> dateFormatter() {
        return new DateFormatter(this.mvcProperties.getDateFormat());
    }
    

    自动添加的格式化转换器,只需添加到容器中即可。

    • 支持HttpMessageConverters 消息转换器。
      • HttpMessageConverter:SpringMVC用来转换Http请求和响应的
      • HttpMessageConverters 是从容器中获取的所有的HttpMessageConverter,如果需要给容器中添加HttpMessageConverter,只需要将自定义的组件注册在容器中即可。
    • 自动配置 MessageCodesResolver 用于错误代码的生成规则。
      • PREFIX_ERROR_CODE: error_code + "." + object name + "." + field
      • POSTFIX_ERROR_CODE:object name + "." + field + "." + error_code
    • 自动使用 ConfigurableWebBindingInitializerBean。

      • 可以自定义配置一个ConfigurableWebBindingInitializer来替换默认的,需要添加到容器中。
      • 初始化WebDataBinder:用于将请求数据绑定到数据模型等。

      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.2、扩展SpringMVC

    编写一个配置类,是WebMvcConfigurerAdapter类的子类,但是不能标注@EnableWebMvc注解。

    ​ 既保留了所有的自动配置,也能使用自定义的扩展配置。

    //使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
    @Configuration
    public class MyMvcConfig extends WebMvcConfigurerAdapter{
    
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
    //      super.addViewControllers(registry);
            //浏览器发送/cay请求,会直接跳到success页面。
            registry.addViewController("/cay").setViewName("success");
        }
    }
    

    原理:

    1)、WebMvcAutoConfiguration是SpringMVC的自动配置类,在内部维护了一个内部类WebMvcAutoConfigurationAdapter,该类又继承自WebMvcConfigurerAdapter,看定义:

    @Configuration
    @Import(EnableWebMvcConfiguration.class)
    @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
    public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {}
    

    WebMvcConfigurerAdapter又实现了WebMvcConfigurer接口:

    public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {}
    

    所以自定义的配置类(此例为MyMvcConfig)是个WebMvcConfigurer接口的实现类。

    2)、在做WebMvcAutoConfigurationAdapter自动配置时会导入@Import(EnableWebMvcConfiguration.class)

    @Configuration
    public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {}
    

    父类:

    @Configuration
    public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    
        private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
    
        //从容器中获取所有的WebMvcConfigurer
        @Autowired(required = false)
        public void setConfigurers(List<WebMvcConfigurer> configurers) {
            if (!CollectionUtils.isEmpty(configurers)) {
                this.configurers.addWebMvcConfigurers(configurers);
            }
        }
    }
    

    3)、容器中所有的WebMvcConfigurer都会一起起作用。

    class WebMvcConfigurerComposite implements WebMvcConfigurer {
    
        private final List<WebMvcConfigurer> delegates = new ArrayList<WebMvcConfigurer>();
    
        public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
            if (!CollectionUtils.isEmpty(configurers)) {
                this.delegates.addAll(configurers);
            }
        }
        
        @Override
        public void addFormatters(FormatterRegistry registry) {
            for (WebMvcConfigurer delegate : this.delegates) {
                delegate.addFormatters(registry);
            }
        }
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            for (WebMvcConfigurer delegate : this.delegates) {
                delegate.addInterceptors(registry);
            }
        }
        
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            for (WebMvcConfigurer delegate : this.delegates) {
                delegate.addViewControllers(registry);
            }
        }
        
        //other code...
    }
    

    从源码中可以看到,从容器中获取的所有WebMvcConfigurer对象都会被调用对应的配置方法。

    4)、最后可以结合第2点和第3点看出,自定义的配置类也会被调用。

    总结:SpringMVC的自动配置和自定义的扩展配置都会起作用。

    4.3、全面接管SpringMVC

    在配置类上使用@EnableWebMvc注解,这样Spring Boot对SpringMVC的自动配置就失效了,所有都需要自定义配置。

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

    ​ 1)、EnableWebMvc注解的定义

    @Import(DelegatingWebMvcConfiguration.class)
    public @interface EnableWebMvc {}
    

    ​ 2)、导入了DelegatingWebMvcConfiguration组件配置

    @Configuration
    public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {}
    

    ​ 3)、查看WebMvcAutoConfiguration自动配置类的签名

    @Configuration
    @ConditionalOnWebApplication
    @ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
            WebMvcConfigurerAdapter.class })
    //如果容器中没有该组件的时候,这个自动配置类就自动生效。
    @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
    @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
            ValidationAutoConfiguration.class })
    public class WebMvcAutoConfiguration {}
    

    ​ 4)、@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了,导致Spring Boot对SpringMVC的自动配置失效,即WebMvcAutoonfiguration配置类未注册成功。

    5、如何修改SpringBoot的默认配置?

    模式:

    ​ 1)、Spring Boot在自动配置很多组件的时候,会先检查容器中是否有用户自定义配置的Bean或者组件。如果有,就使用用户自定义的;如果没有,Spring Boot才自动配置;如果有些组件可以有多个,用户可以自定义添加组件,并加入到容器中,这样Spring Boot会自动将用户配置的和默认的组合起来。

    ​ 2)、在Spring Boot中会有很多的Configurer帮助用户进行扩展配置。

    ​ 3)、在Spring Boot中会有很多的Customizer帮助用户进行定制配置。

    6、Restful

    6.1、设置默认访问首页的url

    @Configuration
    public class MyMvcConfig extends WebMvcConfigurerAdapter{
    
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
    //      super.addViewControllers(registry);
            registry.addViewController("/cay").setViewName("success");
            registry.addViewController("/login.html").setViewName("login");
            registry.addViewController("/").setViewName("login");
        }
    }
    

    6.2、国际化

    Spring应用程序处理国际化资源的步骤:

    ​ 1)、编写国际化配置文件

    ​ 2)、使用ResourceBundleMessageSource管理国际化资源文件

    ​ 3)、在页面使用fmt:message取出国际化内容

    步骤:

    ​ 1)、编写国际化配置文件,抽取页面中需要进行显示的国际化信息。


    国际化配置信息.png

    ​ 2)、Spring Boot自动配置了管理国际化资源文件的组件:

    @Configuration
    @ConditionalOnMissingBean(value = MessageSource.class, search = SearchStrategy.CURRENT)
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
    @Conditional(ResourceBundleCondition.class)
    @EnableConfigurationProperties
    @ConfigurationProperties(prefix = "spring.messages")
    public class MessageSourceAutoConfiguration {
        
        /**
         * Comma-separated list of basenames (essentially a fully-qualified classpath
         * location), each following the ResourceBundle convention with relaxed support for
         * slash based locations. If it doesn't contain a package qualifier (such as
         * "org.mypackage"), it will be resolved from the classpath root.
         */
        private String basename = "messages";
        
        //other code...
        
        @Bean
        public MessageSource messageSource() {
            ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
            if (StringUtils.hasText(this.basename)) {
                //设置国际化资源文件的基础名
                messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
                        StringUtils.trimAllWhitespace(this.basename)));
            }
            if (this.encoding != null) {
                messageSource.setDefaultEncoding(this.encoding.name());
            }
            messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale);
            messageSource.setCacheSeconds(this.cacheSeconds);
            messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat);
            return messageSource;
        }
    }
    

    默认情况下,国际化资源文件的基础名为messages,且存放在classpath根路径下,即messages.properties、messages_zh_CN.properties、messages_en_US.properties等等,这样就无需在配置文件中设置spring.messages.basename=...了,但是如果基础名不为messages或者不在classpath根路径下,则需要手动添加spring.messages.basename=文件名.自定义的基础名,如果有多个就用逗号分隔。

    spring.messages.basename=i18n.login
    

    ​ 3)、去页面获取国际化的值:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
            <meta name="description" content="">
            <meta name="author" content="">
            <title>Signin Template for Bootstrap</title>
            <!-- Bootstrap core CSS -->
            <link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.1.0/css/bootstrap.css}" rel="stylesheet">
            <!-- Custom styles for this template -->
            <link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">
        </head>
    
        <body class="text-center">
            <form class="form-signin" action="dashboard.html">
                <img class="mb-4" th:src="@{/asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72">
                <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
                <label class="sr-only" th:text="#{login.username}">Username</label>
                <input type="text" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus="">
                <label class="sr-only" th:text="#{login.password}">Password</label>
                <input type="password" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required="">
                <div class="checkbox mb-3">
                    <label>
              <input type="checkbox" value="remember-me"> [[#{login.rememberme}]]
            </label>
                </div>
                <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.signin}">Sign in</button>
                <p class="mt-5 mb-3 text-muted">© 2017-2018</p>
                <a class="btn btn-sm">中文</a>
                <a class="btn btn-sm">English</a>
            </form>
        </body>
    </html>
    

    效果:根据浏览器语言设置的语言信息进行切换国际化语言。

    ​ 4)、如何通过链接切换国际化语言?

    原理:

    ​ 国际化区域信息对象Locale,有个区域信息对象处理器LocaleResolver,在WebMvcAutoConfiguration自动配置类中配置了一个LocaleResolver的Bean。

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
    public LocaleResolver localeResolver() {
        if (this.mvcProperties
            .getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
            return new FixedLocaleResolver(this.mvcProperties.getLocale());
        }
        AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
        localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
        return localeResolver;
    }
    
    • 如果在配置文件中配置了spring.mvc.locale-resolver=fixed,且指定了spring.mvc.locale的值,则页面使用的是指定且固定的国际语言(FixedLocaleResolver);
    • spring.mvc.locale-resolver默认为accept-header,即如果不配置则通过浏览器请求头中的Accept-Language 作为判断依据(AcceptHeaderLocaleResolver):
    public class AcceptHeaderLocaleResolver implements LocaleResolver {
        
        @Override
        public Locale resolveLocale(HttpServletRequest request) {
            Locale defaultLocale = getDefaultLocale();
            if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
                return defaultLocale;
            }
            Locale requestLocale = request.getLocale();
            List<Locale> supportedLocales = getSupportedLocales();
            if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
                return requestLocale;
            }
            Locale supportedLocale = findSupportedLocale(request, supportedLocales);
            if (supportedLocale != null) {
                return supportedLocale;
            }
            return (defaultLocale != null ? defaultLocale : requestLocale);
        }
        
        //other code..
    }
    

    步骤:

    ​ i)、编写一个自定义的LocaleResolver类:

    //使用链接的方法发送国际语言代码
    public class MyLocaleResolver  implements LocaleResolver{
    
        private final Logger logger = LoggerFactory.getLogger(MyLocaleResolver.class);
    
        @Override
        public Locale resolveLocale(HttpServletRequest request) {
            String lan = request.getParameter("lan");
            Locale locale = Locale.getDefault();
            if(StringUtils.hasText(lan)){
                try{
                    String[] split = lan.split("_");
                    locale = new Locale(split[0], split[1]);
                }catch (Exception e){
                    e.printStackTrace();
                    logger.error("错误信息:", e);
                }
            }
            return locale;
        }
    
        @Override
        public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
    
        }
    }
    

    先从请求参数中获取lan数据,然后通过下划线分割成语言和国家,然后通过语言和国家来封装成一个Locale对象并返回,如果未指定lan参数,则默认使用default的Locale对象。

    ​ ii)、修改页面切换语言的链接,添加国际化语言代码:

    <a class="btn btn-sm" th:href="@{/(lan='zh_CN')}">中文</a>
    <a class="btn btn-sm" th:href="@{/(lan='en_US')}">English</a>
    

    ​ iii)、将自定义的LocaleResolver覆盖默认的LocaleResolver:

    @Configuration
    public class MyMvcConfig extends WebMvcConfigurerAdapter{
    
        //使用自定义的LocaleResolver来替换掉默认的LocaleResolver
        @Bean
        public LocaleResolver localeResolver(){
            return new MyLocaleResolver();
        }
        //other code...
    }
    

    ​ 这样配置了自定义的LocaleResolver对象就会把WebMvcAutoConfiguration自动配置类中预定义的LocaleResolver的Bean屏蔽掉,Spring Boot就会使用自定义的LocaleResolver对象。

    注意:一旦使用链接来切换国际化语言,则会导致通过浏览器语言切换国际化语言的功能失效了。

    6.3、登录

    模版引擎页面修改以后,要实时生效:

    • 禁用模版引擎的缓存:
      • spring.thymeleaf.cache=false
    • 页面修改完成后,按下Ctrl+F9,重新编译。

    登录错误消息的显示:

    <p style="color:red;" th:text="${message}" th:if="${not #strings.isEmpty(message)}"></p>
    

    6.4、拦截器进行登录检查

    步骤:

    ​ i)、编写登录拦截器:

    //登录检查
    public class LoginHandlerInterceptor implements HandlerInterceptor {
    
        //目标方法执行前
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            Object user = request.getSession().getAttribute("loginUser");
            if(user == null){
                //未登录,返回登录页面
                request.setAttribute("message", "请先登录!");
                request.getRequestDispatcher("/").forward(request, response);
                return false;
            }else{
                //已登录,放行
                return true;
            }
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
        }
    }
    

    ​ ii)、配置拦截器:

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //      super.addInterceptors(registry);
        registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
            .excludePathPatterns("/", "/login.html", "/user/login");
    }
    

    6.5、CRUD-员工实验

    thymeleaf公共页面元素抽取:

    ​ 1)、抽取公共片段

    <div th:fragment="copy">
        &copy; 版权所有
    </div>
    

    ​ 2)、引入公共片段

    <div th:insert="~{footer :: copy}"></div>
    

    ~{templatename :: #selectorId} 表示:模版名::#选择器id

    代码片段
    <div id="copy-section">
        &copy; 2011 The Good Thymes Virtual Grocery
    </div>
    
    插入片段
    <div th:insert="~{footer :: #copy-section}"></div>
    

    ~{templatename :: fragmentname} 表示:模版名::片段名

    代码片段
    <div th:fragment="copy">
        &copy; 2011 The Good Thymes Virtual Grocery
    </div>
    
    插入片段
    <div th:insert="~{footer :: copy}"></div>
    

    ​ 3)、默认效果

    ​ insert的功能片段会被插入到div标签中。

    ​ 如果使用th:insert等属性进行引入,可以不用写~{},可以直接写templatename::#selectorId/fragmentname。

    但是如果是行内写法,必须加上~{}, 如[[~{}]]。

    三种引入公共片段的th属性:

    th:insert:将公共片段整个插入到声明引入的元素中

    th:replace:将声明引入的元素替换为公共片段

    th:include:将被引入的片段的内容包含到引入的元素中

    代码片段
    <footer th:fragment="copy">
        &copy; 版权所有
    </footer>
    
    引入方式
    <div th:insert="~{footer::copy}"></div>
    <div th:replace="~{footer::copy}"></div>
    <div th:include="~{footer::copy}"></div>
    
    实际效果:
    th:insert:
    <div>
        <footer>
            &copy; 版权所有
        </footer>
    </div>
    
    th:replace:
    <footer>
        &copy; 版权所有
    </footer>
    
    th:include:
    <div>
        &copy; 版权所有
    </div>
    

    在引入代码片段的时候,可以使用传递参数的方式,这样在代码片段中就可以使用传递过来的参数。

    <div th:fragment="frag">
    ...
    </div>
    
    <div th:replace="::frag (onevar=${value1},twovar=${value2})">
    

    <div th:fragment="frag (onevar,twovar)">
    <p th:text="${onevar} + ' - ' + ${twovar}">...</p>
    </div>
    
    <div th:replace="::frag (${value1},${value2})">...</div>
    使用命名参数时顺序不重要
    <div th:replace="::frag (onevar=${value1},twovar=${value2})">...</div>
    <div th:replace="::frag (twovar=${value2},onevar=${value1})">...</div>
    

    比如,在templates/commons/bar.html中定义了如下代码片段

    <nav id="sidebar">
        <a class="nav-link" th:class="${activeUrl == 'main' ? 'nav-link active' : 'nav-link'}" ...> 
            ...
        </a>
    </nav>
    

    在引入该代码片段的时候,可以使用传递参数的方式

    <div th:replace="commons/bar::#sidebar(activeUrl='main')"></div>
    或
    <div th:replace="commons/bar::#sidebar(activeUrl='emps')"></div>
    

    如果出现status=400,大多数是数据格式不正确,特别是日期格式。

    默认日期格式化是按照将/形式的字符串转换成日期类型,可以通过spring.mvc.date-format来设置日期格式:

    spring.mvc.date-format=yyyy-MM-dd
    

    相关文章

      网友评论

        本文标题:四、SpringBoot的Web开发

        本文链接:https://www.haomeiwen.com/subject/dfjtgqtx.html