《Spring Boot+Vue全栈开发实战》读书笔记

写在前面

  • 嗯,回家处理一些事,所以离职了,之前的公司用开源技术封装了一套自己的低代码平台,所以之前学的spring Boot之类的东西都忘了很多,蹭回家的闲暇时间复习下。
  • 笔记整体以 Spring Boot+Vue全栈开发实战一书为方向,中间穿插一些其他视频(原书作者的视频)的知识点。
  • 嗯,生活加油,这段时间好好休养,笔记在更新中…整装待发 ^ _ ^,加油生活…

我年青时以为金钱至上,而今年事已迈,发现果真如此 —王尔德


使用XML配置搭建SSM项目

代码详见:https://github.com/LIRUILONGS/SSM-XML.git

  • 新建一个maven工程,构造SSM目录结构
    在这里插入图片描述
  • 添加依赖,构建配置文件
    在这里插入图片描述
    SpringMVC是Spring的子容器,所以SpringMVC子容器可以访问Spring父容器,反之则不行。所以Spring的配置文件扫描除了Controller的bean,SpringMVC扫描controller的东西。
    在这里插入图片描述

使用 Java配置类搭建SSM项目

代码详见:https://github.com/LIRUILONGS/SSM-java.git

  • @Configuration 注解表示这是一个配置类,在我们这里,这个配置的作用类似于 applicationContext.xml
  • @ComponentScan 注解表示配置包扫描,里边的属性和 xml 配置中的属性都是一一对应的,useDefaultFilters 表示使用默认的过滤器,然后又除去 Controller 注解,即在 Spring 容器中扫描除了 Controller 之外的其他所有 Bean 。
  • 使用 Java 代码去代替 web.xml 文件,这里会用到 WebApplicationInitializer ,WebInit 的作用类似于 web.xml,这个类需要实现 WebApplicationInitializer 接口,并实现接口中的方法,当项目启动时,onStartup 方法会被自动执行,我们可以在这个方法中做一些项目初始化操作,例如加载 SpringMVC 容器,添加过滤器,添加 Listener、添加 Servlet 等。具体定义如下:
    在这里插入图片描述

注意
由于我们在 WebInit 中只是添加了 SpringMVC 的配置,这样项目在启动时只会去加载 SpringMVC 容器,而不会去加载 Spring 容器,如果一定要加载 Spring 容器,需要我们修改 SpringMVC 的配置,在 SpringMVC 配置的包扫描中也去扫描 @Configuration 注解,进而加载 Spring 容器,还有一种方案可以解决这个问题,就是直接在项目中舍弃 Spring 配置,直接将所有配置放到 SpringMVC 的配置中来完成,这个在 SSM 整合时是没有问题的,在实际开发中,较多采用第二种方案,第二种方案,SpringMVC 的配置如下:

  • 静态资源过滤:重写 addResourceHandlers 方法,在这个方法中配置静态资源过滤,这里我将静态资源放在 resources 目录下,所以资源位置是 classpath:/ ,当然,资源也可以放在 webapp 目录下,此时只需要修改配置中的资源位置即可。如果采用 Java 来配置 SSM 环境,一般来说,可以不必使用 webapp 目录,除非要使用 JSP 做页面模板,否则可以忽略 webapp 目录。

  • 视图解析器

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>
@Configuration
@ComponentScan(basePackages = "org.javaboy")
public class SpringMVCConfig extends WebMvcConfigurationSupport {
    @Override
    protected void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/jsp/", ".jsp");
    }
}
  • 路径映射:控制器的作用仅仅只是一个跳转,就像上面小节中的控制器,里边没有任何业务逻辑,像这种情况,可以不用定义方法,可以直接通过路径映射来实现页面访问。如果在 XML 中配置路径映射
<mvc:view-controller path="/hello" view-name="hello" status-code="200"/>

这行配置,表示如果用户访问 /hello 这个路径,则直接将名为 hello 的视图返回给用户,并且响应码为 200,这个配置就可以替代 Controller 中的方法。

@Configuration
@ComponentScan(basePackages = "org.javaboy")
public class SpringMVCConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/hello3").setViewName("hello");
    }
}
  • JSON 配置SpringMVC 可以接收JSON 参数,也可以返回 JSON 参数,这一切依赖于 HttpMessageConverter。

HttpMessageConverter 可以将一个 JSON 字符串转为 对象,也可以将一个对象转为 JSON 字符串,实际上它的底层还是依赖于具体的 JSON 库。
所有的 JSON 库要在 SpringMVC 中自动返回或者接收 JSON,都必须提供和自己相关的 HttpMessageConverter 。
SpringMVC 中,默认提供了 Jackson 和 gson 的 HttpMessageConverter ,分别是:MappingJackson2HttpMessageConverter 和 GsonHttpMessageConverter 。
正因为如此,我们在 SpringMVC 中,如果要使用 JSON ,对于 jackson 和 gson 我们只需要添加依赖,加完依赖就可以直接使用了。具体的配置是在 AllEncompassingFormHttpMessageConverter 类中完成的。
如果开发者使用了 fastjson,那么默认情况下,SpringMVC 并没有提供 fastjson 的 HttpMessageConverter ,这个需要我们自己提供,如果是在 XML 配置中,fastjson 除了加依赖,还要显式配置 HttpMessageConverter,如下:

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

第1章Spring Boot入门

  • 提供一个快速的Spring项目搭建渠道
  • 开箱即用,很少的Spring 配置就能运行一个Java EE项目。
  • 提供了生产级的服务监控方案。
  • 内嵌服务器,可以快速部署。
  • 提供了一系列非功能性的通用配置。
  • 纯Java配置,没有代码生成,也不需要XML配置。

第2章 Spring Boot基础配置

工程创建的三种方式:

  1. 在线创建
  2. 通过 IDE 来创建(IntelliJ IDEA、STS)
  3. 通过改造一个普通的 Maven 工程来实现

2.1不使用spring-boot-starter-parent

spring-boot-starter-parent主要提供了如下默认配置:

  • Java版本默认使用1.8.编码格式
  • 默认使用UTF-8.
  • 提供Dependency Management进行项目依赖的版本管理。
  • 默认的资源过滤与插件配置

2.2 @Spring BootApplication.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
....

@Spring BootApplication 是一个组合注解:

  • @SpringBootConfiguration原来就是一个@Configuration,所以@Spring BootConfiguration的功能就是表明这是一个配置类。开发者可以在这个类中配置Bean。从这个角度来讲,这个类所扮演的角色有点类似于Spring中applicationContext.xml文件的角色。
  • 第二个注解@EnableAutoConfiguration表示开启自动化配置。 Spring Boot中的自动化配置是非侵入式的,在任意时刻,开发者都可以使用自定义配置代替自动化配置中的某一个配置。
  • 第三个注解@ComponentScan完成包扫描,也是Spring中的功能。由于@ComponentScan注解默认扫描的类都位于当前类所在包的下面,因此建议在实际项目开发中把项目启动类放在根包。

2.3定制banner

  • Spring Boot项目在启动时会打印一个banne
  • 定制网站:http://patorjk.com/software/taag
@SpringBootApplication
public class SpringBootDemoApplication {

    public static void main(String[] args) {
        SpringApplicationBuilder builder = new SpringApplicationBuilder(SpringBootDemoApplication.class);
        builder.bannerMode(Banner.Mode.OFF).run(args);
    }
}

2.4 Web容器配置

2.4.1 Tomcat配置
##配置了Web容器的端口号。
server.port=8081  
##配置了当项目出错时跳转去的页面。
server.error.path=/error  
##配置了session失效时间, 30m表示30分钟,如果不写单位,默认单位是秒。由于Tomcat中配置session过期时间以分钟为单位,因此这里单位如果是秒的话,该时间会被转换为一个不超过所配置秒数的最大分钟数,例如这里配置了119,默认单位为秒,则实际session过期时间为1分钟。
server.servlet.session.timeout=30m
##表示项目名称,不配置时默认为/,如果配置了,就要在访问路径中加上配置的路径。
server.servlet.context-path=/  
##表示配置Tomcat请求编码。
server.tomcat.uri-encoding=utf-8
##表示Tomcat最大线程数。
server.tomcat.threads.max=500  
##是一个存放Tomcat运行日志和临时文件的目录,若不配置,则默认使用系统的临时目录。
server.tomcat.basedir=/home/sang/tmp  
##
HTTPS的配置:

在这里插入图片描述

## 密匙文件
server.ssl.key-store=sang.p12
## 密匙别名
server.ssl.key-alias=tomcathttps
## 就是在cmd命令执行过程中输入的密码
server.ssl.key-store-password=123456

Spring Boot不支持同时在配置中启动HTTPHTTPS,这个时候可以配置请求重定向,将HTTP请求重定向为HTTPS请求。配置方式如下:

@Configuration
public class TomcatConfig {
    /*
     * @return
     * @Description : TODO 配置一个 TomcatServletWebServerFactory 的Bean,
     * @author Liruilong
     * @date  2021/6/3  11:47
     **/
    @Bean
    TomcatServletWebServerFactory tomcatServletWebServerFactory() {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(){
            @Override
            protected void postProcessContext(Context context) {
                SecurityConstraint constraint = new SecurityConstraint();
                constraint.setUserConstraint("CONFIDENTIAL");
                SecurityCollection collection = new SecurityCollection();
                collection.addPattern("/*");
                constraint.addCollection(collection);
                context.addConstraint(constraint);
            }
        };
        factory.addAdditionalTomcatConnectors(createTomcatConnector());
        return factory;
    }
    /*
     * @return
     * @Description
     * @author Liruilong
     * @date  2021/6/3  11:45
     **/
    private Connector createTomcatConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        connector.setPort(8080);
        connector.setSecure(false);
        connector.setRedirectPort(8081);
        return connector;
    }
}

这里首先配置一个TomcatServletWebServerFactory,然后添加一个Tomcat中的Connector (监听8080端口) ,并将请求转发到8081上去。

2.4.2 Jetty配置

除了Tomcat外,也可以在Spring Boot中嵌入Jetty,从spring-boot-starter-web中除去默认的Tomcat,然后加入Jetty的依赖即可配置方式如下:

   <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
2.4.3 Undertow配置

Undertow是一个红帽公司开源的Java服务器,具有非常好的性能,在Spring Boot中也得到了很好的支持,配置方式与Jetty类似。

2.5 Properties配置

Spring Boot项目中的·application.properties配置文件一共可以出现在如下4个位置:加载的优先级从1到4依次降低

在这里插入图片描述

  1. 项目根目录下的config文件夹中。
  2. 项目根目录下。
  3. classpath 下的config文件夹中。
  4. classpath 下

application.yml配置文件的优先级与上面一致默认情况下, 如果开发者不想使用application.properties作为配置文件名,也可以自己定义。例如,在resources目录下创建一个配置文件app.properties,然后将项目打成jar包,打包成功后,使用如下命令运行:
在这里插入图片描述

2.6类型安全配置属性.

Spring提供了@Value注解以及EnvironmentAware接口来将Spring Environment中的数据注入到属性上, Spring Boot对此进一步提出了类型安全配置属性(Type-safe ConfigurationProperties) ,这样即使在数据量非常庞大的情况下,也可以更加方便地将配置文件中的数据注入Bean中.
在这里插入图片描述
yml类型配置文件:

my:
  users:
    - name: 江南一点雨
      address: China
      favorites:
        - 足球
        - 徒步
        - Coding
    - name: sang
      address: GZ
      favorites:
        - 阅读
        - 吉他
/**
 * Created by sang on 2018/7/5.
 */
@Component
@ConfigurationProperties(prefix = "my")
public class Users {
    private List<User> users;
}

2.7 YAML配置

YAMLJSON的超集,简洁而强大,是一种专门用来书写配置文件的语言,可以替代application.properties。在创建一个Spring Boot项目时,引入的spring-boot-starter-web依赖间接地引入了snakeyaml依赖, snakeyaml会实现对YAML配置的解析。YAML的使用非常简单,利用缩进来表示层级关系,并且大小写敏感。在Spring Boot项目中使用YAML只需要在resources目录下创建一个application.yml文件即可,然后向application.yml中添加配置:

server:
  port: 80
  servlet:
    context-path: /chapter02
  tomcat:
    uri-encoding: utf-8

my:
  users:
    - name: 江南一点雨
      address: China
      favorites:
        - 足球
        - 徒步
        - Coding
    - name: sang
      address: GZ
      favorites:
        - 阅读
        - 吉他

2.8 Profile

开发者在项目发布之前,配置需要频繁更改,例如数据库配置、redis配置、mongodb配置、jms配置等。频·繁修改带来了巨大的工作量, Spring对此提供了解决方案(@Profile注解) , Spring Boot则更进一步提供了更加简洁的解决方案, Spring Boot中约定的不同环境下配置文件名称规则为application-{profile}.properties, profile占位符表示当前环境的名称,具体配置步骤如下:

不同的环境指定不同的配置文件
spring.profiles.active=dev

在这里插入图片描述


第3章Spring Boot整合视图层技术

Spring Boot官方推荐使用的模板引擎是Thymeleaf,不过像FreeMarker也支持, JSP技术在这里并不推荐使用。下面分别向读者介绍Spring Boot整合Thymeleaf和FreeMarker两种视图层技术。

3.1整合Thymeleaf

Thymeleaf是新一代Java模板引擎,类似于Velocity, FreeMarker等传统Java模板引擎。与传统Java模板引擎不同的是, Thymeleaf支持HTML原型,既可以让前端工程师在浏览器中直接打开查看样式,也可以让后端工程师结合真实数据查看显示效果。同时, Spring Boot提供了Thymeleaf自动化配置解决方案,因此在Spring Boot中使用Thymeleaf非常方便。Spring Boot整合Thymeleaf主要可通过如下步骤:

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
2.配置Thymeleaf

Spring Boot为Thymeleaf提供了自动化配置类ThymeleafAutoConfiguration,相关的配置属性在ThymeleatProperties类中, ThymeleafProperties部分源码如下:
在这里插入图片描述
如果开发者想对默认的Thymeleaf配置参数进行自定义配置,那么可以直接在application.properties中进行配置,部分常见配置如下:

#是否开启缓存,开发时可设置为false,默认为true
spring.thymeleaf.cache=true
#是否检查模板是否存在,默认为true
spring.thymeleaf.check-template=true
#是否检查模板位置是否存在,默认为true
spring.thymeleaf.check-template-location=true
#模板文件编码
spring.thymeleaf.encoding=UTF-8
#模板文件位置
spring.thymeleaf.prefix=classpath:/templates/
#Content-Type配置
spring.thymeleaf.servlet.content-type=text/html
#模板文件后缀
spring.thymeleaf.suffix=.html

在这里插入图片描述
在这里插入图片描述
官网: https://www.thymeleaf.org

3.2整合FreeMarke

FreeMarker是一个非常古老的模板引擎,可以用在Web环境或者非Web环境中。与Thymeleaf不同, FreeMarker需要经过解析才能够在浏览器中展示出来。FreeMarker不仅可以用来配置HTML页面模板,也可以作为电子邮件模板配置文件模板以及源码模板等。Spring Boot中对FreeMarker整合也提供了很好的支持.

配置FreeMarker

Spring Boot对FreeMarker也提供了自动化配置类FreeMarkerAutoConfiguration,相关的配置属性在FreeMarkerProperties 中,

#HttpServletRequest的属性是否可以覆盖controller中model的同名项
spring.freemarker.allow-request-override=false
#HttpSession的属性是否可以覆盖controller中model的同名项
spring.freemarker.allow-session-override=true
#是否开启缓存
spring.freemarker.cache=fal se
#模板文件编码
spring.freemarker.charset=UTF-8
#是否检查模板位置
spring.freemarker.check-template-location=true
#Content-Type的值
spring.freemarker.content-type=text/html
#是否将HttpServletRequest中的属性添加到Model中
spring.freemarker.expose-request-attributes=false
#是否将HttpSession中的属性添加到Model中
spring.freemarker.expose-session-attributes=true
#模板文件后缀
spring.freemarker.suffix=.ftl
#模板文件位置
spring.freemarker.template-loader-path=classpath:/templates/

在这里插入图片描述
在这里插入图片描述
官网:https://freemarker.apache.org/

3.3 整合 JSP

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>
package com.liruilong.spring_boot_demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @Classname WebMvcConfig
 * @Description TODO
 * @Date 2021/6/4 10:02
 * @Created Li Ruilong
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/jsp/", ".jsp");
    }
}

第4章 Spring Boot整合Web开发.

4.1返回JSON数据

4.1.1 默认实现

JSON是目前主流的前后端数据传输方式, Spring MVC中使用消息转换器HttpMessageConverter对JSON的转换提供了很好的支持,在Spring Boot中更进一步,对相关配置做了更进一步的简化。默认情况下,当开发者新创建一个Spring Boot项目后,添加Web依赖,
在这里插入图片描述
这个依赖中默认加入了jackson-databind作为JSON处理器,此时不需要添加额外的JSON处理器就能返回一段JSON了.

如果需要频繁地用到@ResponseBody注解,那么可以采用@RestController组合注解代替@Controller@ResponseBody

这是Spring Boot自带的处理方式。如果采用这种方式,那么对于字段忽略、日期格式化等常见需求都可以通过注解来解决。这是通过Spring中默认提供的MappingJackson2HttpMessageConverter来实现的.
HttpMessageConverter ,看名字就知道,这是一个消息转换工具,有两方面的功能:

  1. 将服务端返回的对象序列化成 JSON 字符串
  2. 将前端传来的 JSON 字符串反序列化成 Java 对象
    所有的 JSON 生成都离不开相关的 HttpMessageConverter,SpringMVC 自动配置了JacksonGson 的 HttpMessageConverter,Spring Boot 中又对此做了自动化配置:
  3. org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration
  4. org.springframework.boot.autoconfigure.http.GsonHttpMessageConvertersConfiguration

所以,如果用户使用 jacksongson 的话,没有其他额外配置,则只需要添加依赖即可。

修改转化器

添加一个MappingJackson2HttpMessageConverter,由@ConditionalOnMissingBean确定。
嗯,我们温习一下条件化注解吧

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当然这里我们也可以只定义一个 ObjectMapper

package com.liruilong.spring_boot_demo.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;


import java.text.SimpleDateFormat;

/**
 * @Classname WebMvcConfig
 * @Description TODO
 * @Date 2021/6/4 10:02
 * @Created Li Ruilong
 */
@Configuration
public class WebMvcConfig  {
    
    @Bean
    MappingJackson2HttpMessageConverter mappingJackson2CborHttpMessageConverter(){
        return new MappingJackson2HttpMessageConverter(objectMapper());
    }
    
    @Bean
    ObjectMapper objectMapper() {
        ObjectMapper om = new ObjectMapper();
        om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        return om;
    }
    
}
4.1.2 自定义转换器

当然开发者在这里也可以根据实际需求自定义JSON转换器。常见的JSON处理器除了jackson-databind之外,还有Gsonfastison,这里针对常见用法分别举例.

Gson

Gson是Google的一个开源JSON解析框架。使用Gson,需要先除去默认的jackson-databind,然后加入Gson依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-json</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>

由于Spring Boot中默认提供了Gson的自动转换类GsonHttpMessageConvertersConfiguration,因此Gson的依赖添加成功后,可以像使用jackson-databind那样直接使用Gson。但是在Gson进行·转换时,如果想对日期数据进行格式化,那么还需要开发者自定义HttpMessageConverter.自定义HttpMessageConverter可以通过如下方式。也可以直接使用Gson对象。

@Configuration
public class WebMvcConfig {
//    @Bean
//    GsonBuilder gsonBuilder() {
//        GsonBuilder gsonBuilder = new GsonBuilder();
//        gsonBuilder.setDateFormat("yyyy-MM-dd");
//        return gsonBuilder;
//    }
    @Bean
    GsonHttpMessageConverter gsonHttpMessageConverter() {
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.setDateFormat("yyyy-MM-dd");
        GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
        converter.setGson(gsonBuilder.create());
        return converter;
    }
}
fastison

fastjson是阿里巴巴的一个开源JSON解析框架,是目前JSON解析速度最快的开源框架,该框架也可以集成到Spring Boot中。不同于Gson, fastjson继承完成之后并不能立马使用,需要开发者提供相应的HttpMessageConverter后才能使用,集成fastison的步骤如下。

  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-json</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>
spring.http.encoding.force-response=true

对于FastlsonHttpMessageConverter的配置,除了FastJsonHttpMessageConverter这种方式之外,还有另一种方式。在Spring Boot项目中,当开发者引入spring-boo-starter-web依赖之后,该依赖又依赖了spring-boot-autoconfigure,在这个自动化配置中,有一个webMvcAutoConfiguration类提供了对Spring MVC最基本的配置,如果某一项自动化配置不满足开发需求,开发者可以针对该项自定义配置,只需要实现WebMveConfigurer接口即可(在Spring 5.0之前是通过继承WebMvcConfigurerAdapter类来实现的) ,代码如下:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setCharset(Charset.forName("UTF-8"));
        fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
        converter.setFastJsonConfig(fastJsonConfig);
        converter.setDefaultCharset(Charset.forName("UTF-8"));
        converters.add(converter);
    }

//    @Bean
//    FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
//        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
//        FastJsonConfig fastJsonConfig = new FastJsonConfig();
//        fastJsonConfig.setCharset(Charset.forName("UTF-8"));
//        fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
//        converter.setFastJsonConfig(fastJsonConfig);
//        converter.setDefaultCharset(Charset.forName("UTF-8"));
//        return converter;
//    }
}

4.2静态资源访问

Spring MVC中,对于静态资源都需要开发者手动配置静态资源过滤。Spring Boot中对此也提供了自动化配置,可以简化静态资源过滤配置。

Spring MVC中的配置:

xml

