一、构建SpringBoot框架
1.因为我的idea有bug无法使用Initializr快速搭建SpringBoot框架,所以使用maven创建项目再进行依赖导入
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>blog</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<!--父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>0.10.0</version>
</dependency>
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark-ext-heading-anchor</artifactId>
<version>0.10.0</version>
</dependency>
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark-ext-gfm-tables</artifactId>
<version>0.10.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 打jar包时如果不配置该插件,打出来的jar包没有清单文件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.5.RELEASE</version>
</plugin>
</plugins>
</build>
</project>
2.配置yml文件
在开发项目的时候,一般开发环境和部署环境会不一样,为了加以区分,可以在yml配置文件中体现出来,所以分为application-dev.yml(开发环境)application-pro.yml(部署环境),而为了能够让SpringBoot知道用的是哪个配置文件,需要在application.yml配置文件中加以说明,并且开发和部署中相同的配置也可以在application.yml中进行配置,详细配置如下:
- application.yml:公共配置和表明当前配置文件
- application-dev.yml:开发环境配置文件
- application-pro.yml:部署环境配置文件
application.yml:
spring:
thymeleaf:
prefix: classpath:/templates/
suffix: .html
mode: HTML
profiles:
active: dev
application-dev.yml:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT
username: root
password: root
jpa:
hibernate:
ddl-auto: update
show-sql: true
logging:
level:
root: info
com.example: debug
file: log/blog-dev.log #指定日志生成路径
application-pro.yml:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT
username: root
password: root
jpa:
hibernate:
ddl-auto: none
show-sql: true
logging:
level:
root: warn
com.example: info
file: log/blog-pro.log
server:
port: 8081
实际上开发环境和运行环境数据库账户密码应该是不同的
3.重写SpringBoot默认日志配置
自定义日志大小和名称等等,在资源文件夹下添加logback-spring.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!--包含Spring boot对logback日志的默认配置-->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<!--重写了Spring Boot框架 org/springframework/boot/logging/logback/file-appender.xml 配置-->
<appender name="TIME_FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i</fileNamePattern>
<!--保留历史日志一个月的时间-->
<maxHistory>30</maxHistory>
<!--
Spring Boot默认情况下,日志文件10M时,会切分日志文件,这样设置日志文件会在100M时切分日志
-->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="TIME_FILE" />
</root>
</configuration>
<!--
1、继承Spring boot logback设置(可以在appliaction.yml或者application.properties设置logging.*属性)
2、重写了默认配置,设置日志文件大小在100MB时,按日期切分日志,切分后目录:
......
-->
二、异常处理
SpringBoot框架提供了处理错误页面的方法,出现请求的网页不存在返回对应的404;出现服务器遇到错误,无法完成请求,返回对应500页面。
1.定义错误页面
在前端文件夹templates下的error文件夹创建异常页面:
- 404.html
- 505.html
- error.html
SpringBoot可通过文件夹的名称和错误的类型找到相应的异常页面
2.全局异常处理
创建自定义拦截器ControllerExceptionHandler ,通过这个类拦截所有异常
代码如下:
package com.example.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
/**
* 拦截所有异常 进行统一异常处理
*/
@ControllerAdvice //拦截所有Controller注解 实现全局异常处理
public class ControllerExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass()); //获取日志记录器
@ExceptionHandler(Exception.class) // ExceptionHandler 用来指明异常的处理类型 拦截Exception级别的异常
public ModelAndView exceptionHander(HttpServletRequest request, Exception e) throws Exception {
//记录异常信息
logger.error("Requst URL : {},Exception : {}", request.getRequestURL(),e);
//判断是否存在 状态码 存在即抛给springboot自行处理 传递异常类和状态
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
ModelAndView mv = new ModelAndView();
mv.addObject("url",request.getRequestURL());
mv.addObject("exception", e);
mv.setViewName("error/error");
return mv;
}
}
标识状态码的不进行拦截
AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null
3.资源找不到异常处理
对于资源找不到异常,一般也是要跳转到404页面的,这里就需要自定义一个异常类,专门用来应对资源找不到,新建NotFoundException类。
package com.example;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
*异常处理 跳转页面
*/
@ResponseStatus(HttpStatus.NOT_FOUND) //指定返回状态码
public class NotFoundException extends RuntimeException {
public NotFoundException() {
}
public NotFoundException(String message) {
super(message);
}
public NotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
必须在自定义拦截器ControllerExceptionHandler中判断是否存在状态码,存在的话将异常抛出给springboot处理
//判断是否存在 状态码 存在即抛给springboot自行处理 传递异常类和状态
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
RuntimeException:运行时异常,这种异常我们不需要处理,完全由虚拟机接管。
ControllerExceptionHandler 继承RuntimeException,实现继承RuntimeException的构造函数;并且返回指定的状态码(此处返回状态码方便在 全局异常处理类ControllerExceptionHandler中排除含有状态码的异常)
三、日志处理
使用SpringBoot的AOP进行日志处理,AOP可以以切面的形式拦截,将日志内容记录下来,这里记录以下日志信息:
- 访问的URL
- 访问者的IP
- 访问时调用的方法
- 访问时传递的参数
- 访问时返回的内容
创建切面类LogAspect:
package com.example.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
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;
/**
* 日志 aop
*/
@Aspect
@Component
public class LogAspect {
//获取日志信息
private final Logger logger = LoggerFactory.getLogger(this.getClass());
// 定义切面,声明log是一个切面
@Pointcut("execution(* com.example.web.*.*(..))")
public void log(){}
// 在切面之前执行 获取请求的信息
@Before("log()")
public void doBefore(JoinPoint joinPoint){
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String url = request.getRequestURL().toString();
String ip = request.getRemoteAddr();
String classMethod = joinPoint.getSignature().getDeclaringTypeName() +"." + joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
RequestLog requestLog = new RequestLog(url,ip,classMethod,args);
logger.info("Request : {}",requestLog);
}
//切面之后执行
@After("log()")
public void doAfter(){
// logger.info("----------------After-----------");
}
//返回之后拦截 获取返回的信息
@AfterReturning(returning = "result", pointcut = "log()")
public void doAfterRuturn(Object result){
logger.info("Result:{ }",result);
}
private class RequestLog{
private String url;
private String ip;
private String classMethod; //调用方法
private Object[] args; //请求参数
public RequestLog(String url, String ip, String classMethod, Object[] args) {
this.url = url;
this.ip = ip;
this.classMethod = classMethod;
this.args = args;
}
@Override
public String toString() {
return "Request{" +
"url='" + url + '\'' +
", ip='" + ip + '\'' +
", classMethod='" + classMethod + '\'' +
", args='" + args + '\'' +
'}';
}
}
}
- @Aspect注解:AOP切面作用
- @Component注解:开启组件扫描,通过注解找到要扫描的对象
- @Pointcut(“execution(* com.example.web…(…))”):定义切面,申明log()是一个切面,通过execution来表示需要拦截的类,这里表示拦截控制器下面的所有类所有方法
- RequestLog:将请求的参数封装成一个内部类
- 在访问页面(controller)之前,拦截请求的URL、IP、调用的方法、传递的参数、返回的内容,并记录到日志