标签(空格分隔): springbootcss
Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员再也不须要定义样板化的配置。用个人话来理解,就是spring boot其实不是什么新的框架,它默认配置了不少框架的使用方式,就像maven整合了全部的jar包,spring boot整合了全部的框架(不知道这样比喻是否合适)。html
其实就是简单、快速、方便!平时若是咱们须要搭建一个spring web项目的时候须要怎么作呢?前端
- 配置web.xml,加载spring和spring mvc
如今很是流行微服务,若是我这个项目仅仅只是须要发送一个邮件,若是个人项目仅仅是生产一个积分;我都须要这样折腾一遍!vue
可是若是使用spring boot呢?java
很简单,我仅仅只须要很是少的几个配置就能够迅速方便的搭建起来一套web项目或者是构建一个微服务!mysql
因此spring boot的优势为:react
本节主要目标完成Spring Boot基础项目的构建,而且实现一个简单的Http请求处理,经过这个例子对Spring Boot有一个初步的了解,并体验其结构简单、开发快速的特性。web
本教材采用Java 1.8.0_131
、Spring Boot 1.5.10
实现。spring
虽然JDK目前已经发布1.9版本,可是目前在互联网公司中,使用JDK1.8版本的更多,而传统软件公司中不少还停留在JDK1.6,JDK1.7,甚至还有JDK1.5的。
spring boot也在2018年3月发布了2.0的正式版本,可是对如今来讲仍是太新。sql
经过SPRING INITIALIZR工具产生基础项目
访问:http://start.spring.io/
选择构建工具Maven Project
、Spring Boot
版本1.5.10
以及一些工程基本信息,可参考下图所示:
点击Generate Project
下载项目压缩包,解压项目包,并用IDE以Maven项目导入,以IntelliJ IDEA
为例:
菜单中选择File
–>New
–>Project from Existing Sources...
选择解压后的项目文件夹,点击OK
点击Import project from external model
并选择Maven
,点击Next
到底为止。
若你的环境有多个版本的JDK,注意到选择Java SDK的时候请选择Java 7`以上的版本
前面构建的spring boot项目的目录结构以下:
经过上面步骤完成了基础项目的建立,如上图所示,Spring Boot的基础结构共三个文件(具体路径根据用户生成项目时填写的Group全部差别):
src/main/java
下的程序入口:SbDemoApplication.java
src/main/resources
下的配置文件:application.properties
src/test/
下的测试入口:SbDemoApplicationTests.java
生成的SbDemoApplication.java
和SbDemoApplicationTests.java
类均可以直接运行来启动当前建立的项目,因为目前该项目未配合任何数据访问或Web模块,程序会在加载完Spring以后结束运行。
当前的pom.xml
内容以下,仅引入了两个模块:
spring-boot-starter
:核心模块,包括自动配置支持、日志和YAML
spring-boot-starter-test
:测试模块,包括JUnit、Hamcrest、Mockito
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
引入Web模块,需添加spring-boot-starter-web
模块:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
HelloWorld
服务建立package
命名为com.dengcl.sb_demo.web
(根据实际状况修改)
建立HelloController
类,内容以下
@RestController public class HelloController { @RequestMapping("/hello") public String sayHello() { return "Hello World"; } }
启动主程序,执行src/main/java
下的程序入口:SbDemoApplication.java
,在控制台出现以下图内容:
打开浏览器访问http://localhost:8080/hello
,能够看到页面输出Hello World
打开的src/test/
下的测试入口SbDemoApplicationTests.java
类。下面编写一个简单的单元测试来模拟http请求,具体以下:
@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class SbDemoApplicationTests { @Autowired private MockMvc mvc; @Test public void getHello() throws Exception { mvc.perform(get("/hello").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().string(equalTo("Hello World!"))); } }
注意引入下面内容,让status
、content
、equalTo
函数可用
import static org.hamcrest.Matchers.equalTo; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
至此已完成目标,经过Maven
构建了一个空白Spring Boot
项目,再经过引入web
模块实现了一个简单的请求处理。
前面提到使用SPRING INITIALIZR
页面工具来建立spring boot
项目,接下来将介绍嵌入的IDEA中的Spring Initializr
工具,它同Web提供的建立功能同样,能够帮助咱们快速的构建出一个基础的Spring Boot
工程
菜单栏中选择File
=>New
=>Project
..,咱们能够看到以下图所示的建立功能窗口。其中Initial Service Url
指向的地址就是Spring官方提供的Spring Initializr
工具地址,因此这里建立的工程实际上也是基于它的Web工具来实现的。
点击Next
,等待片刻后,咱们能够看到以下图所示的工程信息窗口,在这里咱们能够编辑咱们想要建立的工程信息。其中,Type能够改变咱们要构建的工程类型,好比:Maven
、Gradle
;Language能够选择:Java
、Groovy
、Kotlin
。
点击Next
,进入选择Spring Boot版本和依赖管理的窗口。在这里值的咱们关注的是,它不只包含了Spring Boot Starter POMs中的各个依赖,还包含了Spring Cloud的各类依赖。
点击Next
,进入最后关于工程物理存储的一些细节。最后,点击Finish
就能完成工程的构建了。
IDEA中的Spring Initializr虽然仍是基于官方Web实现,可是经过工具来进行调用并直接将结果构建到咱们的本地文件系统中,让整个构建流程变得更加顺畅,尚未体验过此功能的Spring Boot/Cloud爱好者们不妨能够尝试一下这种不一样的构建方式。
首先,回顾并详细说明一下在前面使用的@Controller
、@RestController
、@RequestMapping
注解。能够发现这些注解都和前面学习的Spring MVC
中一致,其实spring boot的web实现就是经过spring mvc来实现的。
@Controller
:修饰class,用来建立处理http请求的对象
@RestController
:Spring4以后加入的注解,原来在@Controller
中返回json
须要@ResponseBody
来配合,若是直接用@RestController
替代@Controller
就不须要再配置@ResponseBody
,默认返回json
格式。
@RequestMapping
:配置url
映射
下面咱们尝试使用Spring MVC来实现一组对User对象操做的RESTful API,配合注释详细说明在Spring MVC中如何映射HTTP请求、如何传参、如何编写单元测试。
RESTful API具体设计以下:
User实体定义:
public class User { private Long id; private String name; private Integer age; // 省略setter和getter }
实现对User对象的操做接口UserController
@RestController @RequestMapping(value="/users") // 经过这里配置使下面的映射都在/users下 public class UserController { // 建立线程安全的Map static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>()); @RequestMapping(value="/", method= RequestMethod.GET) public List<User> getUserList() { // 处理"/users/"的GET请求,用来获取用户列表 // 还能够经过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递 List<User> r = new ArrayList<User>(users.values()); return r; } @RequestMapping(value="/", method=RequestMethod.POST) public String postUser(@ModelAttribute User user) { // 处理"/users/"的POST请求,用来建立User // 除了@ModelAttribute绑定参数以外,还能够经过@RequestParam从页面中传递参数 users.put(user.getId(), user); return "success"; } @RequestMapping(value="/{id}", method=RequestMethod.GET) public User getUser(@PathVariable Long id) { // 处理"/users/{id}"的GET请求,用来获取url中id值的User信息 // url中的id可经过@PathVariable绑定到函数的参数中 return users.get(id); } @RequestMapping(value="/{id}", method=RequestMethod.PUT) public String putUser(@PathVariable Long id, @ModelAttribute User user) { // 处理"/users/{id}"的PUT请求,用来更新User信息 User u = users.get(id); u.setName(user.getName()); u.setAge(user.getAge()); users.put(id, u); return "success"; } @RequestMapping(value="/{id}", method=RequestMethod.DELETE) public String deleteUser(@PathVariable Long id) { // 处理"/users/{id}"的DELETE请求,用来删除User users.remove(id); return "success"; } }
针对该Controller编写测试用例验证正确性,具体以下。固然也能够经过浏览器插件等进行请求提交验证。
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.hamcrest.Matchers.equalTo; @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class UserControllerTest { @Autowired private MockMvc mvc; @Test public void testUserController() throws Exception { // 测试UserController RequestBuilder request = null; // 一、get查一下user列表,应该为空 request = get("/users/"); mvc.perform(request) .andExpect(status().isOk()) .andExpect(content().string(equalTo("[]"))); // 二、post提交一个user request = post("/users/") .param("id", "1") .param("name", "测试大师") .param("age", "20"); mvc.perform(request) .andExpect(content().string(equalTo("success"))); // 三、get获取user列表,应该有刚才插入的数据 request = get("/users/"); mvc.perform(request) .andExpect(status().isOk()) .andExpect(content().string(equalTo("[{\"id\":1,\"name\":\"测试大师\",\"age\":20}]"))); // 四、put修改id为1的user request = put("/users/1") .param("name", "测试终极大师") .param("age", "30"); mvc.perform(request) .andExpect(content().string(equalTo("success"))); // 五、get一个id为1的user request = get("/users/1"); mvc.perform(request) .andExpect(content().string(equalTo("{\"id\":1,\"name\":\"测试终极大师\",\"age\":30}"))); // 六、del删除id为1的user request = delete("/users/1"); mvc.perform(request) .andExpect(content().string(equalTo("success"))); // 七、get查一下user列表,应该为空 request = get("/users/"); mvc.perform(request) .andExpect(status().isOk()) .andExpect(content().string(equalTo("[]"))); }
至此,咱们经过引入web模块(没有作其余的任何配置),就能够轻松利用Spring MVC的功能,以很是简洁的代码完成了对User
对象的RESTful API的建立以及单元测试的编写。其中同时介绍了Spring MVC中最为经常使用的几个核心注解:@Controller
,@RestController
,RequestMapping
以及一些参数绑定的注解:@PathVariable
,@ModelAttribute
,@RequestParam
等。
在前面章节中咱们完成了一个简单的RESTful Service,体验了快速开发的特性。可是如何把处理结果渲染到页面上呢?那么本篇就在上篇基础上介绍一下如何进行Web应用的开发。
在咱们开发Web应用的时候,须要引用大量的js、css、图片等静态资源。
Spring Boot默认提供静态资源目录位置需置于classpath
下,目录名需符合以下规则:
/static
/public
/resources
/META-INF/resources
举例:咱们能够在src/main/resources/
目录下建立static
,在该位置放置一个图片文件。启动程序后,尝试访问http://localhost:8080/D.jpg
。如能显示图片,配置成功。
在以前的示例中,咱们都是经过@RestController
来处理请求,因此返回的内容为json
对象。那么若是须要渲染html页面的时候,要如何实现呢?
在动态HTML实现上Spring Boot依然能够完美胜任,而且提供了多种模板引擎的默认配置支持,因此在推荐的模板引擎下,咱们能够很快的上手开发动态网站。
Spring Boot提供了默认配置的模板引擎主要有如下几种:
当使用上述模板引擎中的任何一个,它们默认的模板配置路径为:src/main/resources/templates
。固然也能够修改这个路径,具体如何修改,可在后续模板引擎的配置属性中查询并修改。
Thymeleaf是一个XML/XHTML/HTML5模板引擎,可用于Web与非Web环境中的应用开发。它是一个开源的Java库,基于Apache License 2.0许可,由Daniel Fernández建立,该做者仍是Java加密库Jasypt的做者。
Thymeleaf提供了一个用于整合Spring MVC的可选模块,在应用开发中,你可使用Thymeleaf来彻底代替JSP或其余模板引擎,如Velocity、FreeMarker等。Thymeleaf的主要目标在于提供一种可被浏览器正确显示的、格式良好的模板建立方式,所以也能够用做静态建模。你可使用它建立通过验证的XML与HTML模板。相对于编写逻辑或代码,开发者只需将标签属性添加到模板中便可。接下来,这些标签属性就会在DOM(文档对象模型)上执行预先制定好的逻辑。
示例模板:
<table> <thead> <tr> <th th:text="#{msgs.headers.name}">Name</td> <th th:text="#{msgs.headers.price}">Price</td> </tr> </thead> <tbody> <tr th:each="prod : ${allProducts}"> <td th:text="${prod.name}">Oranges</td> <td th:text="${#numbers.formatDecimal(prod.price,1,2)}">0.99</td> </tr> </tbody> </table>
能够看到Thymeleaf主要以属性的方式加入到html标签中,浏览器在解析html时,当检查到没有的属性时候会忽略,因此Thymeleaf的模板能够经过浏览器直接打开展示,这样很是有利于先后端的分离。
在Spring Boot中使用Thymeleaf,只须要引入下面依赖,并在默认的模板路径src/main/resources/templates
下编写模板文件便可完成。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
在完成配置以后,举一个简单的例子,在前面工程的基础上,举一个简单的示例来经过Thymeleaf渲染一个页面。
定义Controller
@Controller @RequestMapping("/view") public class ViewController { @RequestMapping(value = "/",method = RequestMethod.GET) public String index(Model model) { // 加入一个属性,用来在模板中读取 model.addAttribute("host","http://www.dengcl.com"); // return模板文件的名称,对应src/main/resources/templates/index.html return "index"; } }
定义Thymeleaf模板页面index.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" > <head> <meta charset="UTF-8"/> <title>Title</title> </head> <body> <h1 th:text="${host}">Hello World</h1> </body> </html>
注意:
index.html
文件在/src/main/resource/templates/
目录下。
如上页面,直接打开html页面展示Hello World
,可是启动程序后,访问http://localhost:8080/view
,则是展现Controller中host的值:·http://blog.didispace.com·,作到了不破坏HTML自身内容的数据逻辑分离。
Thymeleaf的默认参数配置
若有须要修改默认配置的时候,只需复制下面要修改的属性到application.properties中,并修改为须要的值,如修改模板文件的扩展名,修改默认的模板路径等。
# Enable template caching. spring.thymeleaf.cache=true # Check that the templates location exists. spring.thymeleaf.check-template-location=true # Content-Type value. spring.thymeleaf.content-type=text/html # Enable MVC Thymeleaf view resolution. spring.thymeleaf.enabled=true # Template encoding. spring.thymeleaf.encoding=UTF-8 # Comma-separated list of view names that should be excluded from resolution. spring.thymeleaf.excluded-view-names= # Template mode to be applied to templates. See also StandardTemplateModeHandlers. spring.thymeleaf.mode=HTML5 # Prefix that gets prepended to view names when building a URL. spring.thymeleaf.prefix=classpath:/templates/ # Suffix that gets appended to view names when building a URL.
虽然spring boot支持这么多模板引擎实现视图渲染,可是全部的这些都是后端生成UI,在互联网的应用中仍是建议设计成基于api的系统,后台只提供api,前端开发html app。基于vue,react均可以,不过vue更轻量级。
因为Spring Boot可以快速开发、便捷部署等特性,相信有很大一部分Spring Boot的用户会用来构建RESTful API。而咱们构建RESTful API的目的一般都是因为多终端的缘由,这些终端会共用不少底层业务逻辑,所以咱们会抽象出这样一层来同时服务于多个移动端或者Web前端。
这样一来,咱们的RESTful API就有可能要面对多个开发人员或多个开发团队:IOS开发、Android开发或是Web开发等。为了减小与其余团队平时开发期间的频繁沟通成本,传统作法咱们会建立一份RESTful API文档来记录全部接口细节,然而这样的作法有如下几个问题:
因为接口众多,而且细节复杂(须要考虑不一样的HTTP请求类型、HTTP头部信息、HTTP请求内容等),高质量地建立这份文档自己就是件很是吃力的事,下游的抱怨声不绝于耳。
随着时间推移,不断修改接口实现的时候都必须同步修改接口文档,而文档与代码又处于两个不一样的媒介,除非有严格的管理机制,否则很容易致使不一致现象。
为了解决上面这样的问题,本文将介绍RESTful API的重磅好伙伴Swagger2,它能够轻松的整合到Spring Boot中,并与Spring MVC程序配合组织出强大RESTful API文档。它既能够减小咱们建立文档的工做量,同时说明内容又整合入实现代码中,让维护文档和修改代码整合为一体,可让咱们在修改代码逻辑的同时方便的修改文档说明。另外Swagger2也提供了强大的页面测试功能来调试每一个RESTful API。具体效果以下图所示:
下面来具体介绍,若是在Spring Boot中使用Swagger2。首先,咱们须要一个Spring Boot实现的RESTful API工程,这里使用前面的为用户构建RESTFul API的那个工程来持续开发。
在pom.xml
中加入Swagger2的依赖
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.2.2</version> </dependency>
在Springboot的入口函数类SbDemoApplication
同级建立Swagger2的配置类SwaggerConfig
。其代码以下:
@Configuration //定义这是一个spring的配置类 @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo())//API的说明信息 .select() //选定要生成API的接口或者类的父包 .apis(RequestHandlerSelectors.basePackage("com.dengcl.sb_demo.controller")) .paths(PathSelectors.any()) //路径规则,这里是包中全部 .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("XXXX项目接口说明文档,使用Swagger2实现") .description("XXXX项目接口说明文档的描述信息") .termsOfServiceUrl("http://127.0.0.1:8080") .contact("后台研发团队") .version("0.0.1-SNAPSHOT") .build(); } }
如上代码所示,经过@Configuration
注解,让Spring来加载该类配置。再经过@EnableSwagger2
注解来启用Swagger2。
再经过createRestApi
函数建立Docket
的Bean
以后,apiInfo()
用来建立该Api的基本信息(这些基本信息会展示在文档页面中)。select()
函数返回一个ApiSelectorBuilder
实例用来控制哪些接口暴露给Swagger来展示,本例采用指定扫描的包路径来定义,Swagger会扫描该包下全部Controller
定义的API,并产生文档内容(除了被@ApiIgnore
指定的请求)
启动spring boot应用,在浏览器输入:localhost:8080/swagger-ui.html 就能够查看接口文档了,而且能够在该文档上执行API进行测试验证。以下图所示:
虽然在完成了上述配置后,已经能够生产文档内容,可是这样的文档主要针对请求自己,而描述主要来源于函数等命名产生,对用户并不友好,咱们一般须要本身增长一些说明来丰富文档内容。以下所示,咱们经过@ApiOperation
注解来给API增长说明、经过@ApiImplicitParams
、@ApiImplicitParam
注解来给参数增长说明。
@RestController @RequestMapping(value="/users") // 经过这里配置使下面的映射都在/users下 public class UserController { // 建立线程安全的Map static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>()); @ApiOperation(value="获取用户列表", notes="") @RequestMapping(value={""}, method=RequestMethod.GET) public List<User> getUserList() { List<User> r = new ArrayList<User>(users.values()); return r; } @ApiOperation(value="建立用户", notes="根据User对象建立用户") @ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User") @RequestMapping(value="", method=RequestMethod.POST) public String postUser(@RequestBody User user) { users.put(user.getId(), user); return "success"; } @ApiOperation(value="获取用户详细信息", notes="根据url的id来获取用户详细信息") @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long") @RequestMapping(value="/{id}", method=RequestMethod.GET) public User getUser(@PathVariable Long id) { return users.get(id); } @ApiOperation(value="更新用户详细信息", notes="根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息") @ApiImplicitParams({ @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long"), @ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User") }) @RequestMapping(value="/{id}", method=RequestMethod.PUT) public String putUser(@PathVariable Long id, @RequestBody User user) { User u = users.get(id); u.setName(user.getName()); u.setAge(user.getAge()); users.put(id, u); return "success"; } @ApiOperation(value="删除用户", notes="根据url的id来指定删除对象") @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long") @RequestMapping(value="/{id}", method=RequestMethod.DELETE) public String deleteUser(@PathVariable Long id) { users.remove(id); return "success"; } }
完成上述代码添加上,启动Spring Boot程序,访问:http://localhost:8080/swagger-ui.html
。就能看到前文所展现的RESTful API的页面。咱们能够再点开具体的API请求,以POST类型的/users请求为例,可找到上述代码中咱们配置的Notes信息以及参数user的描述信息,以下图所示。
在上图请求的页面中,咱们看到user的Value是个输入框?是的,Swagger除了查看接口功能外,还提供了调试测试功能,咱们能够点击上图中右侧的Model Schema(黄色区域:它指明了User的数据结构),此时Value中就有了user对象的模板,咱们只须要稍适修改,点击下方“Try it out!”按钮,便可完成了一次请求调用!
此时,你也能够经过几个GET请求来验证以前的POST请求是否正确。
相比为这些接口编写文档的工做,咱们增长的配置内容是很是少并且精简的,对于原有代码的侵入也在忍受范围以内。所以,在构建RESTful API的同时,加入swagger来对API文档进行管理,是个不错的选择。
咱们在作Web应用的时候,请求处理过程当中发生错误是很是常见的状况。Spring Boot提供了一个默认的映射:/error
,当处理中抛出异常以后,会转到该请求中处理,而且该请求有一个全局的错误页面用来展现异常内容。
选择上一章节实现过的Web应用为基础,启动该应用,访问一个不存在的URL,或是修改处理内容,直接抛出异常,如:
@RestController public class HelloWorld { @RequestMapping("/hello") public String sayHello(){ return "Hello World!"; } @RequestMapping("/happenError") public String happenError() throws Exception { throw new Exception("发生错误"); } }
此时启动spring boot,访问/happenError
,能够看到相似下面的报错页面,该页面就是Spring Boot提供的默认error映射页面。
虽然,Spring Boot中实现了默认的error映射,可是在实际应用中,上面你的错误页面对用户来讲并不够友好,咱们一般须要去实现咱们本身的异常提示。
下面咱们在上一章的应用中来进行统一异常处理的改造。
建立全局异常处理类:经过使用@ControllerAdvice
定义统一的异常处理类,而不是在每一个Controller中逐个定义。@ExceptionHandler
用来定义函数针对的异常类型,最后将Exception
对象和请求URL映射到error.html
中
@ControllerAdvice class GlobalExceptionHandler { public static final String DEFAULT_ERROR_VIEW = "error"; @ExceptionHandler(value = Exception.class) public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { ModelAndView mav = new ModelAndView(); mav.addObject("exception", e); mav.addObject("url", req.getRequestURL()); mav.setViewName(DEFAULT_ERROR_VIEW); return mav; } }
实现error.html
页面展现:在templates
目录下建立error.html
,将请求的URL
和Exception
对象的message
输出。
<!DOCTYPE html> <html> <head lang="en" > <meta charset="UTF-8" /> <title>统一异常处理</title> </head> <body> <h1>Error Handler</h1> <div th:text="${url}"></div> <div th:text="${exception.message}"></div> </body> </html>
启动该应用,访问:http://localhost:8080/happenError,能够看到以下错误提示页面。
经过实现上述内容以后,咱们只须要在Controller
中抛出Exception
,固然咱们可能会有多种不一样的Exception
。而后在@ControllerAdvice
类中,根据抛出的具体Exception
类型匹配@ExceptionHandle
r中配置的异常类型来匹配错误映射和处理。
在上述例子中,经过@ControllerAdvice
统必定义不一样Exception
映射到不一样错误处理页面。而当咱们要实现RESTful API时,返回的错误是JSON
格式的数据,而不是HTML
页面,这时候咱们也能轻松支持。
本质上,只需在@ExceptionHandler
以后加入@ResponseBody
,就能让处理函数return的内容转换为JSON
格式。
下面以一个具体示例来实现返回JSON
格式的异常处理。
public class ErrorInfo<T> { public static final Integer OK = 0; public static final Integer ERROR = 100; private Integer code; private String message; private String url; private T data; // 省略getter和setter }
@ControllerAdvice class GlobalExceptionHandler { public static final String DEFAULT_ERROR_VIEW = "error"; @ExceptionHandler(value = Exception.class) @ResponseBody //异常时响应JSON public ErrorInfo defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { ErrorInfo errorInfo = new ErrorInfo(); errorInfo.setUrl(req.getRequestURL().toString()); //NoHandlerFoundException对应的是404异常 if(e instanceof NoHandlerFoundException){ errorInfo.setCode(404); errorInfo.setMessage("页面找不到"); }else{ errorInfo.setCode(500); errorInfo.setMessage(e.getMessage()); } return errorInfo; } }
#出现错误时, 直接抛出异常 spring.mvc.throw-exception-if-no-handler-found=true #不要为咱们工程中的资源文件创建映射, spring.resources.add-mappings=false
说明: spring boot中发生404,默认处理是servlet容器的404处理方案,而不是抛出异常,须要增长上面的配置后才能处理404.
设置spring.resources.add-mappings=false
后,客户端不能直接访问工程中的资源文件了。
若是咱们访问一个不存在映射地址时,以下:
至此,已完成在Spring Boot中建立统一的异常处理,实际实现仍是依靠Spring MVC的注解,更多更深刻的使用可参考Spring MVC的文档。
Spring Boot在全部内部日志中默认使用Commons Logging,可是默认配置也提供了对经常使用日志的支持,如:Java Util Logging
,Log4J
, Log4J2
和Logback
。每种Logger均可以经过配置使用控制台或者文件输出日志内容。
默认的日志输出以下:
2018-03-16 11:01:19.728 INFO 10600 --- [ main] d.s.w.p.DocumentationPluginsBootstrapper : Context refreshed
输出内容元素具体以下:
在Spring Boot中默认配置了ERROR
、WARN
和INFO
级别的日志输出到控制台。
咱们能够经过两种方式切换至DEBUG
级别:
--debug
标志,如:java -jar myapp.jar --debug
application.properties
中配置debug=true
,该属性置为true
的时候,核心Logger(包含嵌入式容器、hibernate、spring)会输出更多内容,可是你本身应用的日志并不会输出为DEBUG级别。若是你的终端支持ANSI
,设置彩色输出会让日志更具可读性。经过在application.properties
中设置spring.output.ansi.enabled
参数来支持。
Spring Boot默认配置只会输出到控制台,并不会记录到文件中,可是咱们一般生产环境使用时都须要以文件方式记录。
若要增长文件输出,须要在application.properties
中配置logging.file
或logging.path
属性。
logging.file=my.log
logging.path=/var/log
日志文件会在10Mb大小的时候被截断,产生新的日志文件,默认级别为:ERROR、WARN、INFO
在Spring Boot中只须要在application.properties
中进行配置完成日志记录的级别控制。
配置格式:logging.level.*=LEVEL
*
为包名或Logger名举例:
logging.level.com.dengcl=DEBUG
:com.dengcl
包下全部class以DEBUG级别输出logging.level.root=WARN
:root日志以WARN级别输出因为日志服务通常都在ApplicationContext
建立前就初始化了,它并非必须经过Spring的配置文件控制。所以经过系统属性和传统的Spring Boot外部配置文件依然能够很好的支持日志控制和管理。
根据不一样的日志系统,你能够按以下规则组织配置文件名,就能被正确加载:
|日志系统|配置文件名|
|---|----|
|Logback|logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy|
|Log4j|log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml|
|Log4j2|log4j2-spring.xml, log4j2.xml|
|JDK (Java Util Logging)|logging.properties|
Spring Boot官方推荐优先使用带有-spring
的文件名做为你的日志配置(如使用logback-spring.xml
,而不是logback.xml
)
在Spring Boot中能够经过在application.properties
配置以下参数控制输出格式:
logging.pattern.console
:定义输出到控制台的样式(不支持JDK Logger)logging.pattern.file
:定义输出到文件的样式(不支持JDK Logger)AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,经过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是Spring框架中的一个重要内容,它经过对既有程序定义一个切入点,而后在其先后切入不一样的执行内容,好比常见的有:打开数据库链接/关闭数据库链接、打开事务/关闭事务、记录日志等。基于AOP不会破坏原来程序逻辑,所以它能够很好的对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度下降,提升程序的可重用性,同时提升了开发的效率。
下面主要讲两个内容,一个是如何在Spring Boot中引入Aop功能,二是如何使用Aop作切面去统一处理Web请求的日志。
由于须要对web请求作切面来记录日志,因此先引入web模块,并建立一个简单的hello请求的处理。
pom.xml中引入web模块
在Spring Boot中引入AOP就跟引入其余模块同样,很是简单,只须要在pom.xml中加入以下依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
在完成了引入AOP依赖包后,通常来讲并不须要去作其余配置。也许在Spring中使用过注解配置方式的人会问是否须要在程序主类中增长@EnableAspectJAutoProxy
来启用,实际并不须要。
能够看下面关于AOP的默认配置属性,其中spring.aop.auto
属性默认是开启的,也就是说只要引入了AOP依赖后,默认已经增长了@EnableAspectJAutoProxy
。
# AOP spring.aop.auto=true # Add @EnableAspectJAutoProxy. spring.aop.proxy-target-class=false # Whether subclass-based (CGLIB) proxies are to be created (true) as opposed to standard Java interface-based proxies (false).
而当咱们须要使用CGLIB来实现AOP的时候,须要配置spring.aop.proxy-target-class=true
,否则默认使用的是标准Java的实现AOP。
实现AOP的切面主要有如下几个要素:
@Aspect
注解将一个java类定义为切面类@Pointcut
定义一个切入点,能够是一个规则表达式,好比下例中某个package下的全部函数,也能够是一个注解等。@Before
在切入点开始处切入内容@After
在切入点结尾处切入内容@AfterReturning
在切入点return内容以后切入内容(能够用来对处理返回值作一些加工处理)@Around
在切入点先后切入内容,并本身控制什么时候执行切入点自身的内容@AfterThrowing
用来处理当切入内容部分抛出异常以后的处理逻辑切面类代码以下:
@Aspect @Component public class WebLogAspect { private Logger logger = Logger.getLogger(this.getClass()); @Pointcut("execution(public * com.dengcl.sb_demo.controller.*.*(..))") public void pt(){} @Before("pt()") public void doBefore(JoinPoint joinPoint) throws Throwable { // 接收到请求,记录请求内容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 记录下请求内容 logger.info("URL : " + request.getRequestURL().toString()); logger.info("HTTP_METHOD : " + request.getMethod()); logger.info("IP : " + request.getRemoteAddr()); logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs())); } @AfterReturning(returning = "ret", pointcut = "pt()") public void doAfterReturning(Object ret) throws Throwable { // 处理完请求,返回内容 logger.info("RESPONSE : " + ret); } }
能够看上面的例子,经过·@Pointcut·定义的切入点为com.dengcl.sb_demo.controller
包下的全部函数(对web层全部请求处理作切入点),而后经过@Befor
e实现,对请求内容的日志记录(本文只是说明过程,能够根据须要调整内容),最后经过@AfterReturning
记录请求返回的对象。
经过运行程序并访问:http://localhost:8080/users/
,能够得到下面的日志输出
2018-03-16 11:01:32.662 INFO 10600 --- [nio-8080-exec-1] com.dengcl.sb_demo.advice.WebLogAspect : URL : http://localhost:8080/users 2018-03-16 11:01:32.662 INFO 10600 --- [nio-8080-exec-1] com.dengcl.sb_demo.advice.WebLogAspect : HTTP_METHOD : GET 2018-03-16 11:01:32.663 INFO 10600 --- [nio-8080-exec-1] com.dengcl.sb_demo.advice.WebLogAspect : IP : 0:0:0:0:0:0:0:1 2018-03-16 11:01:32.663 INFO 10600 --- [nio-8080-exec-1] com.dengcl.sb_demo.advice.WebLogAspect : CLASS_METHOD : com.dengcl.sb_demo.controller.UserController.getUserList 2018-03-16 11:01:32.665 INFO 10600 --- [nio-8080-exec-1] com.dengcl.sb_demo.advice.WebLogAspect : ARGS : [] 2018-03-16 11:01:32.675 INFO 10600 --- [nio-8080-exec-1] com.dengcl.sb_demo.advice.WebLogAspect : RESPONSE : []
在WebLogAspect切面中,分别经过doBefore
和doAfterReturning
两个独立函数实现了切点头部和切点返回后执行的内容,若咱们想统计请求的处理时间,就须要在doBefore
处记录时间,并在doAfterReturning
处经过当前时间与开始处记录的时间计算获得请求处理的消耗时间。
那么咱们是否能够在WebLogAspect
切面中定义一个成员变量来给doBefore
和doAfterReturning
一块儿访问呢?是否会有同步问题呢?
的确,直接在这里定义基本类型会有同步问题,因此咱们能够引入ThreadLocal
对象,像下面这样进行记录:
@Aspect @Component public class WebLogAspect { private Logger logger = Logger.getLogger(getClass()); ThreadLocal<Long> startTime = new ThreadLocal<>(); @Pointcut("execution(public * com.dengcl.sb_demo.controller.*.*(..))") public void webLog(){} @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { startTime.set(System.currentTimeMillis()); // 省略日志记录内容 } @AfterReturning(returning = "ret", pointcut = "webLog()") public void doAfterReturning(Object ret) throws Throwable { // 处理完请求,返回内容 logger.info("RESPONSE : " + ret); logger.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get())); } }
因为经过AOP实现,程序获得了很好的解耦,可是也会带来一些问题,好比:咱们可能会对Web层作多个切面,校验用户,校验头信息等等,这个时候常常会碰到切面的处理顺序问题。
因此,咱们须要定义每一个切面的优先级,咱们须要@Order(i)
注解来标识切面的优先级。i的值越小,优先级越高。假设咱们还有一个切面是CheckNameAspect
用来校验name
必须为didi
,咱们为其设置@Order(10)
,而上文中WebLogAspect
设置为@Order(5)
,因此WebLogAspect
有更高的优先级,这个时候执行顺序是这样的:
@Before
中优先执行@Order(5)
的内容,再执行@Order(10)
的内容@After
和@AfterReturning
中优先执行@Order(10)
的内容,再执行@Order(5)
的内容因此咱们能够这样子总结:
pom.xml
中引入依赖
引入链接mysql
的必要依赖mysql-connector-java
引入整合MyBatis
的核心依赖mybatis-spring-boot-starter
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.45</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency>
在application.properties
中配置mysql
的链接配置
spring.datasource.url=jdbc:mysql://localhost:3306/users spring.datasource.username=root spring.datasource.password=12345678 spring.datasource.driver-class-name=com.mysql.jdbc.Driver
简单且简洁的的完成了基本配置,下面看看如何在这个基础下轻松方便的使用MyBatis访问数据库。
在Mysql中建立User
表,包含id(BIGINT)、name(INT)、age(VARCHAR)字段。同时,建立映射对象User
,这里再也不列出建表和映射对象的代码。
建立User映射的操做UserMapper.java
,为了后续单元测试验证,实现插入和查询操做。
@Mapper public interface UserMapper { @Select("SELECT * FROM USERS WHERE NAME = #{name}") User findByName(@Param("name") String name); @Insert("INSERT INTO USERS(NAME, AGE) VALUES(#{name}, #{age})") int insert(@Param("name") String name, @Param("age") Integer age); }
@Mapper
定义本接口是一个mybatis的mapper接口类。
建立单元测试
@RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper mapper; @Test @Transactional @Rollback public void findByName() throws Exception { mapper.insert("AAA", 20); User u = mapper.findByName("AAA"); Assert.assertEquals(20, u.getAge().intValue()); } }
前面的示例是经过注解的方式来实现mybatis的mapper,可是在复杂的应用场景下,基于xml的mapper更加灵活,更方便维护。
UserMapper
,去掉注解@Mapper public interface UserMapper { // @Select("SELECT * FROM USERS WHERE NAME = #{name}") User findByName(@Param("name") String name); // @Insert("INSERT INTO USERS(NAME, AGE) VALUES(#{name}, #{age})") int insert(@Param("name") String name, @Param("age") Integer age); }
UserMapper.xml
,实现mybatis映射<?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="com.dengcl.sb_demo.mapper.UserMapper"> <select id="findByName" resultType="com.dengcl.sb_demo.pojo.User"> SELECT * FROM USERS WHERE NAME = #{name} </select> <insert id="insert" > INSERT INTO USERS(NAME, AGE) VALUES(#{name}, #{age}) </insert> </mapper>
在application.properties
文件中增长以下配置:
mybatis.mapper-locations=classpath:/com/dengcl/sb_demo/mapper/*.xml
在Spring Boot中,当咱们使用了spring-boot-starter-jdbc
依赖的时候,框架会自动默认分别注入DataSourceTransactionManager
或JpaTransactionManager
。因此咱们不须要任何额外配置就能够用@Transactional
注解进行事务的使用。
注意:在上面的示例中咱们虽然未使用
spring-boot-starter-jdbc
依赖,但也能够直接使用事务,缘由是mybatis-spring-boot-starter
中已经包含了此依赖
例如:
@Test @Transactional public void batchAdd(){ mapper.insert("aaa",12); mapper.insert("aaaa",13); mapper.insert("aaaaa",14); mapper.insert("aaaaaa",15); mapper.insert("aaaaaaa",16); }
该方法中的多个数据操做就在同一个事务中。
这里是经过单元测试演示了如何使用@Transactional
注解来声明一个方法须要被事务管理,一般咱们单元测试为了保证每一个测试之间的数据独立,会使用@Rollback
注解让每一个单元测试都能在结束时回滚。而真正在开发业务逻辑时,咱们一般在service
层接口中使用@Transactional
来对各个业务逻辑进行事务管理的配置,例如:
public interface UserService { @Transactional User login(String name, String password); }
上面的例子中咱们使用了默认的事务配置,能够知足一些基本的事务需求,可是当咱们项目较大较复杂时(好比,有多个数据源等),这时候须要在声明事务时,指定不一样的事务管理器。在声明事务时,只须要经过value属性指定配置的事务管理器名便可,例如:@Transactional(value="transactionManagerPrimary")
。
除了指定不一样的事务管理器以后,还能对事务进行隔离级别和传播行为的控制,下面分别详细解释:
隔离级别是指若干个并发的事务之间的隔离程度,与咱们开发时候主要相关的场景包括:脏读取、重复读、幻读。
咱们能够看org.springframework.transaction.annotation.Isolation枚举类中定义了五个表示隔离级别的值:
public enum Isolation { DEFAULT(-1), READ_UNCOMMITTED(1), READ_COMMITTED(2), REPEATABLE_READ(4), SERIALIZABLE(8); }
DEFAULT
:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,一般这值就是READ_COMMITTED
。READ_UNCOMMITTED
:该隔离级别表示一个事务能够读取另外一个事务修改但尚未提交的数据。该级别不能防止脏读和不可重复读,所以不多使用该隔离级别。READ_COMMITTED
:该隔离级别表示一个事务只能读取另外一个事务已经提交的数据。该级别能够防止脏读,这也是大多数状况下的推荐值。REPEATABLE_READ
:该隔离级别表示一个事务在整个过程当中能够屡次重复执行某个查询,而且每次返回的记录都相同。即便在屡次查询之间有新增的数据知足该查询,这些新增的记录也会被忽略。该级别能够防止脏读和不可重复读。SERIALIZABLE
:全部的事务依次逐个执行,这样事务之间就彻底不可能产生干扰,也就是说,该级别能够防止脏读、不可重复读以及幻读。可是这将严重影响程序的性能。一般状况下也不会用到该级别。指定方法:经过使用isolation
属性设置事务的隔离级别,例如:
@Transactional(isolation = Isolation.DEFAULT)
所谓事务的传播行为是指,若是在开始当前事务以前,一个事务上下文已经存在,此时有若干选项能够指定一个事务性方法的执行行为。
咱们能够看org.springframework.transaction.annotation.Propagation
枚举类中定义了6个表示传播行为的枚举值:
public enum Propagation { REQUIRED(0), SUPPORTS(1), MANDATORY(2), REQUIRES_NEW(3), NOT_SUPPORTED(4), NEVER(5), NESTED(6); }
REQUIRED
:若是当前存在事务,则加入该事务;若是当前没有事务,则建立一个新的事务。SUPPORTS
:若是当前存在事务,则加入该事务;若是当前没有事务,则以非事务的方式继续运行。MANDATORY
:若是当前存在事务,则加入该事务;若是当前没有事务,则抛出异常。REQUIRES_NEW
:建立一个新的事务,若是当前存在事务,则把当前事务挂起。NOT_SUPPORTED
:以非事务方式运行,若是当前存在事务,则把当前事务挂起。NEVER
:以非事务方式运行,若是当前存在事务,则抛出异常。NESTED
:若是当前存在事务,则建立一个事务做为当前事务的嵌套事务来运行;若是当前没有事务,则该取值等价于REQUIRED
。指定方法:经过使用propagation属性设置事务的传播行为,例如:
@Transactional(propagation = Propagation.REQUIRED)