<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/css/**" location="/css/"/>
<mvc:resources mapping="/html/**" location="/html/"/>

由于这是一种Ant风格的路径匹配符,/** 表示可以匹配任意层级的路径,因此上面的代码也可以像下面这样简写:

<mvc:resources mapping="/**" location="/"/>

java:重写 WebMvcConfigurationSupport 类中的addResourceHandlers方法,在该方法中配置静态资源位置即可

4.2.1默认策略

Spring Boot中对于Spring MVC的自动化配置都在webMvcAutoConfiguration类中,因此对于默认的静态资源过滤策略可以从这个类中一窥究竟。在WebMvcAutoConfiguration类中有一个静态内部类webMvcAutoConfigurationAdapter,实现了4.1节提到的WebMvcConfigurer接口。webMvcConfigurer接口中有一个方法addResourceHandlers是用来配置静态资源过滤的。方法在WebMvcAutoConfigurationAdapter类中得到了实现,部分核心代码如下
在这里插入图片描述
Spring Boot在这里进行了默认的静态资源过滤配置,其中staticPathPattern默认定义在WebMvcProperties
在这里插入图片描述
registration.addResourceLocations(this.resourceProperties.getStaticLocations());获取到的默认静态资源位置定义在ResourceProperties
在这里插入图片描述

在一个新创建的Spring Boot项目中,添加了spring-boot-starter-web依赖之后,在resources目录下分别创建4个目录, 4个目录中放入同名的静态资源(如图4-4所示,数字表示不同位置资源的优先级)
在这里插入图片描述

4.2.2自定义策略

自定义静态资源过滤策略有以下两种方式;

  1. 在配置文件中定义可以在application.properties中直接定义过滤规则和静态资源位置,
# 静态资源位置
spring.web.resources.static-locations=classpath:/static/
# 过滤规则
spring.mvc.static-path-pattern=/static/**  

在这里插入图片描述

  1. Java编码定义也可以通过Java编码方式来定义,此时只需要实现WebMveConfigurer接口即可,然后实现该接口的addResourceHandlers方法,代码如下:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
    }
}

4.3文件上传.

Spring MVC对文件上传做了简化,在Spring Boot中对此做了更进一步的简化,文件上传更为方便。Java中的文件上传一共涉及两个组件,一个是CommonsMultipartResolver,另一个是StandardServletMultipartResolver.
其中CommonsMultipartResolver使用commons-fileupload来处理multipart请求,而StandardServletMultipartResolver则是基于Servlet 3.0来处理multipart请求的,因此若使用StandardServletMultipartResolver,则不需要添加额外的jar包。Tomcat 7.0开始就支持Servlet3.0.

Spring Boot提供的文件上传自动化配置类MultiparAutoConfiguraton中,默认也是采用StandardServletMultipartResolver
在这里插入图片描述

spring.servlet.multipart.max-file-size=1KB
.....
4.3.1单文件上传
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <input type="submit" value="上传">
</form>
</body>
</html>
 DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("/yyyy/MM/dd/");
    @PostMapping("/upload")
    public String upload(MultipartFile file, HttpServletRequest req) {
        String realPath = req.getServletContext().getRealPath("/");
        String format = LocalDate.now().format(dateTimeFormatter);
        String path = realPath + format;
        File folder = new File(path);
        if (!folder.exists()) {
            folder.mkdirs();
        }
        String oldName = file.getOriginalFilename();
        String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));
        try {
            file.transferTo(new File(folder, newName));
            String s = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + format + newName;
            return s;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "error";
    }
4.3.2多文件上传
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/upload2" method="post" enctype="multipart/form-data">
    <input type="file" name="files" multiple>
    <input type="submit" value="上传">
</form>
</body>
</html>
 @PostMapping("/upload2")
    public void upload(MultipartFile[] files, HttpServletRequest req) {
        String realPath = req.getServletContext().getRealPath("/");
        String format = LocalDate.now().format(dateTimeFormatter);
        String path = realPath + format;
        File folder = new File(path);
        if (!folder.exists()) {
            folder.mkdirs();
        }
        try {
            for (MultipartFile file : files) {
                String oldName = file.getOriginalFilename();
                String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));
                file.transferTo(new File(folder, newName));
                String s = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + format + newName;
                System.out.println("s = " + s);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/upload3" method="post" enctype="multipart/form-data">
    <input type="file" name="file1">
    <input type="file" name="file2">
    <input type="submit" value="上传">
</form>
</body>
</html>
@PostMapping("/upload3")
    public void upload(MultipartFile file1, MultipartFile file2, HttpServletRequest req) {
        String realPath = req.getServletContext().getRealPath("/");
        String format = LocalDate.now().format(dateTimeFormatter);
        String path = realPath + format;
        File folder = new File(path);
        if (!folder.exists()) {
            folder.mkdirs();
        }
        try {
            String oldName1 = file1.getOriginalFilename();
            String newName1 = UUID.randomUUID().toString() + oldName1.substring(oldName1.lastIndexOf("."));
            file1.transferTo(new File(folder, newName1));
            String s1 = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + format + newName1;
            System.out.println("s1 = " + s1);

            String oldName2 = file2.getOriginalFilename();
            String newName2 = UUID.randomUUID().toString() + oldName2.substring(oldName2.lastIndexOf("."));
            file2.transferTo(new File(folder, newName2));
            String s2 = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + format + newName2;
            System.out.println("s2 = " + s2);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
4.3.3AJAX文件上传
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://code.jquery.com/jquery-3.5.1.js" integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc=" crossorigin="anonymous"></script>
</head>
<body>
<div id="result"></div>
<input type="file" id="file">
<input type="button" value="上传" onclick="uploadFile()">
<script>
    function uploadFile() {
        var file = $("#file")[0].files[0];
        var formData = new FormData();
        formData.append("file", file);
        formData.append("username", "javaboy");
        $.ajax({
            type:'post',
            url:'/upload',
            processData:false,
            contentType:false,
            data:formData,
            success:function (msg) {
                $("#result").html(msg);
            }
        })
    }
</script>
</body>
</html>

4.4 @ControllerAdvice

顾名思义, @ControllerAdvice就是@Controller的增强版。@ControllerAdvice主要用来处理全局数据,一般搭配@ExceptionHandier. @ModelAttribute以及@InitBinder使用。

4.4.1 全局异常处理

上传文件大小超出限制。

@ControllerAdvice
//@Controller
//@RestControllerAdvice
//@RestController
public class MyGlobalException {
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public ModelAndView customException(MaxUploadSizeExceededException e) {
        ModelAndView mv = new ModelAndView("javaboy");
        mv.addObject("error", e.getMessage());
        return mv;
    }
}
4.4.2 添加全局数据

@ControllerAdvice是一个全局数据处理组件,因此也可以在@ControllerAdvice中配置全局数据,使用@ModelAtribute注解进行配置,代码如下:

@ControllerAdvice
public class MyGlobalData {
    @ModelAttribute("info")
    public Map<String,String> mydata() {
        Map<String, String> info = new HashMap<>();
        info.put("username", "javaboy");
        info.put("address", "www.javaboy.org");
        return info;
    }
 }   

在全局配置中添加mydata方法,返回一个map.该方法有一个注解@ModelAttribute,其中的value属性表示这条返回数据的key,而方法的返回值是返回数据的value,此时在任意请求的Controller中,通过方法参数中的Model都可以获取info的数据。

4.4.3 请求参数预处理

@ControllerAdvice结合@InitBinder还能实现请求参数预处理,即将表单中的数据绑定到实体类上时进行一些额外处理。
多个实体类存在相同的字段时,会合并字段值,使用ControllerAdvice来做预处理。

@ControllerAdvice
public class MyGlobalData {
 
    @InitBinder("b")
    public void b(WebDataBinder binder) {
        binder.setFieldDefaultPrefix("b.");
    }
    @InitBinder("a")
    public void a(WebDataBinder binder) {
        binder.setFieldDefaultPrefix("a.");
    }

}
@RestController
public class BookController {

    @PostMapping("/book")
    public void addBook(@ModelAttribute("b") Book book, @ModelAttribute("a") Author author) {
        System.out.println("book = " + book);
        System.out.println("author = " + author);
    }
}

在GlobalConfig类中创建两个方法,

  • 第一个@InitBinder(“b”)表示该方法是处理@ModelAttribute(")对应的参数的,
  • 第二个@nitBinder(“a”)表示该方法是处理@ModelAttribute(“a”)对应的参数的。

在WebDataBinder对象中,还可以设置允许的字段、禁止的字段、必填字段以及验证器等。

4.5 自定义错误页

Spring Boot中的全局异常处理。在处理异常时,开发者可以根据实际情况返回不同的页面,但是这种异常处理方式一般用来处理应用级别的异常,有一些容器级别的错误就处理不了,例如Filter 中抛出异常,使用@ControllerAdvice定义的全局异常处理机制就无法处理

因此, Spring Boot中对于异常的处理还有另外的方式,这就是本节要介绍的内容。在Spring Boot 中,默认情况下,如果用户在发起请求时发生了404错误, Spring Boot会有一个默认的页面展示给用户.
在这里插入图片描述
Spring Boot中的错误默认是由BasicErrorController类来处理的,该类中的核心方法主要有两个:
在这里插入图片描述
·errorHtml方法用来返回错误HTML页面, error用来返回错误JSON,具体返回的是HTML还是JSON,则要看请求头的Accept参数。返回JSON的逻辑很简单,不必过多介绍,返回HTML的逻辑稍微有些复杂,在errorHtml方法中,通过调用resolveErrorView方法来获取一个错误视图的ModelAndView,而resolveErrorView方法的调用最终会来到DefaultErrorViewResolver类中。DefaultErrorViewResolver类是Spring Boot中默认的错误信息视图解析器,部分源码如下:
在这里插入图片描述

4.5.1 简单配置.静态页面

自定义错误页面其实很简单,提供4xx和Sxx页面即可。如果开发者不需要向用户展示详细的错误信息,那么可以把错误信息定义成静态页面,直接,在resources/static 录下创建error目录,然后在error目录中创建错误展示页面。错误展示页面的命名规则有两种:
在这里插入图片描述

  • 一种是4xx.html5xx.html;
  • 另一种是直接使用响应码命名文件,例如404.html.405.html, 500.html.第二种命名方式划分得更细,当出错时,不同的错误会展示不同的错误页面
.模板页面

Spring Boot在这里一共返回了5条错误相关的信息,分别是timestamp, status, error, message以及path
在这里插入图片描述
若用户定义了多个错误页面,则响应码html页面的优先级4xx.html. Sxx.tml页面的优先级,即若当前是一个404错误,则优先展示404.html而不是4xx.html;动态页面的优级静态页面,即若resources/templatesresource/static 同时定义了4xx.html,则优先展示resources/templates/4xx.html.

4.5.2 复杂配置

上面这种配置还是不够灵活,只能定义HTML页面,无法处理JSON的定制。Spring Boot中支持对Error信息的深度定制,接下来将从三个方面介绍深度定制:自定义Error数据自定义Error视图以及完全自定义

1.自定义Error数据

自定义Error数据就是对返回的数据进行自定义。Spring Boot返回的Error信息一共有5条,分别是timestamp, status, error, message以及path,在BasicErrorController的errorHtml方法和error方法中,都是通过getErrorAttributes方法获取Error信息的。该方法最终会调用到DefaultErrorAttributes类getErrorAttributes方法,而DefaultErrorAttributes类是在ErrorMvcAutoConfiguration中默认提供的.ErrorMvcAutoConfiguration类的errorAttributes方法源码如下:
在这里插入图片描述
源码中可以看出,当系统没有提供ErrorAttributes时才会采用DefaultErrorAttributes.因此自定义错误提示时,只需要自己提供一个ErrorAttributes 可,而DefaultErroAttributes是ErrorAttributes的子类,因此只需要继承DefaultErrorAttributes即可

@Component
public class MyErrorAtributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> map = super.getErrorAttributes(webRequest, options);
        if ((Integer) map.get("status") == 404) {
            map.put("message", "页面不存在");
        }
        return map;
    }
}
2,自定义Error视图

Error视图是展示给用户的页面,在BasicErrorControllererrorHtml方法中调用resolveErrorView方法获取一个ModelAndView实例。 resolveErrorView方法是由ErrorViewResolver提供的,通过ErrorMvcAutoConfiguration类的源码可以看到Spring Boot默认采用的ErrorViewResolver是DefaultErrorViewResolver. ErrorMvcAutoConfiguration部分源码如下:
在这里插入图片描述

@Component
public class MyErrorViewResolver extends DefaultErrorViewResolver {
    public MyErrorViewResolver(ApplicationContext applicationContext, WebProperties.Resources resources) {
        super(applicationContext, resources);
    }

    @Override
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        Map<String, Object> map = new HashMap<>();
        map.putAll(model);
        if ((Integer) model.get("status") == 500) {
            map.put("message", "服务器内部错误");
        }
        ModelAndView view = new ModelAndView("javaboy/999",map);
        return view;
    }
}
3,完全自定义

前面提到的两种自定义方式都是对BasicErrorController类中的某个环节进行修补。查看Error自动化配置类ErrorMvcAutoConfiguration,读者可以发现BasicErrorController 身只是一个默认的配置,相关源码如下:
在这里插入图片描述
从这段源码中可以看到,若开发者没有提供自己的ErrorController,则Spring Boot提供BasicErrorController作为默认的ErrorController,因此,如果开发者需要更加灵活地对Error视图数据进行处理,那么只需要提供自己的ErrorController即可。提供自己的ErrorController有两种方式:一种是实现ErrorController接口,另一种是直接继承BasicErrorController,由于ErorController接口只提供一个待实现的方法,而BasicErrorController已经实现了很多功能,因此这里选择第二种方式,即通过继承BasicErrorController来实现自己的ErrorController.具体定义如下:

@Controller
public class MyErrorController extends BasicErrorController {
    @Autowired
    public MyErrorController(ErrorAttributes errorAttributes,
                             ServerProperties serverProperties,
                             List<ErrorViewResolver> errorViewResolvers) {
        super(errorAttributes, serverProperties.getError(), errorViewResolvers);
    }
    @Override
    public ModelAndView errorHtml(HttpServletRequest request,
                                  HttpServletResponse response) {
        HttpStatus status = getStatus(request);
        Map<String, Object> model = getErrorAttributes(
                request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
        model.put("custommsg", "出错啦!");
        ModelAndView modelAndView = new ModelAndView("myErrorPage", model, status);
        return modelAndView;
    }
    @Override
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.ALL));
        body.put("custommsg", "出错啦!");
        HttpStatus status = getStatus(request);
        return new ResponseEntity<>(body, status);
    }
}
  • 自定义MyErrorController继承自BasicErrorController并添加@Controller注解,将MyErrorController 注册Spring MVC容器中·
  • 由于BasicErrorController没有无参构造方法,因此在创建BasicErrorController实例时需要传递参数,在MyErrorController的构造方法上添加@Autowired注解注入所需参数
  • 参考BasicErrorController中的实现,重写errorHtml和error方法,对Error的视图和数据进行充分的自定义。

4.6 CORS支持

CORS (Cross-Origin Resource Sharing)是由w3C制定的一种跨域资源共享技术标准,其目的就是为了解决前端的跨域请求。在Java EE开发中,最常见的前端跨域请求解决方案是JSONP,但是JSONP只支持GET请求,这是一个很大的缺陷,而CORS则支持多种HTTP请求方法。以CORS
在这里插入图片描述

响应头中有一个Access-Control-Allow-Origin字段,用来记录可以访问该资源的域。当浏览器收到这样的响应头信息之后,提取出Access-Control-Allow-Origin字段中的值,发现该值包含当前页面所在的,就知道这个跨域是被允许的,因此就不再对前端的跨域请求进行·限制。这就是GET请求的整个跨域流程,在这个过程中,前端请求的代码不需要修改,主.要是后端进行处理。这个流程主要是针对GET, POST以及HEAD请求,并且没有自定义请求头,如果用户发起一个DELETE请求、PUT请求或者自定义了请求头,流程就会稍微复杂一些。

DELETE请求为例,当前端发起一个DELETE请求时,这个请求的处理会经过两个步骤
第一步:发送一个OPTIONS请求。代码如下:
在这里插入图片描述
这个请求将向服务端询问是否具备该资源的DELETE权限,服务端会给浏览器一个响应,代码如下:
在这里插入图片描述
服务端给浏览器的响应, Allow头信息表示服务端支持的请求方法,这个请求相当于一个探测请求,当浏览器分析了请求头字段之后,知道服务端支持本次请求,则进入第二步。
第二步:发送DELETE请求。接下来浏览器就会发送一个跨域的DELETE请求。

在传统的Java EE开发中,可以通过过滤器统一配置,而Spring Boot中对此则提供了更加简洁的解决方案。在Spring Boot中配置CORS的步骤如下:

3.配置跨域

跨域有两个地方可以配置:

  1. 一个是直接在相应的请求方法上加注解: 这种配置方式是一种细粒度的配置.可以控制到每一个方法上。
  • @CrossOrigin中的value表示支持的,这里表示来自http://ocalhost:8081域的请求是支持跨域的.
  • maxAge表示探测请求有效期,在前面的讲解中,读者已经了解到对于DELETE, PUT请求或者有自定义头信息的请求,在执行过程中会先发送探测请求,探测请求不用每次都发送,可以配置一个有效期,有效期过了之后才会发送探测请求。这个属性默认是1800秒,即30分钟。
  • allowedHeaders表示允许的请求头,*表示所有的请求头都被允许
@RestController
@RequestMapping("/book")
public class BookController {
    @PostMapping("/")
    @CrossOrigin(value = "http://localhost:8081"
            ,maxAge = 1800,allowedHeaders = "*")
    public String addBook(String name) {
        return "receive:" + name;
    }
    @DeleteMapping("/{id}")
    @CrossOrigin(value = "http://localhost:8081"
            ,maxAge = 1800,allowedHeaders = "*")
    public String deleteBookById(@PathVariable Long id) {
        return String.valueOf(id);
    }
}
  1. 另一种全局配置,代码如下:
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedHeaders("*")
                .allowedMethods("*")
                .allowedOrigins("http://localhost:8081")
                .maxAge(1800);
    }

全局配置需要自定义类实现WebMvcConfigurer接口,然后实现接口中的addCorsMappings方法。,在addCorsMappings方法中

  • addMapping表示对哪种格式的请求路径进行跨域处理;
  • allowedHeaders表示允许的请求头,默认允许所有的请求头信息;
  • allowedMethods表示允许的请求方法,默认是GET. POST和HEAD,*表示支持所有的请求方法;
  • maxAge表示探测请求的有效期;
  • allowedOrigins表示支持的域。

4.7配置类与XML配置.

Spring Boot推荐使用Java来完成相关的配置工作。在项目中,不建议将所有的配置放在一个配置类中,可以根据不同的需求提供不同的配置类,例如专门处理Spring Security的配置类提供Bean的配置类Spring MVC相关的配置类。这些配置类上都需要添加@Configuration注解
@ComponentScan注解会扫描所有的Spring组件,也包括@Configuration@ComponentScan注解在项目入口类的@Spring BootApplication注解中已经提供,因此在实际项目中只需要按需提供相关配置类即可。Spring Boot中并不推荐使用XML配置,建议尽量用Java配置代替XML配置,本书中的案例都是以Java配置为主。

如果开发者需要使用XML配置,只需在resources目录下提供配置文件,然后通过@ImportResource加载配置文件即可。例如,有一个Book类如下:
在这里插入图片描述

4.8注册拦截器

Spring MVC中提供了AOP风格的拦截器,拥有更加精细的拦截处理能力。Spring Boot中拦.截器的注册更加方便,步骤如下:

创建拦截器实现Handlerinterceptor接口
ublic class MyInterceptor implements HandlerInterceptor {

    //该方法返回 false,请求将不再继续往下走
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle");
        return true;
    }

    //Controller 执行之后被调用
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }

    //preHandle 方法返回 true,afterCompletion 才会执行。
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
}

拦截器中的方法将按preHandle-Controller-postHandle-afterCompletion的顺序执行。注意,只有preHandle方法返回true时后面的方法才会执行。当拦截器链内存在多个拦截器时, postHandler在拦截器链内的所有拦截器返回成功时才会调用,而afterCompletion只有preHandle返回true才调用,但若拦截器链内的第一个拦截器的preHandle方法返回false,则后面的方法都不会执行。

配置拦截器。定义配置类进行拦截器的配置,代码如下:

自定义类实现webMveConfigurer接口,实现接口中的addInterceptors方法。其中,addPathPatterns 表示拦截路径, excludePathPatterns表示排除的路径

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns("/hello");
    }
}

4.9启动系统任务

有一些特殊的任务需要在系统启动时执行,例如配置文件加载数据库初始化等操作。如果没有使用Spring Boot,这些问题可以在Listener中解决。Spring Boot对此提供了两种解决方案:CommandLineRunnerApplicationRunner. CommandLineRunnerApplicationRunner基本一致,差别主要体现在参数上。

4.9.1 CommandLineRunner

Spring Boot项目在启动时会遍历所有CommandLineRunner的实现类并调用其中的run方法,如果整个系统中有多个CommandLineRunner的实现类,那么可以使用@Order注解对这些实现类的调用顺序进行排序。
在这里插入图片描述

4.9.2 ApplicationRunner

ApplicationRunner的用法和CommandLineRunner基本一致,区别主要体现在run方法的参数上。

@Component
@Order(98)
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        //获取没有键的参数,获取到的值和 commandlinerunner 一致
        List<String> nonOptionArgs = args.getNonOptionArgs();
        System.out.println("nonOptionArgs1 = " + nonOptionArgs);
        Set<String> optionNames = args.getOptionNames();
        for (String optionName : optionNames) {
            System.out.println(optionName + "-1->" + args.getOptionValues(optionName));
        }
        //获取命令行中的所有参数
        String[] sourceArgs = args.getSourceArgs();
        System.out.println("sourceArgs1 = " + Arrays.toString(sourceArgs));
    }
}
@Component
@Order(97)
public class MyApplicationRunner2 implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        //获取没有键的参数,获取到的值和 commandlinerunner 一致
        List<String> nonOptionArgs = args.getNonOptionArgs();
        System.out.println("nonOptionArgs2 = " + nonOptionArgs);
        Set<String> optionNames = args.getOptionNames();
        for (String optionName : optionNames) {
            System.out.println(optionName + "-2->" + args.getOptionValues(optionName));
        }
        //获取命令行中的所有参数
        String[] sourceArgs = args.getSourceArgs();
        System.out.println("sourceArgs2 = " + Arrays.toString(sourceArgs));
    }
}

@Order注解依然是用来描述执行顺序的,数字越小越优先执行。不同于CommandLineRunner中run方法的String数组参数,这里run方法的参数是一个ApplicationArguments对象,如果想从ApplicationArguments对象中获取入口类中1main方法1接收的参数,调用ApplicationArguments中的getNonOptionArgs方法即可. ApplicationArguments中的getOptionNames方法用来获取项目启动命令行中参数的key,例如将本项目打成jar包,运行java-jar xxx.jar-name-Michael命令来启动项目,此时getOptionNames方法获取到的就是name,而getOptionValues方法则是获取相应的value.

4.10整合Servlet, Filter和Listener.

Spring Boot中对于整合这些基本的Web组件也提供了很好的支持。在一个Spring Boot Web项目中添加如下三个组件:

@WebFilter("/*")
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig){
        System.out.println("MyFilter>>>init");
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("MyFilter>>>doFilter");
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {
        System.out.println("MyFilter>>>destroy");
    }
}
@WebListener
public class MyListener implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("MyListener>>>requestDestroyed");
    }
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("MyListener>>>requestInitialized");
    }
}
@WebServlet("/my")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp){
        doPost(req,resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp){
        System.out.println("name>>>"+req.getParameter("name"));
    }
}

启动类需要的配置:
在项目入口类上添加@ServletComponentScan注解,实现对ServletFilter以及Listener的扫描,代码如下:

@SpringBootApplication
@ServletComponentScan("org.javaboy.filter")
public class FilterApplication {

    public static void main(String[] args) {
        SpringApplication.run(FilterApplication.class, args);
    }

}

4.11 路径映射.

有一些页面在控制器中不需要加载数据,只是完成·简单的跳转,对于这种页面,可以直接配置路径映射,提高访问速度。例如,有两个Thymeleaf做模板的页面login.htmlindex.tml,直接在MVC配置中重写addViewControllers方法配置映射关系即可:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
//        registry.addViewController("/02").setViewName("02");
        registry.addViewController("/02").setViewName("02");
    }
}

4.12 配置AOP

4.12.1 AOP简介
AOP中的相关知识
4.12.2 Spring Boot支持

Spring BootSpring的基础上对AOP的配置提供了自动化配置解决方案spring-boot-starter-aop,使开发者能够更加便捷地在Spring Boot项目中使用AOP,配置步骤如下。

 		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
@Component
@Aspect
public class LogAspect {
    @Pointcut("execution(* org.javaboy.aop.service.*.*(..))")
    public void pc1() {

    }

    @Before("pc1()")
    public void before(JoinPoint jp) {
        String name = jp.getSignature().getName();
        System.out.println(name + " 方法开始执行了...");
    }

    @After("pc1()")
    public void after(JoinPoint jp) {
        String name = jp.getSignature().getName();
        System.out.println(name + " 方法执行结束了...");
    }

    @AfterReturning(value = "pc1()", returning = "s")
    public void afterReturning(JoinPoint jp, String s) {
        String name = jp.getSignature().getName();
        System.out.println(name + " 方法的返回值是 " + s);
    }

    @AfterThrowing(value = "pc1()", throwing = "e")
    public void afterThrowing(JoinPoint jp, Exception e) {
        String name = jp.getSignature().getName();
        System.out.println(name + " 方法抛出了异常 " + e.getMessage());
    }

    @Around("pc1()")
    public Object around(ProceedingJoinPoint pjp) {
        try {

            //类似于反射中的 invoke 方法
            Object proceed = pjp.proceed();
            return proceed;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }
}

4.13 其他

4.13.1 自定义欢迎页

Spring Boot项目在启动后,首先会去静态资源路径下查找index.html作为首页文件,若查找不到,则会去查找动态的index文件作为首页文件.

  • 使用静态的index.html页面作为项目首页,那么只需在resources/static目录下创建index.html文件即可。
  • 若想使用动态页面作为项目首页,则需在resources/templates目录下创建index.html(使用Thymeleaf模板)或者index.fl (使用FreeMarker模板) ,然后在Controller中返回逻辑视图名,代码如下:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/index").setViewName("index");

    }
}
4.13.2 自定义favicon

favicon.ico是浏览器选项卡左上角的图标,可以放在静态资源路径下或者类路径下,静态资源路径下的favicon.ico优先级高于类路径下的favicon.ico
在线转换网站http:/inaconvert.com/cn/convert-to-ico.php将一张普通图片转为.ico图

4.13.3 除去某个自动配置

Spring Boot中提供了大量的自动化配置类,例如上文提到过的ErrorMvcAutoConfigurationThymeleafAutoConfiguration,FreeMarkerAutoConfiguration, MultipartAutoConfiguration等,这些自动化配置可以减少相应操作的配置,达到开箱即用的效果。在Spring Boot的入口类上有一个@Spring BootApplication注解。该注解是一个组合注解, 由@Spring BootConfiguration@EnableAutoConfiguration以及@ComponentScan组成,其中@EnableAutoConfiguration注解开启自动化配置,相关的自动化配置类就会被使用。如果开发者不想使用某个自动化配置,按如下方式除去相关配置即可.

@SpringBootApplication
@EnableAutoConfiguration(exclude = {ErrorMvcAutoConfiguration.class})
public class OtherApplication {
	public static void main(String[] args) {
		SpringApplication.run(OtherApplication.class, args);
	}
}
4.13.4 使用类型转化器
@Component
public class MyDateConverter implements Converter<String, Date> {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    @Override
    public Date convert(String source) {
        try {
            return sdf.parse(source);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
}

第5章Spring Boot整合持久层技术.

Spring Boot中对常见的持久层框架都提供了自动化配置,例如JabcTemplate, JPA等, MyBatis自动化配置则是MyBatis官方提供的。

5.1 整合JdbcTemplate

JdbcTemplateSpring提供的一套JDBC模板框架,利用AOP技术来解决直接使用JDBC时大量重复代码的问题。JdbcTemplate虽然没有MyBatis那么灵活,但是比直接使用JDBC要方便很多。Spring Boot中对JdbcTemplate的使用提供了自动化配置类JdbcTemplateConfiguration,部分源码如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(JdbcProperties.class)
@Import({ DatabaseInitializationDependencyConfigurer.class, JdbcTemplateConfiguration.class,
		NamedParameterJdbcTemplateConfiguration.class })
public class JdbcTemplateAutoConfiguration {

}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(JdbcOperations.class)
class JdbcTemplateConfiguration {

	@Bean
	@Primary
	JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties) {
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
		JdbcProperties.Template template = properties.getTemplate();
		jdbcTemplate.setFetchSize(template.getFetchSize());
		jdbcTemplate.setMaxRows(template.getMaxRows());
		if (template.getQueryTimeout() != null) {
			jdbcTemplate.setQueryTimeout((int) template.getQueryTimeout().getSeconds());
		}
		return jdbcTemplate;
	}
}

需要的依赖:

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.1.10</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

spring-bool-starter-jdbc 中提供了spring-jdbc,另外还加入了数据库驱动依赖数据库连接池依赖

# 数据源1
spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.one.username=root
spring.datasource.one.password=123
spring.datasource.one.url=jdbc:mysql:///chapter05-1
  • 创建BookDao,注入JdbcTemplate.由于已经添加了spring-jdbc相关的依赖, JabcTemplate会被自动注册Spring容器中,因此这里可以直接注入JdbcTemplate使用。
  • JdbcTemplate中,增删改三种类型的操作主要使用updatebatchUpdate方法来完成.queryqueryForObject方法主要用来完成查询功能。另外,还有execute方法可以用来执行任意的sQL. call方法用来调用存储过程等。
  • 在执行查询操作时,需要有一个RowMapper将查询出来的列和实体类中的属性-一对应起来。如果列名属性名都是相同的,那么可以直接使用BeanPropertyRowMapper;如果列名和属性名不同,就需要开发者自己实现RowMapper接口,将列和实体类属性-一对应起来。
@Repository
public class BookDao {
    @Autowired
    JdbcTemplate jdbcTemplate;
    public int addBook(Book book) {
        return jdbcTemplate.update("INSERT INTO book(name,author) VALUES (?,?)",
                book.getName(), book.getAuthor());
    }
    public int updateBook(Book book) {
        return jdbcTemplate.update("UPDATE book SET name=?,author=? WHERE id=?",
                book.getName(), book.getAuthor(), book.getId());
    }
    public int deleteBookById(Integer id) {
        return jdbcTemplate.update("DELETE FROM book WHERE id=?", id);
    }
    public Book getBookById(Integer id) {
        return jdbcTemplate.queryForObject("select * from book where id=?",
                new BeanPropertyRowMapper<>(Book.class), id);
    }
    public List<Book> getAllBooks() {
        return jdbcTemplate.query("select * from book",
                new BeanPropertyRowMapper<>(Book.class));
    }
}
 public int addUser2(User user) {
        GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
        int result = jdbcTemplate.update(new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
                PreparedStatement ps = connection.prepareStatement("insert into user (username,address) values(?,?)", Statement.RETURN_GENERATED_KEYS);
                ps.setString(1, user.getUsername());
                ps.setString(2, user.getAddress());
                return ps;
            }
        }, keyHolder);
        user.setId(keyHolder.getKey().longValue());
        return result;
    }
public List<User> getAllUsers() {
        List<User> list = jdbcTemplate.query("select * from user", new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet resultSet, int i) throws SQLException {
                String username = resultSet.getString("username");
                String address = resultSet.getString("address");
                long id = resultSet.getLong("id");
                User user = new User();
                user.setId(id);
                user.setUsername(username);
                user.setAddress(address);
                return user;
            }
        });
        return list;
    }

5.2整合MyBatis

MyBatis是一款优秀的持久层框架,原名叫作iBaits, 2010年由ApacheSoftwareFoundation迁移到Google Code并改名为MyBatis, 2013年又迁移到GitHub上。MyBatis支持定制化SQL存储过程以及高级映射MyBatis几乎避免了所有的JDBC代码手动设置参数以及获取结果集。在传统的SSM框架整合中,使用MyBatis需要大量的XML配置,而在Spring Boot中, MyBatis官方提供了一套自动化配置方案,可以做到MyBatis开箱即用。具体使用步骤如下。

  • 添加MyBatis依赖、数据库驱动依赖以及数据库连接池
       <dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.2</version>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.9</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql:///chapter05
spring.datasource.username=root
spring.datasource.password=123
方法一
@SpringBootApplication
@MapperScan(basePackages = "org.javaboy.mybatis.mapper")
public class MybatisApplication {

    public static void main(String[] args) {
        SpringApplication.run(MybatisApplication.class, args);
    }

}
  • 一种简单的方式是在配置类上添加@MapperScan("org.sang.mapper")注解,表示扫描org.sang.mapper包下的所有接口作为Mapper,这样就不需要在每个接口上配置@Mapper注解了。
public interface UserMapper {
    @Select("select * from user where id=#{id}")
    User getUserById(Long id);

    @Results({
            @Result(property = "address",column = "address1")
    })
    @Select("select * from user")
    List<User> getAllUsers();

    @Insert("insert into user (username,address1) values (#{username},#{address})")
    @SelectKey(statement = "select last_insert_id()",keyProperty = "id",before = false,resultType = Long.class)
    Integer addUser(User user);

    @Delete("delete from user where id=#{id}")
    Integer deleteById(Long id);

    @Update("update user set username=#{username} where id=#{id}")
    Integer updateById(String username, Long id);
}
方法二

指明该类是一个Mapper:第一种如前面的代码所示,在BookMapper上添加@Mapper注解,表明该接口是一个MyBatis中的Mapper,这种方式需要在每一个Mapper上都添加注解;

@Mapper
public interface BookMapper {
    int addBook(Book book);
    int deleteBookById(Integer id);
    int updateBookById(Book book);
    Book getBookById(Integer id);
    List<Book> getAllBooks();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.sang.mapper.BookMapper">
    <insert id="addBook" parameterType="org.sang.model.Book">
    INSERT INTO book(name,author) VALUES (#{name},#{author})
    </insert>
    <delete id="deleteBookById" parameterType="int">
        DELETE FROM book WHERE id=#{id}
    </delete>
    <update id="updateBookById" parameterType="org.sang.model.Book">
        UPDATE book set name=#{name},author=#{author} WHERE id=#{id}
    </update>
    <select id="getBookById" parameterType="int" resultType="org.sang.model.Book">
        SELECT * FROM book WHERE id=#{id}
    </select>
    <select id="getAllBooks" resultType="org.sang.model.Book">
        SELECT * FROM book
    </select>
</mapper>
  • 针对BookMapper接口中的每一个方法都在BookMapper.xml中列出了实现
  • #{}用来代替接口中的参数,实体类中的属性可以直接通过#(实体类属性名}获取。
配置pom.xml文件

Maven工程中, XML配置文件建议写在resources目录下(同包同级目录),当Mapper.xml文件写在包下, ·Maven在运行时会忽略包下的XML文件,因此需要在pom.xml文件中重新指明资源文件位置,配置如下:

<build>
		<resources>
			<resource>
				<directory>src/main/java</directory>
				<includes>
					<include>**/*.xml</include>
				</includes>
			</resource>
			<resource>
				<directory>src/main/resources</directory>
			</resource>
		</resources>
		......
