公司的很多项目都陆陆续续引入了Spring Boot,通过对Spring Boot的接触了解发现其真的是大大地简化了开发、简化了依赖配置,很多功能注解一下就可以实现,真的是太方便了。下面记录了一个Spring Boot的入门程序实现,包括过滤器、servlet、定时器、全局异常处理、日志、Druid数据源的SQL监控配置。
1,pom.xml文件:
4.0.0 com.zws spring-boot 0.0.1-SNAPSHOT jar spring-boot http://maven.apache.org UTF-8 org.springframework.boot spring-boot-starter-parent 1.5.10.RELEASE org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-logging org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter-jdbc org.mybatis.spring.boot mybatis-spring-boot-starter 1.3.1 com.alibaba druid-spring-boot-starter 1.1.0 mysql mysql-connector-java commons-logging commons-logging 1.2 org.slf4j slf4j-api log4j log4j 1.2.17 com.alibaba fastjson 1.2.46 junit junit test org.apache.maven.plugins maven-compiler-plugin 1.8 org.springframework.boot spring-boot-maven-plugin
可以看到这个文件内容并不多,但是却引入了很多需要的依赖。这里引入的Spring Boot版本为1.5.10.RELEASE,其中spring-boot-maven-plugin插件是Spring Boot提供的打包用的插件。mybatis-spring-boot-starter为MyBatis集成Spring Boot的依赖。druid-spring-boot-starter为阿里开源数据源Druid集成Spring Boot的依赖,它会下载对应的druid jar包,注意这里并没有直接引入Druid依赖,如果直接引入Druid依赖jar包,则druid的监控页面无法显示sql。
2,Spring Boot配置文件resources/application.properties:
server.port=8080 server.context-path=/app spring.mvc.throw-exception-if-no-handler-found=true spring.resources.add-mappings=false #tomcat访问日志 server.tomcat.basedir=logs server.tomcat.accesslog.enabled=true server.tomcat.accesslog.directory=access server.tomcat.accesslog.pattern=%t %a "%r" %s %b (%D ms) datasource.druid.url=jdbc:mysql://localhost/db_boot?useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false datasource.druid.username=root datasource.druid.password=root datasource.druid.driver-class-name=com.mysql.jdbc.Driver datasource.druid.initialSize=5 datasource.druid.minIdle=5 datasource.druid.maxActive=20 datasource.druid.maxWait=60000 datasource.druid.minEvictableIdleTimeMillis=300000 datasource.druid.validationQuery=select 'x' datasource.druid.testOnBorrow=false datasource.druid.testOnReturn=false datasource.druid.testWhileIdle=true datasource.druid.poolPreparedStatements=false datasource.druid.maxPoolPreparedStatementPerConnectionSize=100 datasource.druid.filters=stat,wall,log4j mybatis.config-location=classpath:mybatis/mybatis-config.xml mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
application.properties文件为Spring Boot的总配置文件,Spring Boot默认会加载resources目录下的application.properties文件。Spring Boot默认内嵌Tomcat作为web容器,server.port为端口,server.context-path为上下文路径。datasource开头的为druid数据源的配置。
3,MyBatis的配置文件resources/mybatis/mybatis-config.xml:
此配置文件的路径已经配置在了resources/application.properties文件内。
4,resources/mybatis/UserMapper.xml:
5,Sping Boot启动类Application.java:
package com.zws.be; import javax.sql.DataSource; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableScheduling; import com.alibaba.druid.pool.DruidDataSource; @SpringBootApplication @MapperScan(basePackages = {"com.zws.be.*.mapper"}) @ServletComponentScan @EnableScheduling public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean("dataSource") @ConfigurationProperties("datasource.druid") public DataSource getDruidDataSource() { return DataSourceBuilder.create().type(DruidDataSource.class).build(); } }
这个是程序启动的入口,里面有mian函数,运行此类即可启动Spring Boot。注解@MapperScan(basePackages = {"com.zws.be.*.mapper"})指定了映射mapper文件的接口所在的包。@EnableScheduling注解用于开启Spring的定时组件,下面会有例子。@ServletComponentScan注解搭配@WebServlet和@WebFilter注解使得定义servlet和filter时非常方便。
6,下面是定义一个简单的restful接口所需的各类:
com.zws.be.user.bean.User:
package com.zws.be.user.bean; import java.util.Date; import org.apache.ibatis.type.Alias; @Alias("User") public class User { private Long id; private String name; private String pass; private Date createDate; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPass() { return pass; } public void setPass(String pass) { this.pass = pass; } public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } @Override public String toString() { return "User [id=" + id + ", name=" + name + ", pass=" + pass + ", createDate=" + createDate + "]"; } }
com.zws.be.user.mapper.UserMapper:
package com.zws.be.user.mapper; import java.util.List; import com.zws.be.user.bean.User; public interface UserMapper { ListgetAll(); User getUserById(Long id); }
com.zws.be.user.service.UserService:
package com.zws.be.user.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.zws.be.user.bean.User; import com.zws.be.user.mapper.UserMapper; @Service public class UserService { @Autowired private UserMapper userMapper; public ListgetUsers() { return userMapper.getAll(); } public User getUser(Long id) { return userMapper.getUserById(id); } }
com.zws.be.user.controller.UserController:
package com.zws.be.user.controller; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; 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 com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.zws.be.user.bean.User; import com.zws.be.user.service.UserService; @RestController @RequestMapping("/user") public class UserController { public Logger logger = Logger.getLogger(UserController.class); @Autowired private UserService userService; @RequestMapping(value="/{id}", method=RequestMethod.GET, produces = "application/json") public String hello(@PathVariable Long id) { User user = userService.getUser(id); return JSON.toJSONString(user, SerializerFeature.WriteDateUseDateFormat); } }
7,配置Druid的sql监控servlet:
package com.zws.be.filter; import javax.servlet.annotation.WebServlet; import com.alibaba.druid.support.http.StatViewServlet; /** * DRUID的sql监控 * @author wensh.zhu * @2018-03-11 */ @WebServlet(name="monitorSrvlet", urlPatterns= {"/druid/*"}) public class MonitorServlet extends StatViewServlet { private static final long serialVersionUID = -2211104135110049275L; }
启动后访问http://localhost:8080/app/druid即可进入Druid的sql监控的监控页面。
8,过滤器例子:
package com.zws.be.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import org.apache.log4j.Logger; @WebFilter(filterName="filterDemo", urlPatterns = {"/*"}) public class FilterDemo implements Filter{ private Logger logger = Logger.getLogger(FilterDemo.class); @Override public void destroy() {} @Override public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain chain) throws IOException, ServletException { logger.info("FilterDemo is working..."); chain.doFilter(arg0, arg1); } @Override public void init(FilterConfig arg0) throws ServletException { } }
9,定时器例子:
package com.zws.be.timer; import org.apache.log4j.Logger; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component public class TimerDemo { private Logger logger = Logger.getLogger(getClass()); @Scheduled(cron = "0/30 * * * * *") public void timer() { logger.info("定时器例子"); } }
10,全局异常处理:
package com.zws.be.exception; import org.apache.log4j.Logger; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestControllerAdvice; /** * 全局异常处理 * @author wensh.zhu * @2018-03-11 */ @RestControllerAdvice public class ExceptionAdvice { private Logger logger = Logger.getLogger(getClass()); private String pattern = "抱歉,异常了:{0}"; @ExceptionHandler(value = {Exception.class}) @ResponseBody public String handleAllException(Exception e, HttpServletRequest req, HttpServletResponse resp) { logger.error("接口调用异常", e); if (e instanceof NoHandlerFoundException) { String reqURI = req.getRequestURI().toString(); return MessageFormat.format(pattern, "资源" + reqURI + "不存在!!!"); } return MessageFormat.format(pattern, e.getMessage()); } }
11,log4j配置文件resources/log4j.properties:
log4j.rootLogger=INFO, stdout, infoLog, errorLog log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.appender.stdout.layout.conversionPattern = %d{yyyy-MM-dd HH\:mm\:ss}|%p|%C|%M|%L|%m%n log4j.appender.infoLog=com.zws.be.log.MyRollingFileAppender log4j.appender.infoLog.File=logs/info/info.log log4j.appender.infoLog.Threshold = info log4j.appender.infoLog.layout=org.apache.log4j.PatternLayout log4j.appender.infoLog.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss}|%p|%C|%M|%L|%m%n log4j.appender.errorLog=com.zws.be.log.MyRollingFileAppender log4j.appender.errorLog.File=logs/error/error.log log4j.appender.errorLog.Threshold = error log4j.appender.errorLog.layout=org.apache.log4j.PatternLayout log4j.appender.errorLog.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss}|%p|%C|%M|%L|%m%n
自定义日志生成文件名称:
package com.zws.be.log; import java.io.File; import java.io.IOException; import java.io.InterruptedIOException; import org.apache.log4j.Priority; import org.apache.log4j.RollingFileAppender; import org.apache.log4j.helpers.CountingQuietWriter; import org.apache.log4j.spi.LoggingEvent; public class MyRollingFileAppender extends RollingFileAppender{ private long nextRollover = 0L; public void rollOver() { File target, file; if (qw != null) { long size = ((CountingQuietWriter) qw).getCount(); nextRollover = size + maxFileSize; } boolean renameSuccessed = true; if (maxBackupIndex > 0) { file = new File(getRollingFileName(fileName, maxBackupIndex)); if (file.exists()) { renameSuccessed = file.delete(); } for (int i = maxBackupIndex - 1; i >= 1 && renameSuccessed; i --) { file = new File(getRollingFileName(fileName, i)); if (file.exists()) { target = new File(getRollingFileName(fileName, i + 1)); renameSuccessed = file.renameTo(target); } } if (renameSuccessed) { target = new File(getRollingFileName(fileName, 1)); closeFile(); renameSuccessed = file.renameTo(target); if (!renameSuccessed) { try { setFile(fileName, true, bufferedIO, bufferSize); } catch (IOException e) { if (e instanceof InterruptedIOException) { Thread.currentThread().interrupt(); } } } } } } private String getRollingFileName(String fileName, int index) { String fName = ""; String newIndex = index < 10 ? ("0" + index) : String.valueOf(index); if (index > 0) { fName = fileName.replace(".log", "") + "_" + newIndex + ".log"; } else { fName = fileName; } return fName; } @Override protected void subAppend(LoggingEvent event) { super.subAppend(event); if (fileName != null && qw != null) { long size = ((CountingQuietWriter) qw).getCount(); if (size >= maxFileSize && size >= nextRollover) { rollOver(); } } } @Override public boolean isAsSevereAsThreshold(Priority priority) { String level = priority.toString(); if ("ERROR".equals(level)) { return getThreshold().equals(priority); } return super.isAsSevereAsThreshold(priority); } }
12,Spring MVC请求参数以及返回信息自动日志的辅助类:
package com.zws.be.log; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.text.MessageFormat; import org.apache.log4j.Logger; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; /** * 用于请求参数、响应信息的自动日志。 * @author wensh.zhu * @2018-03-11 */ @ControllerAdvice(basePackages = {"com.zws.be"}) public class LogResponseBodyAdvice implements ResponseBodyAdvice{ private String pattern = "\n---BEGIN---\nReqURI:{0}\nParams:{1}\nMethod:{2}\nReturn:{3}\n---END---"; private Logger logger = Logger.getLogger(LogResponseBodyAdvice.class); @Override public String beforeBodyWrite(String rtnMsg, MethodParameter param, MediaType arg2, Class extends HttpMessageConverter>> arg3, ServerHttpRequest req, ServerHttpResponse resp) { String clazName = param.getDeclaringClass().getName(); String mtdName = param.getMethod().getName(); String reqURI = req.getURI().toString(); String reqParams = getReqParams(req); String log = getLog(reqURI, reqParams, clazName + "." + mtdName, rtnMsg); logger.info(log); return rtnMsg; } @Override public boolean supports(MethodParameter arg0, Class extends HttpMessageConverter>> arg1) { return true; } private String getLog(String reqURI, String reqParams, String proMethod, String rtnMsg) { return MessageFormat.format(pattern, reqURI, reqParams, proMethod, rtnMsg); } private String getReqParams(ServerHttpRequest req) { StringBuilder body = new StringBuilder(); String line = null; BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(req.getBody(), "UTF-8")); while ((line = reader.readLine()) != null) { body.append(line); } } catch (IOException e) { logger.error("I/O error ", e); } finally { try { if (reader != null) { reader.close(); } } catch (IOException e) { logger.error("I/O error ", e); } } return body.toString(); } }
此类功能为将Spring MVC接受到请求的参数、处理请求对应的方法、请求返回内容自动打印到日志文件内,在接口出现问题的时候方便排查,日志输出样例:
---BEGIN--- ReqURI:http://localhost:8080/app/user/1 Params: Method:com.zws.be.user.controller.UserController.hello Return:{"createDate":"2018-03-04 15:44:25","id":1,"name":"zhangsan","pass":"zhangsan@123"} ---END---
13,运行Application,访问http://localhost:8080/app/user/1,如下图:
查看druid的监控:
项目目录结构图: