另外,我看到网上有不少人说如果把这个@PropertySource("classpath:my.properties")放在根容器的config文件里导入,controller层就使用@Value/占位符获取不到值了,其实这是**不正确**的。理由如下:
Spring MVC子容器在创建时:initWebApplicationContext()
if (cwac.getParent() == null) { cwac.setParent(rootContext); // 设置上父容器(根容器) } AbstractApplicationContext:如下代码 // 相当于子容器的环境会把父容器的Enviroment合并进来 @Override public void setParent(@Nullable ApplicationContext parent) { this.parent = parent; if (parent != null) { Environment parentEnvironment = parent.getEnvironment(); if (parentEnvironment instanceof ConfigurableEnvironment) { getEnvironment().merge((ConfigurableEnvironment) parentEnvironment); } } } AbstractEnvironment:merge()方法如下 @Override public void merge(ConfigurableEnvironment parent) { // 完全的从parent里所有的PropertySources里拷贝一份进来 for (PropertySource<?> ps : parent.getPropertySources()) { if (!this.propertySources.contains(ps.getName())) { this.propertySources.addLast(ps); } } ... }
这就是为什么说即使你是在根容器里使用的@PropertySource导入的外部资源,子容器也可以使用的原因(因为子容器会把父环境给merge一份过来)。
但是,但是,但是:如果你是使用形如PropertyPlaceholderConfigurer这种方式导进来的,那是会有容器隔离效应的~
第二类:参数类型是Map的
数据来源同上,只是参数类型是Map
这类解析器我认为是对第一类的有些处理器的一种补充,它依赖上面的相关注解。
你是否想过通过@RequestParam一次性全给封装进一个Map里,然后再自己分析?同样的本类处理器给@RequestHeader、@PathVariable、@MatrixVariable都赋予了这种能力~
PathVariableMapMethodArgumentResolver
// @since 3.2 晚一个版本号 public class PathVariableMapMethodArgumentResolver implements HandlerMethodArgumentResolver { // 必须标注@PathVariable注解 并且类型是Map,并且注解不能有value值 // 处理情况和PathVariableMethodArgumentResolver形成了互补 @Override public boolean supportsParameter(MethodParameter parameter) { PathVariable ann = parameter.getParameterAnnotation(PathVariable.class); return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) && !StringUtils.hasText(ann.value())); } @Override public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { ... // 处理上极其简单,把所有的路径参数使用Map装着返回即可 } }
RequestParamMapMethodArgumentResolver
它依赖的方法是:HttpServletRequest#getParameterMap()、MultipartRequest#getMultiFileMap()、MultipartRequest#getFileMap()等,出现于Spring 3.1。
演示一把:
@ResponseBody @GetMapping("/test") public Object test(@RequestParam Map<String,Object> params) { System.out.println(params); return params; }
请求:/test?name=fsx&age=18&age=28
。打印
{name=fsx, age=18}
从结果看出:
- 它不能传一key多值情况
- 若出现相同的key,以在最前面的key的值为准。
- Map实例是一个LinkedHashMap<String,String>实例
RequestHeaderMapMethodArgumentResolver
一次性把请求头信息都拿到:数据类型支出写MultiValueMap(LinkedMultiValueMap)/HttpHeaders/Map。实例如下:
@ResponseBody @GetMapping("/test") public Object test(@RequestHeader Map<String, Object> headers) { headers.forEach((k, v) -> System.out.println(k + "-->" + v)); return headers; }
请求打印:
host-->localhost:8080 connection-->keep-alive cache-control-->max-age=0 upgrade-insecure-requests-->1 user-agent-->Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36 sec-fetch-mode-->navigate sec-fetch-user-->?1 accept-->text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 sec-fetch-site-->none accept-encoding-->gzip, deflate, br accept-language-->zh-CN,zh;q=0.9 cookie-->JSESSIONID=123456789
不过强烈不建议直接使用Map,而是使用HttpHeaders类型。这么写@RequestHeader HttpHeaders headers,获取的时候更为便捷。
MatrixVariableMapMethodArgumentResolver
略。
MapMethodProcessor
它处理Map类型,但没有标注任何注解的情况,它的执行顺序是很靠后的,所以有点兜底的意思。
// @since 3.1 public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler { @Override public boolean supportsParameter(MethodParameter parameter) { return Map.class.isAssignableFrom(parameter.getParameterType()); } // 处理逻辑非常简单粗暴:把Model直接返回~~~~ @Override @Nullable public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { return mavContainer.getModel(); } }
使用案例:略。
这个处理器同时也解释了:为何你方法入参上写个
Map、HashMap、ModelMap
等等就可以非常便捷的获取到模型的值的原因~
第三类:固定参数类型
参数比如是SessionStatus, ServletResponse, OutputStream, Writer, WebRequest, MultipartRequest, HttpSession, Principal, InputStream等
这种方式使用得其实还比较多的。比如平时我们需要用Servlet源生的API:HttpServletRequest, HttpServletResponse肿么办? 在Spring MVC内就特别特别简单,只需要在入参上声明:就可以直接使用啦~
ServletRequestMethodArgumentResolver
// 它支持到的可不仅仅是ServletRequest,多到令人发指 public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver { // 连Servlet 4.0的PushBuilder都支持了(Spring5.0以上版本支持的) @Nullable private static Class<?> pushBuilder; static { try { pushBuilder = ClassUtils.forName("javax.servlet.http.PushBuilder", ServletRequestMethodArgumentResolver.class.getClassLoader()); } catch (ClassNotFoundException ex) { // Servlet 4.0 PushBuilder not found - not supported for injection pushBuilder = null; } } // 支持"注入"的类型,可谓多多益善 @Override public boolean supportsParameter(MethodParameter parameter) { Class<?> paramType = parameter.getParameterType(); return (WebRequest.class.isAssignableFrom(paramType) || ServletRequest.class.isAssignableFrom(paramType) || // webRequest.getNativeRequest(requiredType) MultipartRequest.class.isAssignableFrom(paramType) || HttpSession.class.isAssignableFrom(paramType) || //request.getSession() (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) || //PushBuilderDelegate.resolvePushBuilder(request, paramType); Principal.class.isAssignableFrom(paramType) || //request.getUserPrincipal() InputStream.class.isAssignableFrom(paramType) || // request.getInputStream() Reader.class.isAssignableFrom(paramType) || //request.getReader() HttpMethod.class == paramType || //HttpMethod.resolve(request.getMethod()); Locale.class == paramType || //RequestContextUtils.getLocale(request) TimeZone.class == paramType || //RequestContextUtils.getTimeZone(request) ZoneId.class == paramType); //RequestContextUtils.getTimeZone(request); } }
看到这你应该明白,以后你需要使用这些参数的话,直接在方法上申明即可,不需要自己再去get了,又是一种依赖注入的效果体现有木有~