</build>		

也可以自定义resourcesmapper位置

mybatis.mapper-locations=classpath:mapper/*.xml

5.3整合Spring Data JPA

JPA (Java Persistence API)Spring Data是两个范畴的概念。JPA则是一种ORM规范, JPAHibernate的关系就像JDBCJDBC驱动的关系,即JPA制定了ORM规范,而Hibernate是这些规范的实现(事实上,是先有Hibernate后有JPA, JPA规范的起草者也是Hibernate的作者) ,因此从功能上来说, JPA相当于Hibernate的一个子集。

Spring DataSpring的一个子项目,致力于简化数据库访问,通过规范的方法名称来分析开发者的意图,进而减少数据库访问层的代码量。Spring Data不仅支持关系型数据库,也支持非关系型数据库Spring Data JPA可以有效 简化关系型数据库访问代码。Spring Boot整合Spring Data JPA的步骤如下:

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.9</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql:///jpa
spring.datasource.username=root
spring.datasource.password=123
spring.jpa.show-sql=true
spring.jpa.database=mysql
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57InnoDBDialect
#spring.jpa.properties.database=mysql
#spring.jpa.properties.hibernate.hbm2ddl.auto=update
#spring.jpa.properties.show-sql= true
  • @Entity注解表示该类是一个实体类,在项目启动时会根据该类自动生成一张表,表的名称即@Entity注解中name的值,如果不配置name,默认表名为类名。所有的实体类都要有主键,
  • @ld注解表示该属性是一个主键, @GeneratedValue注解表示主键自动生成, strategy则表示主键的生成策略。默认情况下,生成的表中字段的名称就是实体类中属性的名称,通过@Column注解可以定制生成的字段的属性, name表示该属性对应的数据表中字段的名称, nullable表示该字段非空。
  • @Transient注解表示在生成数据库中的表时,该属性被忽略,即不生成对应的字段。
@Entity(name = "t_book")
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column(name = "book_name",nullable = false)
    private String name;
    private String author;
    private Float price;
    @Transient
    private String description;
    //省略getter/setter
}   
  • 自定义BookDao继承自JpaRepository. JpaRepository中提供了一些基本的数据操作方法,有基本的增删改查、分页查询、排序查询等。
  • 2行定义的方法表示查询以某个字符开始的所有书。·
  • 3行定义的方法表示查询单价大于某个值的所有书。
  • Spring Data JPA中,只要方法的定义符合既定规范, Spring Data就能分析出开发者的意图,从而避免开发者定义SQL所谓的既定规范,就是一定的方法命名规则
public interface BookDao extends JpaRepository<Book,Integer>{
    List<Book> getBooksByAuthorStartingWith(String author);
    List<Book> getBooksByPriceGreaterThan(Float price);
    @Query(value = "select * from t_book where id=(select max(id) from t_book)",nativeQuery = true)
    Book getMaxIdBook();
    @Query("select b from t_book b where b.id>:id and b.author=:author")
    List<Book> getBookByIdAndAuthor(@Param("author") String author, @Param("id") Integer id);
    @Query("select b from t_book b where b.id<?2 and b.name like %?1%")
    List<Book> getBooksByIdAndName(String name, Integer id);
}
public interface BookDao extends JpaRepository<Book,Long> {
    List<Book> getBookByAuthorIs(String author);

    @Query(nativeQuery = true,value = "select * from t_book where id=(select max(id) from t_book)")
    Book maxIdBook();

    @Query("update t_book set b_name=:name where id=:id")
    @Modifying
    void updateBookById(String name, Long id);
}

支持的命名规则如表所示:
在这里插入图片描述
部分方法直接由JpaRepository

@Service
public class BookService {
    @Autowired
    BookDao bookDao;
    public void addBook(Book book) {
        bookDao.save(book);
    }
    public Page<Book> getBookByPage(Pageable pageable) {
        return bookDao.findAll(pageable);
    }
    public List<Book> getBooksByAuthorStartingWith(String author){
        return bookDao.getBooksByAuthorStartingWith(author);
    }
    public List<Book> getBooksByPriceGreaterThan(Float price){
        return bookDao.getBooksByPriceGreaterThan(price);
    }
    public Book getMaxIdBook(){
        return bookDao.getMaxIdBook();
    }
    public List<Book> getBookByIdAndAuthor(String author, Integer id){
        return bookDao.getBookByIdAndAuthor(author, id);
    }
    public List<Book> getBooksByIdAndName(String name, Integer id){
        return bookDao.getBooksByIdAndName(name, id);
    }
}
 @GetMapping("/findAll")
    public void findAll() {
        PageRequest pageable = PageRequest.of(2, 3);
        Page<Book> page = bookService.getBookByPage(pageable);
        System.out.println("总页数:"+page.getTotalPages());
        System.out.println("总记录数:"+page.getTotalElements());
        System.out.println("查询结果:"+page.getContent());
        System.out.println("当前页数:"+(page.getNumber()+1));
        System.out.println("当前页记录数:"+page.getNumberOfElements());
        System.out.println("每页记录数:"+page.getSize());
    }

5.4多数据源.

所谓多数据源,就是一个Java EE项目中采用了不同数据库实例中的多个库,或者同一个数据库实例中多个不同的库。一般来说,采用MyCat分布式数据库中间件是比较好的解决方案,这样可以把数据库读写分离、分库分表、备份等操作交给中间件去做, Java代码只需要专注于业务即可。不过,这并不意味着无法使用Java代码解决类似的问题,在Spring Framework中就可以配置多数据源, Spring Boot继承其衣钵,只不过配置方式有所变化。

5.4.1 JdbcTemplate多数据源

JdbcTemplate多数据源的配置是比较简单的,因为一个JdbcTemplate对应一个DataSource,开发者只需要手动提供多个DataSource,再手动配置JdbcTemplate即可。具体步骤如下。

# 数据源1
spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.one.username=root
spring.datasource.one.password=123
spring.datasource.one.url=jdbc:mysql:///chapter05-1
# 数据源2
spring.datasource.two.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.two.username=root
spring.datasource.two.password=123
spring.datasource.two.url=jdbc:mysql:///chapter05-2
  • DataSourceConfig中提供了两个数据源: dsOnedsTwo,默认方法名即实例名。
  • @ConfigurationProperties注解表示使用不同前缀的配置文件来创建不同的DataSource实例。
@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.one")
    DataSource dsOne() {
        return DruidDataSourceBuilder.create().build();
    }
    @Bean
    @ConfigurationProperties("spring.datasource.two")
    DataSource dsTwo() {
        return DruidDataSourceBuilder.create().build();
    }
}
@Configuration
public class JdbcTemplateConfig {
    @Bean
    JdbcTemplate jdbcTemplateOne(@Qualifier("dsOne") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    @Bean
    JdbcTemplate jdbcTemplateTwo(@Qualifier("dsTwo") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}
  • JdbcTemplateConfig中提供两个JdbcTemplate实例。每个JdbcTemplate实例都需要提供-DataSource,由于Spring容器中有两个DataSource实例,因此需要通过方法名查找。@Qualifier注解表示查找不同名称的DataSource实例注入进来
    @Resource(name = "jdbcTemplateOne")
//    @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    @Qualifier("jdbcTemplateTwo")
    JdbcTemplate jdbcTemplateTwo;
    @GetMapping("/test1")
5.4.2 MyBatis多数据源
# 数据源1
spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.one.username=root
spring.datasource.one.password=123
spring.datasource.one.url=jdbc:mysql:///chapter05-1
# 数据源2
spring.datasource.two.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.two.username=root
spring.datasource.two.password=123
spring.datasource.two.url=jdbc:mysql:///chapter05-2
@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.one")
    DataSource dsOne() {
        return DruidDataSourceBuilder.create().build();
    }
    @Bean
    @ConfigurationProperties("spring.datasource.two")
    DataSource dsTwo() {
        return DruidDataSourceBuilder.create().build();
    }
}
@Configuration
@MapperScan(basePackages = "org.javaboy.mybatismulti.mapper1",sqlSessionFactoryRef = "sqlSessionFactory1",sqlSessionTemplateRef = "sqlSessionTemplate1")
public class MyBatisConfigOne {
    @Autowired
    @Qualifier("dsOne")
    DataSource ds;

    @Bean
    SqlSessionFactory sqlSessionFactory1() {
        SqlSessionFactory sqlSessionFactory = null;
        try {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(ds);
            sqlSessionFactory = bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sqlSessionFactory;
    }

    @Bean
    SqlSessionTemplate sqlSessionTemplate1() {
        return new SqlSessionTemplate(sqlSessionFactory1());
    }
}


@Configuration
@MapperScan(basePackages = "org.javaboy.mybatismulti.mapper2",sqlSessionFactoryRef = "sqlSessionFactory2",sqlSessionTemplateRef = "sqlSessionTemplate2")
public class MyBatisConfigTwo {
    @Autowired
    @Qualifier("dsTwo")
    DataSource ds;

    @Bean
    SqlSessionFactory sqlSessionFactory2() {
        SqlSessionFactory sqlSessionFactory = null;
        try {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(ds);
            sqlSessionFactory = bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sqlSessionFactory;
    }

    @Bean
    SqlSessionTemplate sqlSessionTemplate2() {
        return new SqlSessionTemplate(sqlSessionFactory2());
    }
}
5.4.3 JPA多数据源
spring.datasource.one.password=123
spring.datasource.one.username=root
spring.datasource.one.url=jdbc:mysql:///chapter05-1
spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource

spring.datasource.two.password=123
spring.datasource.two.username=root
spring.datasource.two.url=jdbc:mysql:///chapter05-2
spring.datasource.two.type=com.alibaba.druid.pool.DruidDataSource

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57InnoDBDialect
spring.jpa.properties.database=mysql
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.show-sql= true

这里的配置与配置单独的JPA有区别,因为在后文的配置中要从JpaProperties中的getProperties方法中获取所有JPA相关的配置, 因此这里的属性前缀都是spring.jpa.properties

@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.one")
    @Primary
    DataSource dsOne() {
        return DruidDataSourceBuilder.create().build();
    }
    @Bean
    @ConfigurationProperties("spring.datasource.two")
    DataSource dsTwo() {
        return DruidDataSourceBuilder.create().build();
    }
}
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "org.sang.dao1",
        entityManagerFactoryRef = "entityManagerFactoryBeanOne",
        transactionManagerRef = "platformTransactionManagerOne")
public class JpaConfigOne {
    @Resource(name = "dsOne")
    DataSource dsOne;
    @Autowired
    JpaProperties jpaProperties;
    @Bean
    @Primary
    LocalContainerEntityManagerFactoryBean entityManagerFactoryBeanOne(
            EntityManagerFactoryBuilder builder) {
        return builder.dataSource(dsOne)
                .properties(jpaProperties.getProperties())
                .packages("org.sang.model")
                .persistenceUnit("pu1")
                .build();
    }
    @Bean
    PlatformTransactionManager platformTransactionManagerOne(
            EntityManagerFactoryBuilder builder) {
        LocalContainerEntityManagerFactoryBean factoryOne = entityManagerFactoryBeanOne(builder);
        return new JpaTransactionManager(factoryOne.getObject());
    }
}
  • 使用@EnableJpaRepositories注解来进行JPA的配置,该注解中主要配置三个属性:basePackages, entityManagerFactoryRef以及transactionManagerRef.其中, basePackages用来指定Repository所在的位置, entityManagerFactoryRef用来指定实体类管理工厂Bean的名称,transactionManagerRef则用来指定事务管理器的引用名称,这里的引用名称就是JpaConfigOne类中注册的Bean的名称(默认的Bean名称为方法名)
  • 创建LocalContainerEntityManagerFactoryBean,该Bean将用来提供EntityManager实例,在该类的创建过程中,首先配置数据源,然后设置JPA相关配置(JpaProperties由系统自动加载),再设置实体类所在的位置,最后配置持久化单元名,若项目中只有一个EntityManagerFactory, 则persistenceUnit可以省略掉,若有多个,则必须明确指定持久化单元名。
  • 由于项目中会提供两个LocalContainerEntityManagerFactoryBean实例,第12行的注解@Primary表示当存在多个LocalContainerEntityManagerFactoryBean实例时,该实例将被优先使用。
    +PlatformTransactionManager表示创建一个事务管理器。 JpaTransactionManager提供对单个EntityManagerFactory的事务支持,专门用于解决JPA中的事务管理。
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "org.sang.dao2",
entityManagerFactoryRef = "entityManagerFactoryBeanTwo",
transactionManagerRef = "platformTransactionManagerTwo")
public class JpaConfigTwo {
    @Resource(name = "dsTwo")
    DataSource dsTwo;
    @Autowired
    JpaProperties jpaProperties;
    @Bean
    LocalContainerEntityManagerFactoryBean entityManagerFactoryBeanTwo(
            EntityManagerFactoryBuilder builder) {
        return builder.dataSource(dsTwo)
                .properties(jpaProperties.getProperties())
                .packages("org.sang.model")
                .persistenceUnit("pu2")
                .build();
    }
    @Bean
    PlatformTransactionManager platformTransactionManagerTwo(
            EntityManagerFactoryBuilder builder) {
        LocalContainerEntityManagerFactoryBean factoryTwo = entityManagerFactoryBeanTwo(builder);
        return new JpaTransactionManager(factoryTwo.getObject());
    }
}

第6章Spring Boot整合NosQL

NoSQL是指非关系型数据库,非关系型数据库和关系型数据库两者存在许多显著的不同点,其中最重要的是NoSQL不使用SQL作为查询语言。其数据存储可以不需要固定的表格模式,一般.都有水平可扩展性的特征。NoSQL主要有如下几种不同的分类:

  • Key/Value键值存储。这种数据存储通常都是无数据结构的,一般被当作字符串或者二进制数据,但是数据加载速度快,典型的使用场景是处理高并发或者用于日志系统等,这一类的数据库有Redis. Tokyo Cabinet等.
  • 列存储数据库。列存储数据库功能相对局限,但是查找速度快,容易进行分布式扩展,一般用于分布式文件系统中,这一类的数据库有HBase, Cassandra等。
  • 文档型数据库 。和Key/Value键值存储类似,文档型数据库也没有严格的数据格式,这既是缺点也是优势,因为不需要预先创建表结构,数据格式更加灵活,一般可用在Web应用中,这一类数据库有MongoDB, CouchDB等。
  • 图形数据库 。图形数据库专注于构建关系图谱,例如社交网络,推荐系统等,这一类的数据库有Neo4JDEX等。

6.1整合Redis

Redis是一个使用C编写的基于内存NoSQL数据库,它是目前最流行的键值对存储数据库。Redis由一个Key, Value映射的字典构成,与其他NoSQL不同, Redis中Value的类型不局限于字符串,还支持列表集合有序集合散列等。

6.1.1 Redis简介

Redis不仅可以当作缓存使用,也可以配置数据持久化后当作NoSQL数据库使用, 目前支持两种持久化方式:快照持久化AOF持久化。另一方面, Redis也可以搭建集群或者主从复制结构,在高并发环境下具有高可用性

6.1.2 Redis安装
Loaded plugins: fastestmirror, product-id, search-disabled-repos, subscription-manager

This system is not registered with an entitlement server. You can use subscription-manager to register.

Repository epel is listed more than once in the configuration
Repository epel-debuginfo is listed more than once in the configuration
Repository epel-source is listed more than once in the configuration
Loading mirror speeds from cached hostfile
 * base: mirrors.cloud.aliyuncs.com
 * extras: mirrors.cloud.aliyuncs.com
 * updates: mirrors.cloud.aliyuncs.com
Package redis-3.2.12-2.el7.x86_64 already installed and latest version
Nothing to do
[root@liruilong ~]# 
[root@liruilong ~]# redis-server -v
Redis server v=3.2.12 sha=00000000:0 malloc=jemalloc-3.6.0 bits=64 build=7897e7d0e13773f
[root@liruilong ~]# redis-cli -v
redis-cli 3.2.12
6.1.3 Redis整合Spring Boot

RedisJava客户端有很多,例如JedisJRedisSpring Data Redis等, Spring Boot借助于Spring. Data Redis为Redis提供了开箱即用自动化配置,开发者只需要添加相关依赖并配置Redis连接信息即可,具体整合步骤如下。

添加如下依赖:
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
			<exclusions>
				<exclusion>
					<groupId>io.lettuce</groupId>
					<artifactId>lettuce-core</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
		</dependency>

默认情况下,spring-boot-starter-data-redis使用的Redis工具是Lettuce,考虑到有的开发者习惯使用Jedis,因此可以从spring-boot-starter-data-redis中排除Lettuce并引入Jedis,修改为如下依赖:

配置Redis接下来在application.properties 中配置Redis连接信息
#基本连接信息配置

#表示使用的Redis库的编号, Redis中提供了16个database,编号为0-15
spring.redis.database=0
spring.redis.host=192.168.66.130
spring.redis.port=6379
spring.redis.password=123@456
#连接池信息配置.
spring.redis.lettuce.pool.max-active=
spring.redis.lettuce.pool.max-idle=
spring.redis.lettuce.pool.max-wait=
spring.redis.lettuce.pool.min-idle=
spring.redis.lettuce.shutdown-timeout=
#连接池最大连接数
spring.redis.jedis.pool.max-active=8
#连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
#连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1ms
#连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0

·Spring Boot·的自动配置类中提供了·RedisAutoConfiguration·进行Redis的配置,部分源码
在这里插入图片描述
由这一段源码可以看到, application.properties中配置的信息将被注入RedisProperties中,如果开发者自己没有提供RedisTemplate或者StringRedis Template实例,则Spring Boot默认会提供这两个实例, RedisTemplateStringRedisTemplate实例则提供了Redis的基本操作方法。
在这里插入图片描述

@RestController
public class BookController {
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @GetMapping("/test1")
    public void test1() {
        ValueOperations<String, String> ops1 = stringRedisTemplate.opsForValue();
        ops1.set("name", "三国演义");
        String name = ops1.get("name");
        System.out.println(name);
        ValueOperations ops2 = redisTemplate.opsForValue();
        Book b1 = new Book();
        b1.setId(1);
        b1.setName("红楼梦");
        b1.setAuthor("曹雪芹");
        ops2.set("b1", b1);
        Book book = (Book) ops2.get("b1");
        System.out.println(book);
    }
}
  • StringRedisTemplateRedisTemplate的子类,StringRedisTemplate中的keyvalue都是字符串,采用的序列化方案是StringRedisSerializer,而RedisTemplate则可以用来操作对象,RedisTemplate采用的序列化方案是JdkSerializationRedisSerializer.无论是StringRedis Template还是RedisTemplate,操作Redis的方法都是一致的。
  • StringRedisTemplateRedisTemplate都是通过opsForValue, opsForZSet或者opsForSet等方法首先获取一个操作对象,再使用该操作对象完成数据的读写。
  • 第10行向Redis中存储一条记录,第11行将之读取出来,第18行向Redis中存储一个对象,第19行将之读取出来。
6.1.4 Redis集群整合Spring Boot.
1,搭建Redis集群

·(1)集群原理·Redis集群中,所有的Redis节点彼此互联,节点内部使用二进制协议优化传输速度和带宽。当一个节点挂掉后,集群中超过半数的节点检测失效时才认为该节点已失效。不同于Tomcat集群需要使用反向代理服务器, Redis集群中的任意节点都可以直接和Java客户端连接

Redis集群上的数据分配则是采用哈希槽(HASH SLOT), Redis集群中内置了16384个哈希槽,当有数据需要存储时, Redis会首先使用CRC16算法对key进行计算,将计算获得的结果16384取余,这样每一个key都会对应一个取值在0-16383之间的哈希槽, Redis则根据这个余数将该条数据存储到对应的Redis节点上,开发者可根据每个Redis实例的性能来调整每个Redis实例上哈希槽分布范伟

6.2 整合MongoDB.

6.2.1 MongoDB简介

·MongoDB·是一种面向文档的数据库管理系统,它是一个介于关系型数据库和非关系型数据库,之间的产品, ·MongoDB·功能丰富,它支持一种类似JSONBSON数据格式,既可以存储简单的数据格式,也可以存储复杂的数据类型。·MongoDB·最大的特点是它支持的查询语言非常强大,并且还支持对数据建立索引。总体来说, ·MongoDB·是一款应用相当广泛的NosQL数据库。

6.2.2 MongoDB安装

關於Mongodb的學習《MongoDB大数据处理权威指南》读书笔记:https://blog.csdn.net/sanhewuyang/article/details/107597408

6.2.3 MongoDB整合Spring Boot.

借助于Spring Data MongoDB, Spring BootMongoDB也提供了开箱即用的自动化配置方案,具体配置步骤如下

       <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-mongodb</artifactId>
		</dependency>
spring.data.mongodb.authentication-database=admin
spring.data.mongodb.database=test
spring.data.mongodb.host=192.168.248.144
spring.data.mongodb.port=27017
spring.data.mongodb.username=root
spring.data.mongodb.password=123
#spring.data.mongodb.uri=mongodb://root:123@192.168.248.144:27017/admin
#spring.data.mongodb.uri=mongodb://192.168.248.144:27017/test
public interface BookDao extends MongoRepository<Book,Integer> {
    List<Book> findByAuthorContains(String author);
    Book findByNameEquals(String name);
}

使用MongoTemplate除了继承MongoRepository外,Spring Data MongoDB还提供了MongoTemplate用来方便地操作MongoDB。在Spring Boot中,若添加了MongoDB相关的依赖,而开发者并没有提供MongoTemplate,则默认会有一个MongoTemplate注册到Spring容器中,相关配置源码在MongoDataAutoConfiguration类中。因此,用户可以直接使用MongoTemplate,在Controller中直接注入MongoTemplate就可以使用了,添加如下代码到第5步的Controller中:

@RestController
public class BookController {
    @Autowired
    BookDao bookDao;
    @Autowired
    MongoTemplate mongoTemplate;

    @GetMapping("/test2")
    public void test2() {
        List<Book> books = new ArrayList<>();
       ....
        mongoTemplate.insertAll(books);
        List<Book> list = mongoTemplate.findAll(Book.class);
        System.out.println(list);
        Book book = mongoTemplate.findById(3, Book.class);
        System.out.println(book);
    }

    @GetMapping("/test1")
    public void test1() {
        List<Book> books = new ArrayList<>();
       ....
        bookDao.insert(books);
        List<Book> books1 = bookDao.findByAuthorContains("鲁迅");
        System.out.println(books1);
        Book book = bookDao.findByNameEquals("朝花夕拾");
        System.out.println(book);
    }
}

6.3 Session共享

正常情况下, HttpSession是通过Servlet容器创建并进行管理的,创建成功之后都是保存在内存中。如果开发者需要对项目进行横向扩展搭建集群,那么可以利用一些硬件或者软件工具来做负载均衡,此时,来自同一用户的HTTP请求就有可能被分发到不同的实例上去,如何保证各个实例之间Session的同步就成为一个必须解决的问题。

Spring Boot提供了自动化的Session共享配置,它结合Redis可以非常方便地解决这个问题。使用Redis解决Session共享问题的原理非常简单,就是把原本存储在不同服务器上Session拿出来放在一个独立的服务器上.
在这里插入图片描述
当一个请求到达Nginx服务器后,首先进行请求分发,假设请求被real serverl处理了, real server在处理请求时,无论是存储Session还是读取Session,都去操作Session服务器而不是操作自身内存中的Session,其他real server在处理请求时也是如此,这样就可以实现Session共享

6.3.1 Session共享配置
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
			<exclusions>
				<exclusion>
					<groupId>io.lettuce</groupId>
					<artifactId>lettuce-core</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		  <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

除了Redis依赖之外,这里还要提供spring-session-data-redis依赖, Spring Session可以做到透·明化替换掉应用的Session容器。项目创建成功后,在application.properties中进行Redis基本连接信息配置,代码如下:

spring.redis.database=0
spring.redis.host=192.168.66.130
spring.redis.port=6379
spring.redis.password=123@456
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0

添加··@EnableRedisHttpSession

/**
 * session托管到redis
 */
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds= 3600*24, redisFlushMode = RedisFlushMode.ON_SAVE, redisNamespace = "aurora-web")
public class RedisSessionConfig {
	
}
  • maxInactiveIntervalInSeconds: 设置 Session 失效时间,使用 Redis Session 之后,原 Spring Boot 的server.session.timeout 属性不再生效。
  • 经过上面的配置后,Session调用就会自动去Redis存取。另外,想要达到Session共享的目的,只需要在其他的系统上做同样的配置即可。
