经过前面的设计,搭建好数据库后,就进入到开发过程。
前端页面直接在oneStar老哥的博客中下载,导入即可。前期不建议直接用有模板引擎的完整前端代码,可以先使用没有thymeleaf的静态页面,当需要编写哪个页面的业务时,再逐个引入(不然在没有数据的又访问了页面的情况会报错)。
一、构建springboot框架
1. 使用idea搭建springboot项目,引入相应模块
使用idea新建springboot项目
选择springboot版本,目前先引入如下依赖,后续还会不断添加。
创建好项目后,初始结构如下,笔者使用的是yml配置文件,所以先把application.properties改成application.yml,然后就可以进行相关配置。
2.配置springboot配置文件
跟oneStar博客的操作一样,配置了开发(application-dev.yml)和部署(application-pro.yml)两个不同生产环境的配置文件,最后也可以在application.yml这个总的配置中就进行切换。
- application.yml
其实上新版thymeleaf引入后,可以不需要配置也能够使用。
默认的服务端口为 8080(可以自定义)。
# 公共配置和表明当前配置文件
spring:
profiles:
active: dev
#thymeleaf新版不需要配什么
thymeleaf:
prefix: classpath:/templates/ #prefix:指定模板所在的目录
check-template-location: true #check-tempate-location: 检查模板路径是否存在
cache: false #cache: 是否缓存,开发模式下设置为false,避免改了模板还要重启服务器,线上设置为true,可以提高性能。
suffix: .html
encoding: UTF-8
#content-type: text/html
#配置mybatis
mybatis:
#别名(后续除了entity,dto也需要配置别名,多个包需要这样分隔 ,;)
type-aliases-package: com.hxj.entity ,;com.hxj.queryvo
#mapper地址(自动在resources下找)
mapper-locations: classpath:mybatis/mapper/*.xml
#受不了经常配resultmape,直接驼峰自动转换
configuration:
map-underscore-to-camel-case: true
然后配置mybatis配置,需要注意这里的mapper-locations路径,用户可以自定义,笔者使用这样的路径。
在开发和部署环境中,配置数据库连接和日志。
不同的环境可能需要不同的 端口号,数据库,日志的不同级别…。
- application-dev.yml
#配置数据库
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/myblog?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
#配置日志级别(整个项目/可以分开来设置不同的级别)
logging:
level:
root: info
com.hxj : debug
#日志文件的目录(指定文件夹下生成日志),默认超过多大就切分(也可以做个性化配置)
file:
path: log/blog-dev.log
- application-pro.yml
#配置数据库
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/myblog?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
#配置日志级别(整个项目/可以分开来设置不同的级别)
logging:
level:
root: warn
# 不让它打印那么多信息
com.hxj : info
#日志文件的目录(指定文件夹下生成日志),默认超过多大就切分
# (也可以做个性化配置,重写springboot日志配置 logback-spring.xml)
file:
path: log/blog-pro.log
server:
port: 8081
- 重写springboot日志配置
先在application.yml同理目录下创建 这样的文件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"/>
<!--上面是springboot默认集成的就有这些,我们需要拿过来覆盖,下面再写自定义的-->
<!--重写了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时,会切分日志文件,这样设置日志文件会在10M时切分日志
-->
<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、重写了默认配置,设置日志文件大小在10MB时,按日期切分日志 (也可以不用这配置文件,默认用大小切分)
-->
随着项目的运行,可以看到在dev和pro配置下打印的log日志以及在项目中自动生成一个log文件夹,存放dev和pro日志文件。运行哪个环境后的日志,就存到对应的文件夹中。
后续开发使用dev环境,可以看到更多的信息。
二、异常处理
1.定义错误页面
- 404.html
- 500.html
- error.html(自定义错误)
错误页面的实现在springboot里面很简单,在template下,写一个error目录(什么错误,里面写对应错误代码的html)
springboot帮我们做了很多静态资源的映射。正常情况, 我们会把页面 放到 template
文件夹中,但是template不是静态资源目录, 只能通过controller去映射,访问这个目录下需要模板引擎.(这就是需要Thymeleaf模板引擎的原因)
定义一个跳转首页的controller,对跳转首页进行测试。
@Controller
public class IndexController {
//通过get方式请求路径
@GetMapping("/")
public String index(){
return "index";
}
}
- 404测试,访问一个无效的后缀路径
localhost:8080/asdaf
,访问就会发现跳转到404错误页面。 - 500测试,(在服务器中)在controller中写一段错误代码
int a=1/0;
让服务器出错,在访问的localhost:8080
时,运行到错误代码时,就会跳转到500页面。(注释掉这段代码,再次访问会发现正常)
对于自定义的错误(我们想当发生一些错误时能进入自定义的一个错误页面),但springboot是不会有对应的错误代码,所以去找也找不到自定义页面。我们此时就需要做一个全局的异常处理,拦截所有的异常,再做一个统一的分发。
2.全局异常处理
在com.xx
包下创建一个handler
包,创建ControllerExceptionHandler
错误页面拦截器,通过定义这个类来拦截所有的异常,代码如下:
@ControllerAdvice //拦截所有有 @controller注解的 controller控制器
public class ControllerExceptionHandler {
//这里是应用切面处理异常,并且将处理的内容加入到日志里
private final Logger logger = LoggerFactory.getLogger(this.getClass());
//获取日志,记录我们的异常
//回顾spring mvc的视图解析器
@ExceptionHandler(Exception.class) //表示这个方法可以做拦截器,而且只要是exception级别的都可以拦截
public ModelAndView exceptionHandler(HttpServletRequest request,Exception e) throws Exception {
//记录error信息, 请求的url,异常信息 {}是占位
logger.error("Request URL : {}, Exception : {}",request.getRequestURI(),e);
//当标识了状态码的时候就不拦截(这样能让它不拦截404 500等)
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
//返回到错误页面(回顾mvc 返回页面)
ModelAndView mv = new ModelAndView();
mv.addObject("url", request.getRequestURI());
mv.addObject("exception", e);
mv.setViewName("error/error"); //统一拦截所有的exception,返回到error页面
return mv;
}
}
为了不把404和500错误也转入到自定义错误页面(error),需要做一些判断,当是这些错误时(即有错误代码时),不转入error错误页面。
通过判断标识了状态码的 异常就不会进行拦截, 该去哪里还是去哪.
这样我们这个统一拦截的页面处理类, 就可以 即能处理 自定义错误 也能处理 404和500错误.
3.自定义异常(资源找不到
对于资源找不到异常,一般也是要跳转到404页面的,这里就需要自定义一个异常类,专门用来应对资源找不到。
在com.xxx
包下面新建NotFoundException
类。
(当我们想让一些错误发生时,不走error页面,可以抛出一个404状态码的异常,走404页面)
@ResponseStatus(HttpStatus.NOT_FOUND) //想要返回404页面,就要指定对应的返回状态码
public class NotFoundException extends RuntimeException{
public NotFoundException() {
}
public NotFoundException(String message) {
super(message);
}
public NotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
疑惑
无论看视频还是在博客里面的解释,自己看的时候,对于为什么要把【资源找不到】也要做一个自定义异常类也感到不解?
原本资源找不到不就算是404么,为什么还要自定义。即使上面已经定义了全局异常处理还做了筛选,难道不就够了吗?
(在做完了项目后)实验了一下,果不其然,如果访问一个不存在的资源(访问了一个不存在的博客http://localhost:8080/blog/1000
),不需要抛出自定义错误,单靠全局异常的拦截器也能够成功找到404。
然后又在完成后的首页控制器中抛出一个不是404和500状态码标识的异常,这样它也会走到自定义错误页面。
在此以我的理解,虽然做了自定义错误类,但不仅是用到资源找不到的情况,可以对其他可能出现的情况,抛出这个异常走404页面,才有了这个“自定义的404标识的异常类“,把这种情况下的错误转到404页面,而不是error页面。
但是目前是没有太大用,用全局异常处理已经能完成绝大部分了。
但还是按正常流程写下记录,上面我的废话可以不用看(仅供参考)。
三、日志处理
采用SpringBoot中的AOP进行日志处理,AOP可以以切面的形式拦截,将日志内容记录下来,这里记录以下日志信息:
- 访问的URL
- 访问者的IP
- 访问时调用的方法
- 访问时传递的参数
- 访问时返回的内容
spring AOP处理
能以切面的方式拦截,所有的web控制器的请求的流,包括从前端返回的请求到业务处理… (就像一个流一样)。
aop 的使用就在这线一样 的 流上 切一刀
- 在pom.xml添加AOP的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 记录日志类
这里使用自定义类配合注解来实现AOP 【切面定义】。
在com.xxx
包下新建aspect
包,新建切面LogAspect
类。
在访问页面(controller)之前,拦截请求的URL、IP、调用的方法、传递的参数、返回的内容,并记录到日志中。
@Aspect
@Component
public class LogAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Pointcut("execution(* com.hxj.controller.*.*(..))") //这里改为用户自己的路径
// 拦截所有(所有的控制器,任何的"类"里面 的 任何参数的"方法" 任何返回值 都拦截了),然后就可以围绕切面做处理
public void log(){}
@Before("log()") //在切面(请求方法)之前
public void doBefore(JoinPoint joinPoint){
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//这样就可以拿到http参数了, ip url
HttpServletRequest request = attributes.getRequest();
String url = request.getRequestURI();
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("-------doAfter------------");
}
@AfterReturning(returning = "result",pointcut = "log()") //方法执行完 返回值之后,拦截
public void doAfterReturn(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 "RequestLog{" +
"url='" + url + '\'' +
", ip='" + ip + '\'' +
", classMethod='" + classMethod + '\'' +
", args=" + Arrays.toString(args) +
'}';
}
}
}
Aop中execution的语法
至此项目的基础框架搭建完成。(启动访问测试无误,日志输出没有问题后即可进入下一个阶段)