写在前面
- 嗯,回家处理一些事,所以离职了,之前的公司用开源技术封装了一套自己的低代码平台,所以之前学的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基础配置
工程创建的三种方式:
- 在线创建
- 通过 IDE 来创建(IntelliJ IDEA、STS)
- 通过改造一个普通的 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不支持同时在配置中启动HTTP和HTTPS,这个时候可以配置请求重定向,将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依次降低

- 项目根目录下的config文件夹中。
- 项目根目录下。
- classpath 下的config文件夹中。
- 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配置
YAML是JSON的超集,简洁而强大,是一种专门用来书写配置文件的语言,可以替代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 ,看名字就知道,这是一个消息转换工具,有两方面的功能:
-
将服务端返回的对象序列化成 JSON 字符串 -
将前端传来的 JSON 字符串反序列化成 Java 对象 所有的 JSON 生成都离不开相关的 HttpMessageConverter,SpringMVC 自动配置了Jackson 和 Gson 的 HttpMessageConverter,Spring Boot 中又对此做了自动化配置: -
org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration -
org.springframework.boot.autoconfigure.http.GsonHttpMessageConvertersConfiguration
所以,如果用户使用 jackson 和 gson 的话,没有其他额外配置,则只需要添加依赖即可。
修改转化器
添加一个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之外,还有Gson和fastison,这里针对常见用法分别举例.
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自定义策略
自定义静态资源过滤策略有以下两种方式;
- 在配置文件中定义可以在
application.properties中直接定义过滤规则和静态资源位置,
# 静态资源位置
spring.web.resources.static-locations=classpath:/static/
# 过滤规则
spring.mvc.static-path-pattern=/static/**

-
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.html、 5xx.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/templates和resource/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视图是展示给用户的页面,在BasicErrorController的errorHtml方法中调用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.配置跨域
跨域有两个地方可以配置:
- 一个是直接在相应的请求方法上加注解: 这种配置方式是一种
细粒度的配置.可以控制到每一个方法上。
-
@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);
}
}
- 另一种全局配置,代码如下:
@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对此提供了两种解决方案:CommandLineRunner和ApplicationRunner. CommandLineRunner和ApplicationRunner基本一致,差别主要体现在参数上。
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注解,实现对Servlet, Filter以及Listener的扫描,代码如下:
@SpringBootApplication
@ServletComponentScan("org.javaboy.filter")
public class FilterApplication {
public static void main(String[] args) {
SpringApplication.run(FilterApplication.class, args);
}
}
4.11 路径映射.
有一些页面在控制器中不需要加载数据,只是完成·简单的跳转,对于这种页面,可以直接配置路径映射,提高访问速度。例如,有两个Thymeleaf做模板的页面login.html和index.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 Boot在Spring的基础上对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中提供了大量的自动化配置类,例如上文提到过的ErrorMvcAutoConfiguration、ThymeleafAutoConfiguration,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
JdbcTemplate是Spring提供的一套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中,增删改三种类型的操作主要使用update和batchUpdate方法来完成.query和queryForObject方法主要用来完成查询功能。另外,还有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>
也可以自定义resources下mapper位置
mybatis.mapper-locations=classpath:mapper/*.xml
5.3整合Spring Data JPA
JPA (Java Persistence API)和Spring Data是两个范畴的概念。JPA则是一种ORM规范, JPA和Hibernate的关系就像JDBC与JDBC驱动的关系,即JPA制定了ORM规范,而Hibernate是这些规范的实现(事实上,是先有Hibernate后有JPA, JPA规范的起草者也是Hibernate的作者) ,因此从功能上来说, JPA相当于Hibernate的一个子集。
Spring Data是Spring的一个子项目,致力于简化数据库访问,通过规范的方法名称来分析开发者的意图,进而减少数据库访问层的代码量。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中提供了两个数据源: dsOne和dsTwo,默认方法名即实例名。 -
@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等。 - 图形数据库 。图形数据库专注于
构建关系图谱,例如社交网络,推荐系统等,这一类的数据库有Neo4J、DEX等。
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
Redis的Java客户端有很多,例如Jedis、JRedis、 Spring 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默认会提供这两个实例, RedisTemplate和StringRedisTemplate实例则提供了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);
}
}
-
StringRedisTemplate是RedisTemplate的子类,StringRedisTemplate中的key和value都是字符串,采用的序列化方案是StringRedisSerializer,而RedisTemplate则可以用来操作对象,RedisTemplate采用的序列化方案是JdkSerializationRedisSerializer.无论是StringRedis Template还是RedisTemplate,操作Redis的方法都是一致的。 -
StringRedisTemplate和RedisTemplate都是通过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·功能丰富,它支持一种类似JSON的BSON数据格式,既可以存储简单的数据格式,也可以存储复杂的数据类型。·MongoDB·最大的特点是它支持的查询语言非常强大,并且还支持对数据建立索引。总体来说, ·MongoDB·是一款应用相当广泛的NosQL数据库。
6.2.2 MongoDB安装
6.2.3 MongoDB整合Spring Boot.
借助于Spring Data MongoDB, Spring Boot为MongoDB也提供了开箱即用的自动化配置方案,具体配置步骤如下
<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.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,URI和XML以及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 JPA和Spring 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,而SpringRunner是Spring 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.x、Hazelcast、Infinispan、Couchbase、Redis、Caffeine、Simple
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.xml的Ehcache配置文件,那么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安全管理…










