本文为读书流水笔记 加深印象的同时为后面回忆和查漏补缺提供便利 各级别开发工程师也可以作快速浏览发现疏漏之用
Spring 是一个轻量级的容器但配置繁琐和各第三方框架整合时代码量都非常大并且大多是重复代码 Spring Boot 带来了 新的自动化配置解决方案,使用 Spring Boot 可以快速创建基于 Spring 产级的独立应用程序 Spring Boot 中对一些常用的第方库提供了默认的自动化配置方案
Spring Boot 项目可以采用传统的方案打成 war 包,然后部署到 Tomcat 中运行,也可以直接打成可执行 jar 包
本书基于 Spring Boot 2.0.4 需要 Java 8及以上
Spring Boot 主要优势
(1) 提供一个快速的 Spring 项目搭建渠道
(2) 开箱即用,很少的 Spring 配置就能运行一个 Java 项目
(3) 提供了生产级的服务监控方案
(4) 内嵌服务器, 可以快速部署
(5) 提供了一系列非功能性的通用配置
(6) 纯 Java 配置,没有代码生成, 也不需要 XML 配置
org.springframework.boot
spring-boot-starter-parent
2.0.4.RELEASE
org.springframework.boot
spring-boot-starter-web
@EnableAutoConfiguration
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
@EnableAutoConfiguration 注解表示开启自动化配置 由于添加了web依赖所以在开启自动化配置后会自动进行Spring和Spring MVC的配置
main方法中通过run方法传入App.class告诉Spring哪个是主要组件
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello spring boot";
}
}
在控制器中提供了"/hello"接口 此时需要配置包扫描才能将HelloController注册到Spring MVC容器中 因此需要在App类上添加注解@ComponentScan
@EnableAutoConfiguration
@ComponentScan
public class App {...
也可以直接使用组合注解@SpringBootApplication
@SpringBootApplication
public class App {...
server.port=8081
server.error.path=/error
server.servlet.session.timeout=30m
server.servlet.context-path=/
server.tomcat.uri-encoding=utf-8
server.tomcat.max-threads=500
server.tomcat.basedir=/home/tmp
配置解释
port配置Web容器的端口号
error.path配置当项目出错时跳出去的页面
servlet.session.timeout配置了session失效时间 默认单位是秒(但会向下取最大分钟数)
servlet.context-path项目名称 默认为/ 如果配置就要在访问路径中添加该路径
tomcat.uri-encoding配置Tomcat请求编码
tomcat.max-threads配置Tomcat最大线程数
tomcat.basedir存放Tomcat运行日志和临时文件的目录 默认使用系统的临时目录
HTTPS配置
使用jdk提供的/jdk/bin/keytool可以自生成数字证书 x.p12 将该文件复制到项目的根目录然后再application.properties中配置
server.ssl.key-store=x.p12
server.ssl.key-alias=tomcathttps
server.ssl.key-store-password=123456
然后使用https访问 因为证书是自己生成的不被浏览器认可 需添加信任继续前进
此时如果进行HTTP请求就会失败 因为Spring Boot不支持同时HTTP和HTTPS方式访问接口 会出现访问失败
Bad Request
This combination of host and port requires TLS.
可用重定向请求将HTTP重定向为HTTPS请求
@Configuration
public class TomcatConfig {
@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;
}
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
Jetty配置
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-tomcat
org.springframework.boot
spring-boot-starter-jetty
移除starter-web中默认的Tomcat并加入Jetty依赖再启动Spring Boot
从日志可以看到 Jetty started on port(s) 8080 取代了 Tomcat
其他如Undertow配置与Jetty类似 是红帽公司开源的Java服务器
● 类型安全配置属性
properties或yaml配置最终加载到Spring Environment
Spring提供@Value注解和EnvironmentAware接口来讲数据注入到属性 Spring Boot对此进一步提出了类型安全配置属性(Type-safe Configuration Properties)
在application.properties中添加
book.name=三国演义
book.author=罗贯中
将配置注入Bean中
@Component
@ConfigurationProperties(prefix="book")
public class Book {
private String name;
private String author;
//getter/setter
}
@ConfigurationProperties中的prefix属性描述要加载的配置文件的前缀
● YAML配置
YAML配置是JSON的超集 starter-web依赖间接引入snakeyaml实现对YAML解析
YAML虽然方便但也有缺陷 如无法使用@PropertySource注解加载YAML文件
● Profile
测试和生产环境配置之间切换 Spring对此提供@Profile注解的方案 Spring Boot更进一步约定配置名称application-{profile}.properties profile占位符代表当前环境的名称
然后再application.properties中配置
spring.profiles.active=dev 表示使用application-dev.properties配置文件启动项目
也可以再main方法上添加代码使用配置
SpringApplicationBuilder builder = new SpringApplicationBuilder(XApplication.class);
builder.application().setAdditionalProfiles(“dev”);
builder.run(args);
还可以再项目启动时配置
java -jar x.jar --spring.profiles.active=dev
Spring Boot官方推荐使用模板引擎Thymeleaf 也支持FreeMarker JSP技术不推荐使用
整合Thymeleaf和FreeMarker为例
● 返回JSON数据
JSON是目前主流的数据传输方式 Spring MVC中使用消息转换器HttpMessageConverter对JSON的转换提供很好的支持 在Spring Boot中更为简化只要添加web依赖默认加入jackson-databind作为JSON处理器
public class Book {
@JsonIgnore
private Float price;
@JsonFormat(pattern = "yyyy-MM-dd")
private Date publicationDate;
//getter/setter
}
需要@ResponseBody注解 可以采用@RestController代替@Controller和@ResponseBody
这是通过Spring中默认提供的MappingJackson2HttpMessageConverter来实现的
● 自定义转换器
常见的JSON处理器除了jackson-databind还有Gson和fastjson
● Gson
Gson是Google开源的JSON解析框架 使用Gson需要先除去starter-web中默认的jackson-databind然后加入Gson依赖
Spring Boot中默认提供Gson的自动转换类GsonHttpMessageConvertersConfiguration 但Gson如果想对日期数据格式化还需要开发者自定义HttpMessageConverter 自定义
首先看GsonHttpMessageConvertersConfiguration中的一段源码
@Bean
@ConditionalOnMissingBean
public GsonHttpMessageConverter gsonHttpMessageConverter(Gson gson) {
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
converter.setGson(gson);
return converter;
}
@ConditionOnMissingBean注解表示当前项目中没有提供GsonHttpMessageConverter时才会使用默认的GsonHttpMessageConverter 所以开发者只要提供一个即可
@Configuration
public class GsonConfig {
@Bean
GsonHttpMessageConverter gsonHttpMessageConverter() {
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
GsonBuilder builder = new GsonBuilder();
builder.setDateFormate("yyyy-MM-dd");
builder.excludeFieldsWithModifiers(Modifier.PROTECTED);
Gson gson = builder.create();
converter.setGson(gson);
return converter;
}
}
● fastjson
fastjson是阿里巴巴开源的JSON解析框架 是目前JSON解析最快的开源框架 不同于Gson fastjson必须要开发者提供HttpMessageConverter才能使用 先除去jackson-databind依赖并引入fastjson依赖
@Configuration
publica class MyFastJsonConfig {
@Bean
FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
FastJsonConfig config = new FastJsonConfig();
config.setDateFormat("yyyy-MM-dd");
config.setCharset(Charset.forName("UTF-8"));
config.setSerializerFeatures(
SerializerFeature.WriteClassName,
SerializerFeature.WriteMapNullValue,
SerializerFeature.PrettyFormat,
SerializerFeature.WriteNullListAsEmpty,
SerializerFeature.WriteNullStringAsEmpty
);
converter.setFastJsonConfig(config);
return converter;
}
}
配置完还要配置响应编码 否则返回JSON中文会乱码 在配置文件中spring.http.encoding.force-response=true
● 静态资源访问默认策略
在Spring MVC中对静态资源需要开发者手动配置静态资源过滤 Spring Boot中对此也提供了自动化配置简化过滤配置
Spring Boot中对Spring MVC的自动化配置都在WebMvcAutoConfiguration中 在其中还有个静态内部类WebMvcAutoConfigurationAdapter实现了WebMvcConfiguration接口 该接口中的addResourceHandlers是用来配置静态资源过滤的
public void addResourceHandlers(ResourceHandlerRegistry registry) {
...
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(
registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(
this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod))
.setCacheControl(cacheControl));
}
Spring Boot在这里进行了默认的静态资源过滤配置 其中staticPathPattern默认定义在WebMvcProperties中
private String staticPathPattern= "/**";
this.resourceProperties.getStaticLocations()获取的默认静态资源位置定义在ResourceProperties中
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/", "classpath:/resources/",
"classpath:/static/", "classpath:/public/" };
在getResourceLocations方法中对这4个静态资源位置作了扩充
static String[] getResourceLocations(String[] staticLocations) {
String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length];
System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length);
System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length);
return locations;
}
其中SERVLET_LOCATIONS的定义是{"/"} 综上Spring Boot默认会过滤所有的静态资源 一共5个位置分别是 “classpath:/META-INF/resources/”、“classpath:/resources/”、“classpath:/static/”、“classpath:/public/“以及”/”
开发者可以将静态资源放到这5个位置 按照定义的顺序优先级递减
Spring Boot项目不需要webapp目录 所以第5个"/"可以暂不考虑
在浏览器中“hppt://localhost:8080/p1.png”即可看到classpath:/META-INF/resources的p1
● 静态资源访问自定义策略
在配置文件中定义
在application.properties中直接定义过滤规则和静态资源位置
spring.mvc.static-path-pattern=/static/**
spring.resources.static-locations=classpath:/static/
过滤规则为/static/** 静态资源位置为classpath:/static/
浏览器中输入“http://localhost8080/static/p1.png”即可看到classpath:/static/下的资源
在Java编码定义
@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addresourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
}
● 文件上传
Spring MVC对文件上传做了简化 在Spring Boot做了更进一步简化
Java中文件上传涉及两个组件CommonsMultipartResolver和StandardServletMultipartResolver
CommonsMultipartResolver使用commons-fileupload处理multipart请求
StandardServletMultipartResolver基于Servlet3.0处理multipart请求 使用时不用添加额外jar包 Tomcat7.0开始支持Servlet3.0而Spring Boot2.0.4内嵌Tomcat8.5.32可以直接使用
Spring Boot文件上传自动配置类MultipartAutoConfiguration中默认采用StandardServletMultipartResolver
部分可选配置
spring.servlet.multipart.enable=true # 是否开启文件上传支持 默认true
spring.servlet.multipart.file-size-threshold=0 # 文件写入磁盘的阈值 默认0
spring.servlet.multipart.location=E:\\temp #上传文件的临时保存位置
spring.servlet.multipart.max-file-size=1MB # 上传单个文件最大大小 默认1MB
spring.servlet.multipart.max-request-size=10MB # 多个文件上传的总大小 默认10MB
spring.servlet.multipart.resolve-lazily=false # 文件是否延迟解析 默认false
● 多文件上传
@PostMapping("/uploads")
public String upload(MultipartFile[] uploadFiles) {}
● @ControllerAdvice
是@Controller的增强版 主要用来处理全局数据 一般搭配@ExceptionHandler、@ModelAttribute和@InitBinder使用
● 全局异常处理
@ControllerAdvice最常用就是全局异常处理
@ControllerAdvice
public class CustomExceptionHandler {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public void uploadException(MaxUploadSizeExceededException e, HttpServletResponse resp) throws IOException {
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("上传文件大小超出限制");
out.flush();
out.close();
}
}
如果想让该方法处理所有异常只需要将异常类改成Exception即可 方法的参数可以有异常实例、HttpServletResponse、HttpServletRequest和Model等 返回值可以是JSON、ModelAndView等
添加全局数据
在@ControllerAdvice类中使用@ModelAttribute来设置任意Controller中Model可获取的值
请求参数预处理
@ControllerAdvice结合@InitBinder实现请求参数预处理
● 自定义错误页
@ControllerAdvice的全局异常处理用来处理应用级别的异常 容器级别的错误如Filter抛出异常就无法处理 Spring Boot还有另外的异常处理方式
Spring Boot中的错误默认是由BasicErrorController类处理 该类核心方法有两个
errorHtml方法返回错误HTML页面 error返回错误JSON 返回JSON比较简单 返回HTML需要调用resolveErrorView获取一个错误视图的ModelAndView 默认在error目录下查找4xx、5xx等文件作为错误视图 找不到时会用error作为默认错误页面
● 自定义Error数据
Spring Boot返回的Error信息共有5条 分别是timestamp、status、error、message和path 在BasicErrorController的errorHtml和error方法中都是通过getErrorAttributes方法获取Error信息的 该方法最终会调用DefaultErrorAttributes类的getErrorAttributes方法,而DefaultErrorAttributes类是在ErrorMvcAutoConfiguration中提供的
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.Current)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
}
可以看到没有提供ErrorAttributes时默认采用DefaultErrorAttributes(同时是ErrorAttributes的子类)自定义错误只要我们提供一个类继承DefaultErrorAttributes即可
自定义Error视图同理 默认提供DefaultErrorViewResolver
当前还有一种完全替换异常流程自定义的方式 替换ErrorMvcAutoConfiguration中的ErrorController(默认提供BasicErrorController)
● CORS支持
CORS(Cross-Origin Resource Sharing)是由W3C制定的跨域资源共享技术标准 目的是为了解决前端的跨域请求 Java中常见的跨域解决方案是JSONP但只支持GET请求
@RestController
@RequestMapping("/book")
public class BookController {
@PostMapping("/")
@CrossOrigin(value="http://localhost:8081", maxAge=1800,allowedHeaders="*")
public String addBook(String name) {
return "receive:" + name;
}
}
@CrossOrigin中的value表示支持的域
maxAge表示探测请求的有效期 如对DELETE、PUT请求或自定义头信息的请求浏览器在执行前会先发送探测请求 配置有效期可以等过期后才再发送一次探测请求 默认1800秒(30分钟)
allowedHeaders表示允许的请求头 *表示所有请求头都被允许
可采用全局配置更方便
@Configuration
public class MyWebMvcConfig implements WebMvcConfiguration {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/book/**")
.allowedHeaders("*")
.allowedMethods("*")
.maxAge(1800)
.allowedOrigins("http://localhost:8081");
}
}
allowedHeaders 允许的请求头 默认允许所有的请求头信息
allowedMethods 允许的请求方法 默认允许GET、POST和HEAD
maxAge OPTIONS 探测请求的有效期
allowedOrigins 支持的域
● 配置类与XML配置
Spring Boot推荐使用Java来完成相关配置而不推荐使用XML配置
@ComponentScan注解会扫描所有Spring组件包括@Configuration
@ComponentScan注解在@SpringBootApplication注解中已经提供
● 注册拦截器
Spring MVC中提供AOP风格的拦截器 精细拦截处理能力 Spring Boot拦截器注册更加方便
public class MyInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRqeust request, HttpServletResponse response, Object handler) {
System.out.println("preHandler");
return true;
}
@Override
public void postHandle(HttpServletRqeust request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
System.out.println("postHandle");
}
@Override
public void afterCompletion(HttpServletRqeust request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("afterCompletion");
}
}
拦截器方法执行顺序 preHandle->Controller->postHandle->afterCompletion
只有preHandle方法返回true后面的方法才执行
postHandle在拦截器链内所有拦截器返回成功时才会调用
afterCompletion只有preHandle返回true才调用
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor1())
.addPathPatterns("/**")
.excludePathPatterns("/hello")
}
}
自定义类实现WebMvcConfigurer接口 设置拦截路径和排除路径
● 启动系统任务
有一些特殊的任务需要在系统启动时执行 如配置加载或数据库初始化等
没有使用Spring Boot可以在Listener中解决 Spring Boot中提供两种方案 CommandLineRunner和ApplicationRunner 两者基本一致 差别主要在参数上
Spring Boot启动时会遍历所有实现Runner接口的@Component并调用run方法 如果有多个可以使用@Order(1) 实现调用顺序(越小先执行)
● 路径映射
使用页面模板后 有些不需要经过控制器加载数据只是简单跳转的页面如login.html和index.html 直接在MVC配置中重写addViewControllers方法配置直接映射 提高访问速度
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.addViewController("/index").setViewName("index");
}
}
● 配置AOP
面向切面编程(Aspect-Oriented Programming) 系统运行时动态添加代码的方式
Spring框架对AOP提供很好支持
Joinpoint(连接点) 类里面可以被增强的方法即为连接点
Pointcut(切入点) 对Joinpoint进行拦截的定义即为切入点
Advice(通知) 拦截到Joinpoint之后要做的事就是通知
Aspect(切面) Pointcut和Advice的结合
Target(目标对象) 要增强的类称为Target
Spring Boot在Spring的基础上对AOP配置提供自动化配置
添加spring-boot-starter-aop依赖
在org.sang.aop.service下创建UserService
@Service
public class UserService {
public String getUserById(Integer id) {
return "user";
}
}
创建切面
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(* org.sang.aop.service.*.*(..))")
public void pc1() {}
@Before(value = "pc1()")
public void before(JoinPoint jp) {
String name = jp.getSignature().getName();
System.out.println(name + "方法开始执行");
}
@After(value = "pc1()")
public void after(JoinPoint jp) {
String name = jp.getSignature().getName();
System.out.println(name + "方法开始结束");
}
@AfterReturning(value = "pc1()", returning = "result")
public void afterReturning(JoinPoint jp, Object result) {
String name = jp.getSignature().getName();
System.out.println(name + "方法返回值: " + result);
}
@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) throws Throwable {
return pjp.proceed();
}
}
@Aspect注解表示这是个切面类
@Pointcut(“execution(* org.sang.aop.service..(…))”) 第一个表示返回任意值 第二个表示service包下任意类 第三个*表示类内任意方法 括号中两个点表示方法参数任意
@Around注解表示环绕通知 是所有通知里功能最强大的 可以实现前置通知、后置通知、异常通知以及返回通知的功能并且可以在此处理目标方法的异常
● 自定义欢迎页
Spring Boot项目启动后首先在静态资源路径下查找index.html作为首页文件 没有则去查找动态的index文件作为首页文件 favicon查找favicon.ico 如静态资源路径resources/static
● 除去某个自动配置
@SpringBootApplication
@EnableAutoConfiguration(exclude = {ErrorMvcAutoConfiguration.class})
开发者也可以在application.properties配置文件中配置
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
● 持久层技术
持久层是Jav EE中访问数据库的核心操作 Spring Boot对常见持久层框架提供了自动化配置 如JdbcTemplate、JPA等 MyBatis的自动化配置则是MyBatis官方提供的
● 整合JdbcTemplate
添加依赖 starter-jdbc mysql-connector-java com.alibaba.druid
properties配置文件
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql:///chapter05
spring.datasource.username=root
spring.datasource.password=l23
实体类
public class Book {...}
数据库访问层
@Repository
public class BookDao {
@Autowired
JdbcTemplate jdbcTemplate;
public int addBook(Book boook) {
return jdbcTemplate.update("INSERT INTO book(name, author) VALUES (?, ?)", book.getName(), book.getAuthor());
}
}
JdbcTemplate 主要操作有update、batchUpdate、query和queryForObject 还有execute方法可执行任意SQL、call方法调用存储过程等
查询时需要RowMapper将列和实体属性对应 如果一致可以直接使用BeanPropertyRowMapper 不一致则要自己实现RowMapper接口
● 整合MyBatis
MyBatis支持定制化SQL、存储过程及高级映射 几乎避免了所有JDBC代码
在传统SSM框架中MyBatis需大量XML配置 在Spring Boot中MyBatis官方提供了自动化配置方案开箱即用
druid数据库连接配置同jdbc
创建数据库访问层
@Mapper
public interface BookMapper {
int addBook(Book book);
int deleteBookById(Integer id);
int updateBookById(Book book);
Book getBookById(Integer id);
List getAllBooks();
}
可以在类上添加@Mapper注解表示是MyBatis的Mapper 还有种方式是在配置类上添加@MapperScan(“org.sang.mapper”)注解表示扫描该目录所有接口作为Mapper
针对BookMapper接口中每个方法都在BookMapper.xml中实现
#{}用来代替接口中的参数 实体类中的属性可以直接通过#{实体类属性名}获取
XML文件建议写在resources目录下 如果放在包下Maven运行时会忽略包下的XML文件(还需要在pom.xml文件中指明资源文件位置)
@Service
public class BookService {
@Autowired
BookMapper bookMapper;
public int addBook(Book book) {
return bookMapper.addBook(book);
}
}
● 整合Spring Data JPA
JPA(Java Persistence API)和Spring Data是两个范畴的概念
如Hibernate是一个ORM框架而JPA则是一种ORM规范 Hibernate是实现了JPA制定的ORM规范(JPA规范起草者也是Hibernate的作者) 从功能上来说 JPA相当于Hibernate的子集
Spring Data是Spring的一个子项目致力于简化数据库访问
Spring Data JPA可以有效简化关系型数据库访问代码
配置Spring Data JPA依赖
配置数据库和JPA相关配置
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
show-sql 表示在控制台打印JPA执行过程中生成的SQL
ddl-auto 表示项目启动时根据实体类更新数据库中的表(其他可选值create、create-drop、validate、no)
创建实体类
@Entity(name = "t_book")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "book_name", nullable = false)
private String name;
@Transient
private String description;
//省略getter/setter
}
@Entity注解表示实体类 项目启动时自动生成表 表明为name值 不填默认为类名
所有实体都要有主键@Id @GeneratedValue表示主键自动生成及生成策略
默认表中字段名就是实体类属性名 @Column可以定制生成字段的属性 name为表的列名
@Transient表示生成数据库表时该属性被忽略
创建BookDao接口集成JpaRepository
public interface BookDao extends JpaRepository {
List getBooksByAuthorStartingWith(String author);
LIst 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 getBookByIdAndAuthor(@Param("author") String author, @Param("id") Integer id);
}
BookDao继承JpaRepository JpaRepository中提供了一些基本增删改查、分页和排序等 如save、findAll(pageable)
Spring Data JPA中只要方法符合既定规范就能分析出意图 如And、Or等
● 多数据源
JdbcTemplate、MyBatis及Spring Data JPA都可以进行多数据源配置
JdbcTemplate使用并不广泛 MyBatis灵活性较好 方便开发者进行SQL优化 Spring Data JPA使用方便 特别是快速实现一个RESTful风格的应用
● 整合NoSQL
非关系型数据库 不使用SQL作为查询语言 数据存储可以不需固定表格模式 可水平扩展
● 整合Redis
Redis的Java客户端很多 Jedis、JRedis、Spring Data Redis等
Spring Boot使用Spring Data Redis为Redis提供开箱即用自动化配置
默认情况下starter-data-redis使用Redis工具是Lettuce 有的开发者习惯使用Jedis可以排除Lettuce并引入Jedis
配置Redis
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123456
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
如果使用Lettuce只需要将上面的jedis替换成lettuce即可
在Spring Boot的自动配置类中提供了RedisAutoConfiguration
StringRedisTemplate是RedisTemplate的子类 key和value都是字符串 采用序列化方案是StringRedisSerializer 而RedisTemplate则可以用来操作对象 序列化方案是JdkSerializationRedisSerializer 两者操作Redis的方法都是一致的
StringRedisTemplate和RedisTemplate都是通过opsForValue、opsForZSet或者opsForSet等方法首先获取一个操作对象 在使用该对象完成数据的读写
Redis集群配置 除了添加jedis外再添加依赖commons-pool2
org.apache.commons
commons-pool2
配置信息(YAML)
spring:
redis:
cluster:
ports:
- 8001
- 8002
host: 127.0.0.1
poolConfig:
max-total: 8
max-idle: 8
max-wait-millis: -1
min-idle: 0
配置Redis
@Configuration
@ConfigurationProperties("spring.redis.cluster")
public class RedisConfig {
List ports;
String host;
JedisPoolConfig poolConfig;
@Bean
RedisClusterConfiguration redisClusterConfiguration() {
RedisClusterConfiguration configuration = new RedisClusterConfiguration();
List nodes = new ArrayList<>();
for (Integer port: ports) {
nodes.add(new RedisNode(host, port));
}
configuration.setPassword(RedisPassword.of("123456"));
configuration.setClusterNodes(nodes);
return configuration;
}
@Bean
JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory(redisClusterConfiguration(), poolConfig);
return factory;
}
@Bean
RedisTemplate redisTemplate() {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(jedisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
return redisTemplate;
}
@Bean
StringRedisTemplate stringRedisTemplate() {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(jedisConnectionFactory());
stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
return stringRedisTemplate;
}
//省略 getter/setter
}
通过@ConfigurationProperties注解将配置文件前缀注入
配置RedisClusterConfiguration实例 设置Redis密码和Redis节点信息
根据RedisClusterConfiguration实例及连接池配置创建连接工厂JedisConnectionFactory
根据JedisConnectionFactory创建RedisTemplate和StringRedisTemplate 同时配置key和value的序列化方式 剩下的用法就和单实例Redis一致了
● 整合MongoDB
Spring Boot也提供了开箱即用的自动化配置方案Spring Data MongoDB
org.springframework.boot
spring-boot-starter-data-mongodb
配置文件
spring.data.mongo.authentication-database=admin
spring.data.mongo.database=test
spring.data.mongo.host=127.0.0.1
spring.data.mongo.port=27017
spring.data.mongo.username=root
spring.data.mongo.password=123
创建BookDao 定义类似于Spring Data JPA中的Repository定义
public interface BookDao extends MongoRepository {
List findByAuthorContains(String author);
Book findByNameEquals(String name);
}
除了继承MongoRepository外 Spring Data MongoDB还提供MongoTemplate操作 如果开发者未提供则默认会有MongoTemplate注册到Spring容器中 配置源码在MongoDataAutoConfiguration类中 直接@Autowired MongoTemplate即可使用
● Session共享
正常情况下 HttpSession是通过Servlet容器创建并进行管理的 创建成功后保存在内存中 如果做了分布式系统 可能同一用户的HTTP请求分发到不同实例就要保证Session同步
Spring Boot提供了自动化Session共享配置 它结合Redis非常方便的解决了这个问题 原理就是把原来存储在服务器上的Session放在了一个独立的服务器上 实现了Session共享
添加Redis和Session依赖
org.springframework.session
spring-session-data-redis
Spring Session可以做到透明化的替换掉应用的Session容器
@RestContoller
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();
}
}
虽然操作的还是HttpSession 实际上HttpSession容器已经被透明替换 此时存储在Redis上
第七章 构建RESTful服务
● JPA实现RESTful
REST(Representational State Transfer)是Web软件架构风格而不是标准 匹配或兼容这种架构风格的网络服务称为REST服务
REST通常基于HTTP、URI、XML及HTML等现有的广泛流行协议和标准 资源由URI来指定 对资源的增删改查通过HTTP提供的GET、POST、PUT、DELETE等方法实现 通讯会话状态由客户端维护 提高服务器的扩展性
在Spring MVC框架中通过@RestController注解开发RESTful服务 Spring Boot对此又提供了自动化配置方案 只需要添加rest依赖就能快速构建RESTful服务
org.springframework.boot
spring-boot-starter-data-rest
创建实体类
@Entity(name = "t_book")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
//getter/setter
}
创建BookRepository
public interface BookRepository extends JpaRepository {}
完成上面步骤即使什么都没写 RESTful服务就已经构建成功了
可以用 POST json http://localhost:8080/books 进行提交测试
GET http://localhost:8080/books[/1] 获取数据
POST json 修改数据
DELETE http://localhost:8080/books/1 删除数据
默认情况下请求路径是实体类名小写加s 如果想对请求路径重定义可通过@RepositoryRestResource注解在BookRepository来实现 @RestResource则可用在方法上修改调用路径 如果不想暴露类或部分方法只要在注解里设置exported=false即可 默认暴露
其他配置
#每页默认记录值 默认值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
#bash-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代码中配置且代码中配置优先级高于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);
}
}
● MongoDB实现REST
org.springframework.boot
spring-boot-starter-data-mongodb
org.springframework.boot
spring-boot-starter-data-rest
properties配置mongodb
创建Book实体类和BookRepository 服务成功搭建
● devtools实战
Spring Boot提供一组开发工具spring-boot-devtools可提高开发效率且支持热部署
org.springframewokr.boot
spring-boot-devtools
true
注意这里optional选项是防止将devtools依赖传递到其他模块 当开发者将应用打包运行后devtools会自动禁用
引入项目后只要classpath路径下的文件发生变化项目就会重启 提高开发速度 IDEA需要Ctrl+F9手动编译或在设置里打开自动编译
静态资源目录下文件变化和模板文件变化不会触发重启 devtools默认嵌入LiveReload服务器可以解决静态文件热部署(浏览器插件监控静态资源目录)
禁用自动重启配置 spring.devtools.restart.enabled=false
● devtools热部署基本原理
Spring Boot自动重启技术涉及两个类加载器
baseclassloader用来加载不会变化的类 例如引用的第三方jar
restartclassloader用来加载开发者写的会变化的类
项目需重启时 restartclassloader将被一个新创建的类加载器替代 而baseclassloader继续使用原来的 所以比冷启动快很多
● 单元测试 Service测试
Spring Boot的单元测试与Spring中的测试一脉相承 又做了大量的简化 进而实现对Controller、Service和Dao层的代码进行测试
添加spring-boot-starter-test依赖 创建测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class Test01ApplicationTests {
@Test
public void contextLoads() {}
}
@Service
public class HelloService {
public String sayHello(String name) {
return "Hello " + name;
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class Test01ApplicationTests {
@Autowired
HelloService helloService;
@Test
public void contextLoads() {
String hello = helloService.sayHello("Michael");
Assert.assertThat(hello, Matchers.is("Hello Michael"));
}
}
@RunWith注解 将JUnit执行类修改为SpringRunner 而Spring Runner是Spring Framework中测试类SpringJUnit4ClassRunner的别名
@SpringBootTest注解除了提供SpringTestContext中的常规测试功能外还提供默认的ContextLoader、自动搜索@SpringBootConfiguration、自定义环境属性、为不同的webEnvironment模式提供支持等 其中webEnvironment模式主要有四种
MOCK 这种模式是当classpath下存在servletAPIS时创建WebApplicationContext并提供一个mockservlet的模拟环境 存在Spring WebFlux则创建 ReactWebApplicationContext 都不存在则创建常规的ApplicationContext
RANDOM_PORT 提供一个真实的Servlet环境 使用内嵌的容器 端口随机
DEFINED_PORT 提供一个真实的Servlet环境 使用内嵌的容器 使用定义好的端口
NONE 加载一个普通的ApplicationContext 不提供Servlet环境 一般不适用于Web测试
Spring Boot中@*Test注解会去包含测试类的包下查找带有@SpringBootApplication或@SpringBootConfiguration注解的主配置类
@Test注解junit junit中的@After、@AfterClass、@Before、@BeforeClass、@Ignore等注解一样可以在这里使用
● 单元测试 Controller测试
Controller测试要使用到Mock测试 对一些不易获取的对象采用创建虚拟对象 Spring中提供的MockMvc提供了对HTTP请求的模拟 不依赖网络即可实现对Controller快速测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class Test01ApplicationTests {
@Autowired
HelloService helloService;
@Autowired
WebApplicationContext wac;
MockMvc mockMvc;
@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());
}
}
注入一个WebApplicationContext来模拟ServletContext环境
@Before每个测试方法执行前进行MockMvc初始化操作
MockMvc调用perform开启一个RequestBuilders请求并验证响应码200和打印结果
● JSON测试
开发者使用@JsonTest测试JSON序列化和反序列化 该注解自动配置Jackson ObjectMapper @JsonComponent以及Jackson Modules 如果用Gson代替Jackson该注解将配置Gson
至此上篇更新本书的1-8章节完成,即将发布本书下篇9-16章节,敬请关注~