@EnableRedisHttpSession源码
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({RedisHttpSessionConfiguration.class})
@Configuration
public @interface EnableRedisHttpSession {
    //Session默认过期时间,单位秒,默认1800秒
    int maxInactiveIntervalInSeconds() default 1800;
 
    //配置key的namespace,默认的是spring:session,如果不同的应用共用一个redis,应该为应用配置不同的namespace,这样才能区分这个Session是来自哪个应用的
    String redisNamespace() default "spring:session";
 
    //配置刷新Redis中Session方式,默认是ON_SAVE模式,只有当Response提交后才会将Session提交到Redis,也可以配置成IMMEDIATE模式,即所有对Session的更改会立即更新到Redis
    RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE;
 
    //清理过期Session的定时任务
    String cleanupCron() default "0 * * * * *";
}

测试一下

@RestController
public class HelloController {
    @Value("${server.port}")
    String port;
    @PostMapping("/save")
    public String saveName(String name, HttpSession session) {
        session.setAttribute("name", name);
        return port;
    }
    @GetMapping("/get")
    public String getName(HttpSession session) {
        return port + ":"
                + session.getAttribute("name").toString();
    }
}
6.3.2 Nginx负载均衡

关于Nginx,可以访问Nginx 学习笔记(《深入理解Nginx:模块开发与架构解析》读书笔记):https://blog.csdn.net/sanhewuyang/article/details/114488191
nginx.conf·配置文件修改:
在这里插入图片描述

