技术是需要积累的。
一、日志
spring boot内部使用Commons Logging来记录日志,但也保留外部接口可以让一些日志框架来进行实现,例如Java Util Logging,Log4J2还有Logback。如果你想用某一种日志框架来进行实现的话,就必须先配置,默认情况下,spring boot使用Logback作为日志实现的框架。
1.显示debug级别的日志
debug是打印信息最冗余的级别,其次是info,warn,error。在开发阶段,可能需要debug级别的日志,这可以通过如下两种方式实现:
- 通过application.properites配置debug=true
- 既然是更改的application.properties,那么肯定也能通过命令行来配置:
java -jar C:UsersAdministratorDesktopxxdemo.jar --debug
2.一份完美的配置
logback.xml
<!-- Logback configuration. See http://logback.qos.ch/manual/index.html -->
<configuration scan="true" scanPeriod="10 seconds">
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${LOG_PATH}/info.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/info-%d{yyyyMMdd}.log.%i</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>2</maxHistory>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n
</Pattern>
</layout>
</appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<File>${LOG_PATH}/error.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/error-%d{yyyyMMdd}.log.%i
</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>2</maxHistory>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n
</Pattern>
</layout>
</appender>
<root level="INFO">
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
</configuration>
在application.properties中,进行如下配置
#log
logging.config=classpath:logback.xml
logging.path=${user.home}/poem-log
3.最佳实践
- 日志目录最好不要放在webapp中,而要放在其它文件夹中。
- 日志配置最好单独一个文件进行配置,这样扩展性好、清晰。
二、视图
spring boot 在springmvc的视图解析器方面就默认集成了ContentNegotiatingViewResolver和BeanNameViewResolver,在视图引擎上就已经集成自动配置的模版引擎,如下:
- FreeMarker
- Groovy
- Thymeleaf
- Velocity (deprecated in 1.4)
- Mustache
JSP技术spring boot 官方是不推荐的,原因有三:
- 在tomcat上,jsp不能在嵌套的tomcat容器解析即不能在打包成可执行的jar的情况下解析
- Jetty 嵌套的容器不支持jsp
- Undertow
而其他的模版引擎spring boot 都支持,并默认会到classpath的templates里面查找模版引擎,这里假如我们使用freemarker模版引擎
三、静态资源
Spring Boot 默认配置的/**
映射到/static
(或/public
,/resources
,/META-INF/resources
),/webjars/**
会映射到classpath:/META-INF/resources/webjars/
。
注意:上面的/static
等目录都是在classpath:
下面。
静态资源映射还有一个配置选项,为了简单这里用.properties方式书写:
spring.mvc.static-path-pattern=/** # Path pattern used for static resources.
这个配置会影响默认的/**
,例如修改为/static/**
后,只能映射如/static/js/sample.js
这样的请求(修改前是/js/sample.js
)。这个配置只能写一个值,不像大多数可以配置多个用逗号隔开的。
四、异常处理
SpringBoot提供了健全的异常机制。异常处理分为三种:
- 按照异常分类处理
- 按照Controller分类处理
- 全局异常处理
1、使用@ResponseStatus
定义异常的类型
众所周知,Java中可以通过继承Exception自定义异常类型。在JavaWeb中还可以更进一步,异常可以分为很多种:
- 404:页面不见了
- 500:内部错误(这是一切异常的默认statusCode)
......
使用SpringBoot可以通过注解来定义异常的种类,如下所以定义了一个“订单未找到”异常,这个异常的状态码是404
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order") // 404
public class OrderNotFoundException extends RuntimeException {
// ...
}
使用这个异常时,如下写法:直接抛出这个异常就行了。
@RequestMapping(value="/orders/{id}", method=GET)
public String showOrder(@PathVariable("id") long id, Model model) {
Order order = orderRepository.findOrderById(id);
if (order == null) throw new OrderNotFoundException(id);
model.addAttribute(order);
return "orderDetail";
}
2、使用@ExceptionHandler
处理某个Controller内的异常
在一个Controller中,使用@RequestMapping
注解某个函数,表示这个函数用来处理请求。使用@ExceptionHandler
注解某个函数,表示这个函数用来处理@RequestMapping
函数所抛出的异常。
如下代码,在Controller中定义了三个ExceptionHandler,体会一下用法。
@Controller
public class ExceptionHandlingController {
// @RequestHandler methods
...
// Exception handling methods
// Convert a predefined exception to an HTTP Status code
@ResponseStatus(value=HttpStatus.CONFLICT,
reason="Data integrity violation") // 409
@ExceptionHandler(DataIntegrityViolationException.class)
public void conflict() {
// Nothing to do
}
// Specify name of a specific view that will be used to display the error:
@ExceptionHandler({SQLException.class,DataAccessException.class})
public String databaseError() {
// Nothing to do. Returns the logical view name of an error page, passed
// to the view-resolver(s) in usual way.
// Note that the exception is NOT available to this view (it is not added
// to the model) but see "Extending ExceptionHandlerExceptionResolver"
// below.
return "databaseError";
}
// Total control - setup a model and return the view name yourself. Or
// consider subclassing ExceptionHandlerExceptionResolver (see below).
@ExceptionHandler(Exception.class)
public ModelAndView handleError(HttpServletRequest req, Exception ex) {
logger.error("Request: " + req.getRequestURL() + " raised " + ex);
ModelAndView mav = new ModelAndView();
mav.addObject("exception", ex);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
}
3、异常显示页面
千万不要让用户看见异常的stacktrace,那样显得很不专业。但是调试的时候,可以直接显示异常栈。
例如使用JSP:
<h1>Error Page</h1>
<p>Application has encountered an error. Please contact support on ...</p>
<!--
Failed URL: ${url}
Exception: ${exception.message}
<c:forEach items="${exception.stackTrace}" var="ste"> ${ste}
</c:forEach>
-->
4、全局异常控制@ControllerAdvice
使用@ControllerAdvice
注解了的类相当于拦截器,把Controller的请求处理前、请求处理后、请求有异常的时候分别进行处理。
使用@ControllerAdvice
注解的类功能可以包含@ModelAttribute
,@ExceptionHandler
,@InitBinder
。但是只需要了解@ExceptionHandler
注解即可,别的都用不上。
@ControllerAdvice
class GlobalControllerExceptionHandler {
@ResponseStatus(HttpStatus.CONFLICT) // 409
@ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}
可以定义一个全局的处理一切异常的函数:
@ControllerAdvice
class GlobalDefaultExceptionHandler {
public static final String DEFAULT_ERROR_VIEW = "error";
@ExceptionHandler(value = Exception.class)
public ModelAndView
defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
// If the exception is annotated with @ResponseStatus rethrow it and let
// the framework handle it - like the OrderNotFoundException example
// at the start of this post.
// AnnotationUtils is a Spring Framework utility class.
if (AnnotationUtils.findAnnotation
(e.getClass(), ResponseStatus.class) != null)
throw e;
// Otherwise setup and send the user to a default error-view.
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}
}
5、优先级
- 同一个异常被局部范围异常处理器和全局范围异常处理器同时覆盖,会选择小范围的局部范围处理器
- 同一个异常被小范围的异常类和大范围的异常处理器同时覆盖,会选择小范围的异常处理器
五、热部署
使用devtools
运行springBoot的两种方式:mvn run,springboot:run
六、SpringBoot测试
过去,我以为每个类都写一个main函数测试一下这个类就可以了。这种方式在使用Spring的情况下不好使,因为很多注解都没有发挥作用。
使用Spring的代码,必须写测试,否则
- application.properties文件不会正常加载。
- 使用的
@Autowired
的成员变量都不会自动注入。
写测试很简单,只需要用到三个注解:
- 用下面两个注解来注解测试类
@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
- 用
@Test
注解来注解测试方法
下面看一个具体的例子,这个例子演示了多例的用法。
在Spring中,使用Component
注解的类相当于一个“Bean”,像Controller本身也是Component。使用Component注解的类默认都是单例,即@Scope("singleton")
,如果改成多例,可以通过@Scope("prototype")
注解来实现。
下面定义了一个类Config,这个类有一个成员变量token。如果Config是单例,会发现config2跟config指向同一个对象;如果Config是多例,会发现config和config2互不影响。
@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class SingletonTest {
@Autowired Config config;
@Autowired Config config2;
@Test
public void go(){
System.out.println(config.getToken());
System.out.println(config2.getToken());
config2.setToken("haha");
System.out.println(config.getToken());
}
}
注意一个知识点,在SpringBoot中,Controller默认是单例。
对于只包含静态方法的类,完全可以用单例来替代。
即便不使用Web,也可以使用Spring的单例、多例、注入等机制。
参考资料
http://jinnianshilongnian.iteye.com/blog/1866350 开涛的@ControllerAdvice(三个作用)
http://www.tuicool.com/articles/fA7nuii springboot约定的异常处理体系
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc springMVC异常处理体系
这篇博客提供了一个github代码,用到thymleaf,是挺好的SpringMVC入门资料。
http://www.baeldung.com/2013/01/31/exception-handling-for-rest-with-spring-3-2/ springMVC异常处理体系