事务支持
修改IUserService接口,增加一个新方法batchAdd,在UserServiceImpl增加相应实现类,在实现类中故意产生一个被0整除的异常。
public interface IUserService {
boolean login(String username,String passwd);
boolean register(String username,String passwd);
void batchAdd(String username, String passwd);
}
在UseServerImpl中实现方法
@Override
public void batchAdd(String username, String passwd) {
User user = new User();
user.setUsername(username);
user.setPasswd(passwd);
userMapper.insertSelective(user);
//异常不会再执行下面的代码,这个时候判断上面的是否插入到了数据库
int i = 10/0;
user = new User();
user.setUsername(username + "2");
user.setPasswd(passwd);
userMapper.insertSelective(user);
}
修改UserContoller,增加batchAdd方法
@RequestMapping("/batchAdd")
public String batchAdd(String username, String passwd){
iUserService.batchAdd(username, passwd);
return "成功";
}
重新运行,在浏览器上输入:localhost:8080/batchAdd?username=zhangsan&passwd=123
可以发现在浏览器上出现
检查数据库,发现表里面已经产生了一个错误的数据,产生了事务问题。
如何开启事务:
在batchAdd方法上增加@Transactional注解,重启服务后,在浏览器上输入:
http://localhost:8080/batchAdd?username=lisi&passwd=123
浏览器还继续报错,但检查数据库,事务问题已经得到了解决。
全局异常处理
通过上面步骤,虽然已经解决了事务问题,但界面上出现这500错误,这对用户来说还是不友好。所以创建一个专门的异常类来统一处理。
package com.bruce.controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = RuntimeException.class) //捕获运行时异常
@ResponseBody
public Object defaultErrorHandler(HttpServletRequest request, Exception e) throws Exception{
e.printStackTrace();
return "我是一个异常处理类";
}
}
重启服务后,在浏览器上输入会出现异常的服务
localhost:8080/batchAdd?username=zhaosi&passwd=123
界面返回:
集成Swagger2构建API文档
Swagger2 的作用:
随项目自动生成强大RESTful API文档,减少工作量
API文档与代码整合在一起,便于同步更新API说明
页面测试功能来调试每个RESTful API
修改pom文件,添加swagger2的相关依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
新建一个swagger的配置类SwaggerConfig.java
package com.bruce.utils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.bruce.controller"))// 指定扫描包下面的注解
.paths(PathSelectors.any()) // 可以根据url路径设置哪些请求加入文档,忽略哪些请求
.build();
}
// 创建api的基本信息
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("集成Swagger2构建RESTful APIs") //设置文档的标题
.description("集成Swagger2构建RESTful APIs") // 设置文档的描述
.termsOfServiceUrl("http://www.bruce.com/") // 设置文档的License信息->1.3 License information
.contact(new Contact("bruce","com.bruce","bruce@163.com"))
.version("1.0.0") // 设置文档的版本信息-> 1.0.0 Version information
.build();
}
}
新建Controller用于显示相关接口
package com.bruce.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@Api("swagger文档")
@RequestMapping(value="/swagger")
public class SwaggerController {
@ApiOperation(value = "获取用户信息", notes = "根据id来获取用户详细信息")
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "String")
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Map<String, String> getInfo(@PathVariable String id) {
Map<String, String> map = new HashMap<String, String>();
map.put("name", "lison");
map.put("age", "38");
return map;
}
}
@Api:修饰整个类,描述Controller的作用
@ApiOperation:描述一个类的一个方法,或者说一个接口
@ApiParam:单个参数描述
@ApiModel:用对象来接收参数
@ApiProperty:用对象接收参数时,描述对象的一个字段
@ApiResponse:HTTP响应其中1个描述
@ApiResponses:HTTP响应整体描述
@ApiIgnore:使用该注解忽略这个API
@ApiError :发生错误返回的信息
@ApiImplicitParam:描述一个请求参数,可以配置参数的中文含义,还可以给参数设置默认值
@ApiImplicitParams:描述由多个 @ApiImplicitParam 注解的参数组成的请求参数列表
访问:http://localhost:8080/swagger-ui.html
日志集成
java有许多的日志组件,比如 log4j,log4j2,logback还有java自生提供的Java Util Logging,其实在springboot中对这些组件都提供了支持,log4j,log4j2和logback都提供相应的组件支持。
Logback
在springboot中默认使用的日志工具是logback,不过在提及具体的日志工具之前要提一个名词,这个名词就是slf4j(Simple Logging Facade For Java)。
slf4j不是具体的日志解决方案,它有点类似于jdbc,使用了门面模式,是一个针对各类日志的抽象实现,既然是抽象的日志实现,在springboot中肯定不需要额外导入。
注意:spring-boot-starter中就提供了对spring-boot-starter-logging的依赖
在spring-boot-starter-logging中可以看到以及集成了slf4j与具体实现logback的默认支持。
修改UserController
在浏览器上输入:localhost:8080/hello,可以看控制台日志的输出。
日志级别
修改controller 把日志的输出改成
logger.debug(“这个一个hello日志”);
这个时候重启,再调用,发现后台并不会有任何输出,这原因是日志级别在作祟。
默认情况下,Spring Boot 配置的是INFO 日志级别,也就是会输出INFO级别以上的日志(ERROR, WARN, INFO)。
如果需要 Debug 级别的日志。在 src/main/resources/application.yml中配置。
debug=true
此外,配置 logging.level.* 来具体输出哪些包的日志级别。
例如
logging:
level:
root: INFO
org:
springframework:
web: DEBUG
com:
bruce:
controller: DEBUG
这个时候,包括springframework.web以及cn.enjoy.controller的debug日志都可以输出来了。
日志文件
一般情况下,springboot日志只会输出到控制台,并不会写入到日志文件,但是,在一些正式环境的应用中,我们需要通过在 application.yml文件中配置 logging.file 文件名称和 logging.path 文件路径,将日志输出到日志文件中。
注意:
如果只配置 logging.file.path,在 /var/tmp文件夹生成一个日志文件为 spring.log。如果只配置 logging.file.name,会在项目的当前路径下生成一个 xxx.log 日志文件。
这里有一个坑,logging.file.path 和logging.file.name都配置了,只会有logging.file.name生效,所以,如果要指定日志生成的具体位置使用logging.file.path 配置就好。
这样在E盘的相应位置出现日志文件。
log4j2
在spring-boot-dependencies POMs中搜索spring-boot-starter-log4j2
发现Spring boot父Pom中自己提供了这个依赖,于是我们加入如下jar依赖:
修改pom.xml文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
注意: 由于默认使用logback在扩展log4j2之前先要把logback移除
日志使用跟上面logback一样。
使用AOP统一处理日志
为了防止在工作中经常在代码中加入大量的日志处理代码,在实际项目开发中,一般使用AOP统一完成日志处理工作
修改pom文件,引入springboot对aop的支持
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
新建AOP日志处理类
package com.bruce.utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
@Aspect
@Component
public class WebLogAspect {
private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
//切点
@Pointcut("execution(public * com.bruce.controller.*.*(..))")
public void webLog() {
}
@Before("webLog()")
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());
Enumeration<String> enu = request.getParameterNames();
//遍历所有的参数
while (enu.hasMoreElements()) {
String name = (String) enu.nextElement();
logger.info("name:{},value:{}", name, request.getParameter(name));
}
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
logger.info("RESPONSE : " + ret);
}
}