前言 :本文主要讲述SpringWheel框架中的实现,阅读前请先了解SpringBoot和SpringMvc的原理
1、全局系统出错提示
在spring boot中BasicErrorController对全局的异常进行处理(包括DispatchServlet.java中的异常),在BasicErrotController中
//访问为表单的形式
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
response.setStatus(getStatus(request).value());
Map<String, Object> model = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.TEXT_HTML));
return new ModelAndView("error", model);
}
//访问为ajax的形式
@RequestMapping
@ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<Map<String, Object>>(body, status);
}
如果按照默认的处理方式显然就不太符合我们的需求,所以在这个时候就需要对BasicErrorController进行重写
public class GlobalExceptionController extends BasicErrorController {
public GlobalExceptionController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
super(errorAttributes, errorProperties);
}
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus httpStatus = getStatus(request); //如果是404的错误
if (HttpStatus.NOT_FOUND == httpStatus) {
return new ModelAndView("error-404");
}
//代码异常
response.setStatus(getStatus(request).value());
Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
return new ModelAndView("error", model);
}
@RequestMapping
@ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<Map<String, Object>>(body, status);
}
}
然后在将该类添加到spring容器中替换BasicController
2、使用log4jdbc对慢查询进行监控(开发环境使用)
在开发过程中,如果传入的参数很多,这个时候查看sql就变得很麻烦了,log4jdbc通过重写驱动类的方式,直接打印出执行的sql和sql的执行时间,大大提升了我们的开发的效率。而且其可以对慢查询sql进行监控,如果查询时间超过设定值就会打印warn日志。但是因为其重写了驱动类,在生成环境上还是不要用。
Step1:引入maven坐标
<dependency>
<groupId>com.googlecode.log4jdbc</groupId>
<artifactId>log4jdbc</artifactId>
<version>1.2</version>
</dependency>
Step2: 更改驱动类和URL
//将驱动类改为net.sf.log4jdbc.DriverSpy
spring.datasource.driverClassName=net.sf.log4jdbc.DriverSpy
//在URL前面加上jdbc:log4jdbc
spring.datasource.url=jdbc:log4jdbc:mysql://127.0.0.1:3306/yk?useUnicode=true&characterEncoding=utf-8
Step3:在logback.xml文件中配置
<!--SQL日志设置 -->
<logger name="jdbc.connection" level="OFF" />
<logger name="jdbc.audit" level="OFF" />
<logger name="jdbc.resultset" level="OFF" />
<logger name="jdbc.sqlonly" level="OFF" />
<logger name="jdbc.sqltiming" level="INFO" />
上面的只是一个简单的入门例子,看到一个帖子讲的比较详细http://www.360doc.com/content/14/0822/14/8072791_403817030.shtml
3、自定义注解@ApiRequestBody、@ApiRequestBody、@ApiController的思路和实现
WHY:在一个项目中,有的时候需要对api的请求参数和返回参数进行一些处理,如果直接重写HttpMessageConverter,所有的json请求都会被处理。这种做法明显不符合我们的需求。这个时候比较明智的做法是对api的方法进行单独的处理。
SpringMvc请求处理过程
- 图中invokeForRequest中涉及了请求参数的处理
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
if (this.argumentResolvers.supportsParameter(parameter)) {
...
args[i] = this.argumentResolvers.resolveArgument( parameter, mavContainer, request, this.dataBinderFactory);
...
}
}
我们再看看argumentResolvers的实现吧,例如RequestResponseBodyMethodProcessor
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
从以上代码可以看出,只要controller的方法上面有@RequestBody的注解,请求就会进入RequestResponseBodyMethodProcessor进行处理(主要是List<HttpMessageConverter> messgeConverters的处理)。
- 图中handleReturnValue方式对返回的参数进行了处理,同样的套路
private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) {
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
...
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
}
同样在RequestResponseBodyMethodProcessor中
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null || returnType.getMethodAnnotation(ResponseBody.class) != null);
}
从以上代码可以看出,只要controller的方法上面有@ResponseBody的注解,请求就会进入RequestResponseBodyMethodProcessor进行处理(主要是List<HttpMessageConverter> messgeConverters的处理)
自定义实现
了解了原理之后,发现我们完全可以按照SpringMVC的套路来实现自己的需求
Step1:定义三个注解@ApiReqeustBody,@ApiResponseBody,@ApiController(参照@RequestBody、@ResponseBody、@RestController)
Step2:定义参数处理类ApiRequestResponseMethodProcessor(参照ReqeustResponseBodyMethodProcessor)
Step3:定义ApiHttpMessageConverter(参照AbstractGenericHttpMessageConverter的实现)
Step4:将ApiRequestResponseMethodProcessor放到argumentResolvers(入参处理list)和returnValueHandlers(返回参数处理list)。
- 在SpringBoot中:
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
List<HttpMessageConverter<?>> messageConverters = getHttpMessageConverters();
argumentResolvers.add(new ApiRequestResponseBodyMethodProcessor(messageConverters));
}
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
List<HttpMessageConverter<?>> messageConverters = getHttpMessageConverters();
returnValueHandlers.add(new ApiRequestResponseBodyMethodProcessor(messageConverters));
}
private List<HttpMessageConverter<?>> getHttpMessageConverters(){
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
ApiHttpMessageConverter apiHttpMessageConverter = new ApiHttpMessageConverter();
messageConverters.add(apiHttpMessageConverter);
return messageConverters;
}
- 在SpringMvc中:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="customArgumentResolvers">
<list>
<bean class="com.springwheel.common.annotation.ApiRequestResponseBodyMethodProcessor"/>
</list>
</property>
<property name="customReturnValueHandlers">
<list>
<bean class="com.springwheel.common.annotation.ApiRequestResponseBodyMethodProcessor"/>
</list>
</property>
</bean>
4、api请求处理过程详解
ApiRequestFilter
- 验签
- 权限检验
ValidationInterceptor
- 参数检验
碰见的问题:
1)Filter中读取Request中的流后, 然后再Control中读取不到的做法
解决方法:实现一个Wrapper ,可参照:http://my.oschina.net/vernon/blog/363693
2)ValidationInterceptor中无法获取参数值
解决方法:在ValidationInterceptor调用ArgumentResolver,对参数进行处理。参照:http://my.oschina.net/FengJ/blog/223727
5、使用profile方便线上环境配置
我想有做过发布的一定配到过这样的问题,本地开发用的配置文件跟线上开发的配置文件不一样。一般如果使用jenkens进行自动部署的会在copy的时候对配置文件进行exclude,但是这样往往会有以下几个缺点:
- 线上配置文件跟本地不一致,如果本地增加了配置,但是线上没有的话,项目启动就会报错
- 运维一般都会控制写入权限,所以线上的配置文件开发没有权限更改,需要增加或者更改配置的话需要通知运维(增加了沟通成本和时间成本)
- 每个人本地的环境都不一样,如果每次从svn或者git上面更新文件都要更改本地配置
spring boot多profile配置
pom.xml
<!--根据环境配置不同的profiles -->
<profiles>
<profile>
<id>dev</id>
<properties>
<profileActive>dev</profileActive>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>cte</id>
<properties>
<profileActive>cte</profileActive>
</properties>
</profile>
<profile>
<id>cte2</id>
<properties>
<profileActive>cte2</profileActive>
</properties>
</profile>
</profiles>
<!--打包的时候对resources的文件进行拦截 (根据maven传入的不同的环境参数打包的时候会对application的配置文件进行拦截)-->
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>application.properties</exclude>
<exclude>application-dev.properties</exclude>
<exclude>application-cte.properties</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>application.properties</include>
<include>application-${profileActive}.properties</include>
</includes>
</resource>
</resources>
application.properties
#profile
spring.profiles.active=@profileActive@
打包时指定环境(如果不指定,起作用的为dev)
例如:dev环境
mvn clean install -Dmaven.test.skip=true -P dev
可以看到,打包的时候只会打入application.properties和application-dev.properties这两个文件。
地址:https://github.com/hjm017/SpringWheel
官网:http://hjm017.github.io/SpringWheel/