upstream backend {
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com;
}
server {
    location / {
    proxy_pass http: //backend;
    }
}

第7章构建RESTful服务

7.1 REST简介

REST (Representational State Transfer)是一种Web软件架构风格,它是一种风格,而不是标准,匹配或兼容这种架构风格的网络服务称为REST服务。REST服务简洁并且有层次, REST通常基于HTTP,URIXML以及HTML这些现有的广泛流行的协议和标准。

在REST中,资源是由URI来指定的,对资源的增删改查操作可以通过HTTP协议提供的GET, POST, PUT, DELETE等方法实现。使用REST可以更高效地利用缓存来提高响应速度,同时REST中的通信会话状态由客户端来维护,这可以让不同的服务器处理一系列请求中的不同请求,进而提高服务器的扩展性。在前后端分离项目中,一个设计良好的Web软件架构必然要满足REST风格。在Spring MVC框架中,开发者可以通过@RestController注解开发一个RESTful服务,不过,Spring Boot对此提供了自动化配置方案,开发者只需要添加相关依赖就能快速构建一个RESTful服务。

7.2 JPA实现REST

Spring Boot中,使用Spring Data JPASpring Data Rest可以快速开发出一个RESTful应用。接下来向读者介绍Spring Boot中非常方便的RESTful应用开发

