前言 :本文主要講述SpringWheel框架中的實(shí)現(xiàn)伍纫,閱讀前請(qǐng)先了解SpringBoot和SpringMvc的原理
1胡陪、全局系統(tǒng)出錯(cuò)提示
在spring boot中BasicErrorController對(duì)全局的異常進(jìn)行處理(包括DispatchServlet.java中的異常)类早,在BasicErrotController中
//訪問(wèn)為表單的形式
@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);
}
//訪問(wèn)為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);
}
如果按照默認(rèn)的處理方式顯然就不太符合我們的需求蟆沫,所以在這個(gè)時(shí)候就需要對(duì)BasicErrorController進(jìn)行重寫
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的錯(cuò)誤
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對(duì)慢查詢進(jìn)行監(jiān)控(開發(fā)環(huán)境使用)
在開發(fā)過(guò)程中耘婚,如果傳入的參數(shù)很多叼丑,這個(gè)時(shí)候查看sql就變得很麻煩了关翎,log4jdbc通過(guò)重寫驅(qū)動(dòng)類的方式,直接打印出執(zhí)行的sql和sql的執(zhí)行時(shí)間鸠信,大大提升了我們的開發(fā)的效率纵寝。而且其可以對(duì)慢查詢sql進(jìn)行監(jiān)控,如果查詢時(shí)間超過(guò)設(shè)定值就會(huì)打印warn日志星立。但是因?yàn)槠渲貙懥蓑?qū)動(dòng)類爽茴,在生成環(huán)境上還是不要用葬凳。
Step1:引入maven坐標(biāo)
<dependency>
<groupId>com.googlecode.log4jdbc</groupId>
<artifactId>log4jdbc</artifactId>
<version>1.2</version>
</dependency>
Step2: 更改驅(qū)動(dòng)類和URL
//將驅(qū)動(dòng)類改為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日志設(shè)置 -->
<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" />
上面的只是一個(gè)簡(jiǎn)單的入門例子,看到一個(gè)帖子講的比較詳細(xì)http://www.360doc.com/content/14/0822/14/8072791_403817030.shtml
3室奏、自定義注解@ApiRequestBody火焰、@ApiRequestBody、@ApiController的思路和實(shí)現(xiàn)
WHY:在一個(gè)項(xiàng)目中胧沫,有的時(shí)候需要對(duì)api的請(qǐng)求參數(shù)和返回參數(shù)進(jìn)行一些處理昌简,如果直接重寫HttpMessageConverter,所有的json請(qǐng)求都會(huì)被處理绒怨。這種做法明顯不符合我們的需求纯赎。這個(gè)時(shí)候比較明智的做法是對(duì)api的方法進(jìn)行單獨(dú)的處理。
SpringMvc請(qǐng)求處理過(guò)程
- 圖中invokeForRequest中涉及了請(qǐng)求參數(shù)的處理
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);
...
}
}
我們?cè)倏纯碼rgumentResolvers的實(shí)現(xiàn)吧南蹂,例如RequestResponseBodyMethodProcessor
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
從以上代碼可以看出犬金,只要controller的方法上面有@RequestBody的注解,請(qǐng)求就會(huì)進(jìn)入RequestResponseBodyMethodProcessor進(jìn)行處理(主要是List<HttpMessageConverter> messgeConverters的處理)碎紊。
- 圖中handleReturnValue方式對(duì)返回的參數(shù)進(jìn)行了處理佑附,同樣的套路
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的注解仗考,請(qǐng)求就會(huì)進(jìn)入RequestResponseBodyMethodProcessor進(jìn)行處理(主要是List<HttpMessageConverter> messgeConverters的處理)
自定義實(shí)現(xiàn)
了解了原理之后,發(fā)現(xiàn)我們完全可以按照SpringMVC的套路來(lái)實(shí)現(xiàn)自己的需求
Step1:定義三個(gè)注解@ApiReqeustBody词爬,@ApiResponseBody秃嗜,@ApiController(參照@RequestBody、@ResponseBody顿膨、@RestController)
Step2:定義參數(shù)處理類ApiRequestResponseMethodProcessor(參照ReqeustResponseBodyMethodProcessor)
Step3:定義ApiHttpMessageConverter(參照AbstractGenericHttpMessageConverter的實(shí)現(xiàn))
Step4:將ApiRequestResponseMethodProcessor放到argumentResolvers(入?yún)⑻幚韑ist)和returnValueHandlers(返回參數(shù)處理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請(qǐng)求處理過(guò)程詳解
ApiRequestFilter
- 驗(yàn)簽
- 權(quán)限檢驗(yàn)
ValidationInterceptor
- 參數(shù)檢驗(yàn)
碰見的問(wèn)題:
1)Filter中讀取Request中的流后, 然后再Control中讀取不到的做法
解決方法:實(shí)現(xiàn)一個(gè)Wrapper 恋沃,可參照:http://my.oschina.net/vernon/blog/363693
2)ValidationInterceptor中無(wú)法獲取參數(shù)值
解決方法:在ValidationInterceptor調(diào)用ArgumentResolver必搞,對(duì)參數(shù)進(jìn)行處理。參照:http://my.oschina.net/FengJ/blog/223727
5囊咏、使用profile方便線上環(huán)境配置
我想有做過(guò)發(fā)布的一定配到過(guò)這樣的問(wèn)題恕洲,本地開發(fā)用的配置文件跟線上開發(fā)的配置文件不一樣。一般如果使用jenkens進(jìn)行自動(dòng)部署的會(huì)在copy的時(shí)候?qū)ε渲梦募M(jìn)行exclude梅割,但是這樣往往會(huì)有以下幾個(gè)缺點(diǎn):
- 線上配置文件跟本地不一致霜第,如果本地增加了配置,但是線上沒(méi)有的話户辞,項(xiàng)目啟動(dòng)就會(huì)報(bào)錯(cuò)
- 運(yùn)維一般都會(huì)控制寫入權(quán)限泌类,所以線上的配置文件開發(fā)沒(méi)有權(quán)限更改,需要增加或者更改配置的話需要通知運(yùn)維(增加了溝通成本和時(shí)間成本)
- 每個(gè)人本地的環(huán)境都不一樣底燎,如果每次從svn或者git上面更新文件都要更改本地配置
spring boot多profile配置
pom.xml
<!--根據(jù)環(huán)境配置不同的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>
<!--打包的時(shí)候?qū)esources的文件進(jìn)行攔截 (根據(jù)maven傳入的不同的環(huán)境參數(shù)打包的時(shí)候會(huì)對(duì)application的配置文件進(jìn)行攔截)-->
<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@
打包時(shí)指定環(huán)境(如果不指定刃榨,起作用的為dev)
例如:dev環(huán)境
mvn clean install -Dmaven.test.skip=true -P dev

可以看到弹砚,打包的時(shí)候只會(huì)打入application.properties和application-dev.properties這兩個(gè)文件。
地址:https://github.com/hjm017/SpringWheel
官網(wǎng):http://hjm017.github.io/SpringWheel/