写在前面
- 嗯,回家处理一些事,所以离职了,之前的公司用开源技术封装了一套自己的低代码平台,所以之前学的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安装
關於Mongodb的學習《MongoDB大数据处理权威指南》读书笔记:https://blog.csdn.net/sanhewuyang/article/details/107597408
6.2.3 MongoDB整合Spring Boot.
借助于Spring Data MongoD
B, 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,可以访问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
,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
表示缓存名称;maxElementsInMemor
y表示缓存最大个数: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");
}
}