7.2.1 基本实现

这里的依赖除了数据库相关的依赖外,还有Spring Data JPA的依赖以及Spring Data Rest的依赖。项目创建完成后,在application.properties 中配置基本的数据库连接信息:

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-rest</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.9</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

这里的依赖除了数据库相关的依赖外,还有Spring Data JPA的依赖以及Spring Data Rest的依赖。项目创建完成后,在application.properties中配置基本的数据库连接信息:

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=123
spring.datasource.url=jdbc:mysql:///jparestful
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database=mysql
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
spring.jpa.show-sql=true

##每页默认记录数,缺省值为20
#spring.data.rest.default-page-size=2
##分页查询页码参数名,缺省值为page
#spring.data.rest.page-param-name=page
##分页查询记录数参数名,缺省值为size
#spring.data.rest.limit-param-name=size
##分页查询排序参数名,缺省值为sort
#spring.data.rest.sort-param-name=sort
##base-path表示给所有请求路径都加上前缀
#spring.data.rest.base-path=/api
##添加成功时是否返回添加内容
#spring.data.rest.return-body-on-create=true
##更新成功时是否返回更新内容
#spring.data.rest.return-body-on-update=true

创建实体类,创建BookRepository类继承JpaRepository, JpaRepository中默认提供了一些基本的操作方法,代码如下:

public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    List<T> findAll();

    List<T> findAll(Sort var1);

    List<T> findAllById(Iterable<ID> var1);

    <S extends T> List<S> saveAll(Iterable<S> var1);

    void flush();

    <S extends T> S saveAndFlush(S var1);

    <S extends T> List<S> saveAllAndFlush(Iterable<S> var1);

    /** @deprecated */
    @Deprecated
    default void deleteInBatch(Iterable<T> entities) {
        this.deleteAllInBatch(entities);
    }

    void deleteAllInBatch(Iterable<T> var1);

    void deleteAllByIdInBatch(Iterable<ID> var1);

    void deleteAllInBatch();

    /** @deprecated */
    @Deprecated
    T getOne(ID var1);

    T getById(ID var1);

    <S extends T> List<S> findAll(Example<S> var1);

    <S extends T> List<S> findAll(Example<S> var1, Sort var2);
}

7.2.2 自定义请求路径

默认情况下,请求路径都是实体类名小写加s,如果开发者想对请求路径进行重定义,通过@RepositoryRestResource注解即可实现,下面的案例只需在BookRepository上添加@RepositoryRestResource注解即可:

@CrossOrigin
@RepositoryRestResource(path = "bs",collectionResourceRel = "bs",itemResourceRel = "b")
public interface BookRepository extends JpaRepository<Book, Integer> {
.....
}
  • @RepositoryRestResource·注解的·path·属性表示将所有请求路径中的books都修改为bs,http://ocalhost: 8080/bs:
  • collectionResourceRel属性表示将返回的JSON集合中book集合的key修改为bs;
  • itemResourceRel表示将返回的JSON集合中的单个book的key修改为b,
7.2.3 自定义查询方法.

默认的查询方法支持分页查询排序查询以及按照id查询,如果开发者想要按照某个属性查询,只需在BookRepository中定义相关方法并暴露出去即可,代码如下:

@RepositoryRestResource(path = "bs",collectionResourceRel = "bs",itemResourceRel = "b")
public interface BookRepository extends JpaRepository<Book, Integer> {
    @Override
    @RestResource(exported = false)
    void deleteById(Integer integer);

    @RestResource(path = "author",rel = "author")
    List<Book> findByAuthorContains(@Param("author") String author);
    @RestResource(path = "name",rel = "name")
    Book findByNameEquals(@Param("name") String name);
}
  • ·自定义查询只需要在BookRepository中定义相关查询方法即可,方法定义好之后可以不添加@RestResource注解,默认路径就是方法名。以第4行定义的方法为例,若不添加@RestResource注解, 则默 认 该方法的调用路径为http://ocalhost:8080/bs/search/indByAuthorContains?author=鲁迅。如果想对查询路径进行自定义,只需要添加@RestResource注解即可, path属性即表示最新的路径。还是以第4行的方法为例,添加@RestResource(path = "author",rel = "author")注解后的查询路径为"http://ocalhost:8080/bs/search/author?author=鲁迅".
  • 用户可以直接访问http/ocalhost:8080/bs/search路径查看该实体类暴露出来了哪些查询方法,默认情况下,在查询方法展示时使用的路径是方法名,通过@RestResource注解中的rel属性可以对这里的路径进行重定义,如图7-6所示。
7.2.4 隐藏方法

默认情况下,凡是继承了Repository接口(或者Repository的子类)的类都会被暴露出来,即开发者可执行基本的增删改查方法。以上文的BookRepository为例,如果开发者提供了BookRepository继承自Repository,就能执行对Book的基本操作,如果开发者继承了Repository但是又不想暴露相关操作,做如下配置即可:

//@RepositoryRestResource(exported = false)
public interface BookRepository extends JpaRepository<Book, Integer> {
    @Override
    @RestResource(exported = false)
    void deleteById(Integer integer);
....
}

