SpringWheel框架实现及原理解析

前言 :本文主要讲述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/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容