美文网首页
SpringWheel框架实现及原理解析

SpringWheel框架实现及原理解析

作者: 拾壹月 | 来源:发表于2016-06-25 22:53 被阅读0次

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

相关文章

网友评论

      本文标题:SpringWheel框架实现及原理解析

      本文链接:https://www.haomeiwen.com/subject/zgpadttx.html