将·@RepositoryRestResource注解中的exported属性置为false之后,则增删改查接口都会失效, BookRepository类中定义的相关方法也会失效。若只是单纯地不想暴露某个方法,则在方法上进行配置即可,例如开发者想屏蔽DELETE接口.

7.2.5 配置CORS

4.6节已经向读者介绍了CORS两种不同的配置方式,一种是直接在方法上添加@CrosSorigin注解,另一种是全局配置全局配置在这里依然适用,但是默认的RESTful工程不需要开发者自己提供Controller,因此添加在Controller的方法上的注解可以直接写在BookRepository上,代码如下:接口跨域:@CrossOrigin注解添加到某一个方法上即可。

//@CrossOrigin
@RepositoryRestResource(path = "bs",collectionResourceRel = "bs",itemResourceRel = "b")
public interface BookRepository extends JpaRepository<Book, Integer> {
   @CrossOrigin
    List<Book> findByAuthorContains(@Param("author") String author);
  ....
}
7.2.6其他配置
application.properties配置
##每页默认记录数,缺省值为20
#spring.data.rest.default-page-size=2
##分页查询页码参数名,缺省值为page
#spring.data.rest.page-param-name=page
##分页查询记录数参数名,缺省值为size
#spring.data.rest.limit-param-name=size
##分页查询排序参数名,缺省值为sort
#spring.data.rest.sort-param-name=sort
##base-path表示给所有请求路径都加上前缀
#spring.data.rest.base-path=/api
##添加成功时是否返回添加内容
#spring.data.rest.return-body-on-create=true
##更新成功时是否返回更新内容
#spring.data.rest.return-body-on-update=true

当然,这些XML配置也可以在Java代码中配置,且代码中配置的优先级高于application.properties配置的优先级,代码如下

@Configuration
public class RestConfig extends RepositoryRestConfigurerAdapter {
    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.setDefaultPageSize(2)
                .setPageParamName("page")
                .setLimitParamName("size")
                .setSortParamName("sort")
                .setBasePath("/api")
                .setReturnBodyOnCreate(true)
                .setReturnBodyOnUpdate(true);
    }
}

7.2.7 JPA使用rest自定义链接
@Configuration
public class MyResourceProcessor implements ResourceProcessor {
    @Override
    public ResourceSupport process(ResourceSupport resourceSupport) {
        resourceSupport.add(new Link("http://www.baidu.com", "百度一下"));
        return resourceSupport;
    }
}

7.3 MongoDB实现REST

MongoDB整合Spring Boot,而使用Spring Boot快速构建RESTful·服务除了结合Spring Data JPA之外,也可以结合Spring Data MongoDB实现。使用Spring DataMongoDB构建RESTful服务也是三个步骤,分别如下。

       <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-mongodb</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-rest</artifactId>
		</dependency>

这里Spring Data Rest的依赖和7.2节中的一致,只是将Spring Data JPA的依赖变为Spring DataMongoDB的依赖。项目创建成功后,在application.properties中配置MongoDB的基本连接信息,

spring.data.mongodb.authentication-database=test
spring.data.mongodb.database=test
spring.data.mongodb.username=sang
spring.data.mongodb.password=123
spring.data.mongodb.host=192.168.248.144
spring.data.mongodb.port=27017
public interface BookRepository extends MongoRepository<Book,Integer> {
}

第8章开发者工具与单元测试

8.1 devtools简介

Spring Boot中提供了一组开发工具spring-boot-devtools,可以提高开发者的工作效率,开发者可以将该模块包含在任何项目中, spring-boot-devtools最方便的地方莫过于热部署了。

8.2 devtools实战

8.2.1 基本用法
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<optional>true</optional>
		</dependency>
  • 这里多了一个optional选项,是为了防止将devtools依赖传递到其他模块中。当开发者将应用打包运行后, devtools会被自动禁用。
  • 当开发者将spring-boot-devtools引入项目后,只要classpath路径下的文件发生了变化,项目就会自动重启,这极大地提高了项目的开发速度。
8.2.2 基本原理

Spring Boot中使用的自动重启技术涉及两个类加载器,一个是baseclassloader,用来加载不会变化的类,例如项目引用的第三方的jar;另一个是restartclassloader,用来加载开发者自己写的会·变化的类。当项目需要重启时, restartclassloader将被一个新创建的类加载器代替,而baseclassloader则继续使用原来的,这种启动方式要比冷启动快很多,因为baseclassloader已经存在并且已经加载好

8.3单元测试

8.3.1 基本用法

当开发者使用Intelli IDEA或者在线创建一个Spring Boot项目时,创建成功后,默认都添加了spring-bool-starter-est依赖,并且创建好了测试类。


@RunWith(SpringRunner.class)
@SpringBootTest
public class AppTest {
    MockMvc mockMvc;
    @Autowired
    WebApplicationContext wac;
    @Autowired
    HelloService helloService;
    @Before
    public void before() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }
    @Test
    public void test2() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/hello", "name=aaa")).andDo(MockMvcResultHandlers.print());
    }
    @Test
    public void test1() {
        System.out.println(helloService.sayHello("里斯"));
    }
}
  • @RunWith注解,该注解将JUnit执行类修改为SpringRunner,而SpringRunnerSpring Framework中测试类SpringJUnit4ClassRunner的别名。
  • @Spring BootTest注解除了提供Spring TestContext中的常规测试功能之外,还提供了其他特性:提供默认的ContextLoader, 自动搜索@Spring BootConfiguration、自定义环境属性、为不同的webEnvironment模式提供支持,这里的webEnvironment模式主要有4种.這裏不説了。
8.3.2 Service测试
@Service
public class HelloService {
    public String sayHello(String name) {
        return "Hello " + name + " !";
    }
}
    @Autowired
    HelloService helloService;
    @Test
    public void contextLoads() {
        String hello = helloService.sayHello("Michael");
        Assert.assertThat(hello, Matchers.is("Hello Michael !"));
    }
8.3.3 Controller测试.
 @Test
    public void test2() throws Exception {
        ObjectMapper om = new ObjectMapper();
        Book book = new Book();
        book.setAuthor("罗贯中");
        book.setName("三国演义");
        book.setId(1);
        String s = om.writeValueAsString(book);
        MvcResult mvcResult = mockMvc
                .perform(MockMvcRequestBuilders
                        .post("/book")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(s))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andReturn();
        System.out.println(mvcResult.getResponse().getContentAsString());
    }
@RestController
public class HelloController {
    @Autowired
    HelloService helloService;
    @GetMapping("/hello")
    public String hello(String name) {
        return helloService.sayHello(name);
    }
}

    MockMvc mockMvc;
    @Autowired
    WebApplicationContext wac;
    @Autowired
    HelloService helloService;
    @Before
    public void before() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }
    @Test
    public void test1() throws Exception {
        MvcResult mvcResult = mockMvc.perform(
                MockMvcRequestBuilders
                        .get("/hello")
                        .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                        .param("name", "Michael"))
                //返回什么数据        
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
        System.out.println(mvcResult.getResponse().getContentAsString());
    }

除了MockMvc这种测试方式之外,Spring Boot还专门提供了TestRestTemplate用来实现集成测试,若开发者使用了@Spring BootTest注解,则TestRestTemplate将自动可用,直接在测试类中注入即可。注意,如果要使用TestRestTemplate进行测试,需要将@Spring BootTest注解中webEnvironment属性的默认值由WebEnvironment.MOCK修改为webEnvironment.DEFINED PORT或者WebEnvironment.RANDOM PORT,因为这两种都是使用一个真实的Servlet环境而不是模拟的Serlet环境。其代码如下:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
 @Autowired
 TestRestTemplate restTemplate;
   @Test
    public void test3() {
        ResponseEntity<String> hello = restTemplate.getForEntity("/hello?name={0}", String.class, "Michael");
        System.out.println(hello.getBody());
    }
  • test2方法演示了POST请求如何传递JSON数据,首先在32行将一个book对象转为一段JSON,然后在36行设置请求的contentType为APPLICATION-JSON,最后在37行设置content为上传的JSON即可。
8.3.4 JSON测试

开发者可以使用@JsonTest测试JSON序列化反序列化是否工作正常,该注解将自动配置Jackson ObjectMapper.@JsonComponent以及Jackson Modules.如果开发者使用Gson代替Jackson,该注解将配置Gson,具体用法如下:

@RunWith(SpringRunner.class)
@JsonTest
public class JSONTest {
    @Autowired
    JacksonTester<Book> jacksonTester;
    @Test
    public void testSerialize() throws IOException {
        Book book = new Book();
        book.setId(1);
        book.setName("三国演义");
        book.setAuthor("罗贯中");
        Assertions.assertThat(jacksonTester.write(book))
                .isEqualToJson("book.json");
        Assertions.assertThat(jacksonTester.write(book))
                .hasJsonPathStringValue("@.name");
        Assertions.assertThat(jacksonTester.write(book))
                .extractingJsonPathStringValue("@.name")
                .isEqualTo("三国演义");
    }
    @Test
    public void testDeserialize() throws Exception {
        String content = "{\"id\":1,\"name\":\"三国演义\",\"author\":\"罗贯中\"}";
//        Book book = new Book();
//        book.setId(1);
//        book.setName("三国演义");
//        book.setAuthor("罗贯中");
        Assertions.assertThat(jacksonTester.parseObject(content).getName())
                .isEqualTo("三国演义");
    }
}

第9章Spring Boot缓存

Spring 3.1中开始对缓存提供支持,核心思路是对方法的缓存,当开发者调用一个方法时,将方法的参数返回值作为key/value缓存起来,当再次调用该方法时,如果缓存中有数据,就直接,从缓存中获取,否则再去执行该方法。但是, Spring中并未提供缓存的实现,而是提供了一套缓存API,开发者可以自由选择缓存的实现, 目前Spring Boot支持的缓存有如下几种:Cache (JSR-107)EhCache 2.xHazelcastInfinispanCouchbaseRedisCaffeineSimple

9.1 Ehcache 2.x缓存.

Ehcache缓存在Java开发领域已是久负盛名,在Spring Boot中,只需要一个配置文件就可以将Ehcache集成到项目中。Ehcache 2.x的使用步骤如下。

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
        </dependency>

2.添加缓存配置文件如果Ehcache的依赖存在,并且在classpath下有一个名为ehcache2.xmlEhcache配置文件,那么EhCacheCacheManager将会自动作为缓存的实现。因此,在resources目录下创建ehcache.xml文件作为Ehcache缓存的配置文件,代码如下:

<ehcache>
    <diskStore path="java.io.tmpdir/cache"/>
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
    />
    <cache name="book_cache"
           maxElementsInMemory="10000"
           eternal="true"
           timeToIdleSeconds="120"
           timeToLiveSeconds="120"
           overflowToDisk="true"
           diskPersistent="true"
           diskExpiryThreadIntervalSeconds="10"/>
</ehcache>

这是一个常规的Ehcache配置文件,提供了两个缓存策略,一个是默认的,另一个名为book-cache.其中,

  • name表示缓存名称; maxElementsInMemory表示缓存最大个数:
  • eternal表示缓存对象是否永久有效,一旦设置了永久有效, timeout将不起作用;
  • timeToldleSeconds表示缓存对象在失效前的允许闲置时间(单位:秒) ,当eternal-false对象不是永久有效时,该属性才生效;
  • timeToLiveSeconds表示缓存对象在失效前允许存活的时间(单位:秒),当eternal-false对象不是永久有效时,该属性才生效;
  • overflowToDisk表示当内存中的对象数量达到maxElementsInMemory时, Ehcache是否将对象写到磁盘中;
  • diskExpiryThreadIntervalSeconds表示磁盘失效线程运行时间间隔。

另外,如果开发者想自定义Ehcache配置文件的名称和位置,可以在application.properties中添加如下配置:

spring.cache.ehcache.config=classpath:ehcache2.xml
@SpringBootApplication
@EnableCaching
public class CacheApplication {

    public static void main(String[] args) {
        SpringApplication.run(CacheApplication.class, args);
    }
}
@Service
@CacheConfig(cacheNames = "book_cache")
public class BookDao {
    @Autowired
    MyKeyGenerator myKeyGenerator;
    @Cacheable(keyGenerator = "myKeyGenerator")
    public Book getBookById(Integer id) {
        System.out.println("getBookById");
        Book book = new Book();
        book.setId(id);
        book.setName("三国演义");
        book.setAuthor("罗贯中");
        return book;
    }
    @CachePut(key = "#book.id")
    public Book updateBookById(Book book) {
        System.out.println("updateBookById");
        book.setName("三国演义2");
        return book;
    }
    @CacheEvict(key = "#id")
    public void deleteBookById(Integer id) {
        System.out.println("deleteBookById");
    }
}

9.2 Redis单机缓存

9.3 Redis集群缓存

9.3.1 搭建Redis集群.
9.3.2 配置缓存
9.3.3 使用缓存

第10章 Spring Boot安全管理…

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山河已无恙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值