文章目录
Spring MVC源码分析相关文章已出:
更多Spring系列源码分析文章见SpringBoot专栏:

Spring 3.0 引入了一个 core.convert,并提供通用类型转换系统的包。系统定义了一个 SPI 来实现类型转换逻辑,并定义一个 API 来在运行时执行类型转换。
这套类型转换系统的顶层接口为:Converter
@FunctionalInterface
public interface Converter<S, T> {
/**
* Convert the source object of type {@code S} to target type {@code T}.
* @param source the source object to convert, which must be an instance of {@code S} (never {@code null})
* @return the converted object, which must be an instance of {@code T} (potentially {@code null})
* @throws IllegalArgumentException if the source cannot be converted to the desired target type
*/
@Nullable
T convert(S source);
}
Converter的作用是将类型 S 转换为 T,在参数解析器中使用该接口的实现类 将前端请求参数 转换成 控制器方法(Controller#Method) 需要的参数类型。
此外,还有ConverterFactory<S, R>(将类型S 转换为 R及其子类型)、ConversionService(用来在运行时执行类型转换);
Spring 提供的所有支持的类型转换器实现类都在 org.springframework.core.convert.support 包下,大多数转换器的convert()方法都很简单,感兴趣可以自己看一下源码。

比如:
ids -> 1,2,3 能够用 List<Long> 接收;enable -> no 能够用 Boolean 接收;要实现的效果:
,分隔;http://127.0.0.1:38888/person/other?person=saint,15,true,otherInfo&other=hahaha1> Java Model类:
public class Person {
private String name;
private Integer age;
private Boolean sex;
private String otherInfo;
}
2> 自定义Converter:
逻辑很简单,就是将Spring字符串用英文逗号,分隔,按Person类的属性顺序,将请求中的参数填充到Person对象中。
@Configuration
public class WebMvcConfig {
/**
* 自定义参数类型转换器
* WebMvcConfigurer#addFormatters()方法用来添加自定义的参数类型转换器;
*/
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Person>() {
@Override
public Person convert(String source) {
if (StringUtils.isEmpty(source)) {
return null;
}
final String[] sourceArgs = source.split(",");
Person person = new Person();
person.setName(sourceArgs[0]);
person.setAge(Integer.valueOf(sourceArgs[1]));
person.setSex(Boolean.valueOf(sourceArgs[2]));
person.setOtherInfo(String.valueOf(sourceArgs[3]));
return person;
}
});
}
};
}
}
3> controller方法:
@PostMapping("/person/other")
public String otherPerson(Person person, String other) {
return person.toString() + other;
}
4> 效果:

Spring中参数解析器的最上层接口为HandlerMethodArgumentResolver,其中有两个方法:
public interface HandlerMethodArgumentResolver {
/**
* Whether the given {@linkplain MethodParameter method parameter} is
* supported by this resolver.
*/
boolean supportsParameter(MethodParameter parameter);
/**
* Resolves a method parameter into an argument value from a given request.
* A {@link ModelAndViewContainer} provides access to the model for the
* request. A {@link WebDataBinderFactory} provides a way to create
* a {@link WebDataBinder} instance when needed for data binding and
* type conversion purposes.
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
前端请求传递两个分页参数 pageNum、pageSize,后端用一个IPage模型接收;
参数解析器策略要支持的是 IPage.class,核心是 HandlerMethodArgumentResolver 的两个方法实现。
这里基于spring-data-common 包下的PageableHandlerMethodArgumentResolver 做一个扩展。
1> maven依赖:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
2> 自定义参数解析器PageHandlerMethodArgumentResolver
package com.saint.config;
import com.saint.model.IPage;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* 自定义分页参数解析器
*
* @author Saint
*/
public class PageHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
private static final int DEFAULT_PAGE = 0;
private static final int DEFAULT_SIZE = 10;
private static final String DEFAULT_PAGE_PARAM = "pageNum";
private static final String DEFAULT_SIZE_PARAM = "pageSize";
/**
* 组合 `spring-data-commons` 包下的PageableHandlerMethodArgumentResolver,实现分页参数解析功能
*/
private final PageableHandlerMethodArgumentResolver pageableArgumentResolver;
public PageHandlerMethodArgumentResolver() {
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
resolver.setFallbackPageable(PageRequest.of(DEFAULT_PAGE, DEFAULT_SIZE));
resolver.setSizeParameterName(DEFAULT_SIZE_PARAM);
resolver.setPageParameterName(DEFAULT_PAGE_PARAM);
resolver.setOneIndexedParameters(true);
this.pageableArgumentResolver = resolver;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return IPage.class.equals(parameter.getParameterType());
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Pageable pageable =
pageableArgumentResolver.resolveArgument(
parameter, mavContainer, webRequest, binderFactory);
return new IPage(pageable.getPageNumber() + 1, pageable.getPageSize());
}
}
3> 将自定义的参数解析器添加到Spring MVC 的参数解析器集合中:
@Configuration
public class WebMvcConfig {
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new PageHandlerMethodArgumentResolver());
}
};
}
}
4> Controller方法:
/**
* http://localhost:38888/page?pageNum=9&pageSize=20
*/
@GetMapping("/page")
public String page(IPage page) {
return page.toString();
}
5> 效果:
GET /xxx?pageNum=1&pageSize=10请求的分页参数被解析成了IPage对象
自定义一个注解用于标注某个JavaModel,JavaModel中的信息是根据请求中的某些数据间接得到。
1> 自定义的注解@UserParam:
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserParam {
}
2> 自定义的JavaModel:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo {
private String userCode;
private String userName;
private String address;
}
3> 自定义的参数解析器:
package com.saint.config;
import com.saint.annotation.UserParam;
import com.saint.model.UserInfo;
import com.saint.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
/**
* @author Saint
*/
public class UserInfoArgumentResolver implements HandlerMethodArgumentResolver {
@Autowired
private UserInfoService userInfoService;
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterType() == UserInfo.class
&& methodParameter.hasParameterAnnotation(UserParam.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
String token = request.getHeader("token");
UserInfo userInfo = userInfoService.getUserInfoByToken(token);
return userInfo;
}
}
4> UserInfoService:
package com.saint.service;
import com.saint.model.UserInfo;
import org.springframework.stereotype.Service;
/**
* @author Saint
*/
@Service
public class UserInfoService {
public UserInfo getUserInfoByToken(String token) {
// todo 调用用户中心返回用户的信息
UserInfo user = new UserInfo("code01", "saint", "你心里 " + token);
return user;
}
}
5> 将自定义的参数解析器添加到Spring MVC 的参数解析器集合中:
package com.saint.config;
import com.saint.model.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.util.StringUtils;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* @author Saint
*/
@Configuration
public class WebMvcConfig {
@Bean
public UserInfoArgumentResolver getUserInfoArgumentResolver() {
return new UserInfoArgumentResolver();
}
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(getUserInfoArgumentResolver());
}
};
}
}
6> 效果:

我正在尝试设置一个puppet节点,但rubygems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由rubygems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby中使用两个参数异步运行exe吗?我已经尝试过ruby命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何rubygems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除
我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby1.9+ 关于ruby-主要:Objectwhenrun
我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"
我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串
我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)
两者都可以defsetup(options={})options.reverse_merge:size=>25,:velocity=>10end和defsetup(options={}){:size=>25,:velocity=>10}.merge(options)end在方法的参数中分配默认值。问题是:哪个更好?您更愿意使用哪一个?在性能、代码可读性或其他方面有什么不同吗?编辑:我无意中添加了bang(!)...并不是要询问nobang方法与bang方法之间的区别 最佳答案 我倾向于使用reverse_merge方法:option