【springmvc】使用@EnableWebMvc自定义配置

如果不使用@EnableWebMvc注解,web项目同样能正确接收请求,只不过使用的是默认配置的一些组件,这些组件都配置在DispatcherServlet.properties文件,如果想修改这些默认的组件或者添加一些新的组件,就需要使用@EnableWebMvc注解开启自定义配置,并注入一个实现了WebMvcConfigurer接口的Bean。

自定义配置的使用

开启MVC配置

要实现SpringMVC自定义配置,需要使用@EnableWebMvc开启MVC配置,并实现WebMvcConfigurer接口重写其中的方法。

package com.morris.spring.mvc.config;

import com.morris.spring.mvc.interceptor.MyInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;

import java.util.List;
import java.util.Properties;

@EnableWebMvc // 开启自定义spring mvc配置
@Configuration
public class MVCConfig implements WebMvcConfigurer {
... ...

相当于在spring-mvc.xml中这么配置:

<mvc:annotation-driven/>

配置视图解析器

重写WebMvcConfigurer接口的configureViewResolvers()方法:

public void configureViewResolvers(ViewResolverRegistry registry) {
	registry.jsp("/jsp/", ".jsp");
}

这样配置后所有Controller返回的视图都会加上前缀/jsp/和后缀.jsp

例如Controller如下配置:

package com.morris.spring.mvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class IndexController {

	@RequestMapping("/index")
	public String index() {
		return "index";
	}

}

浏览器中请求/index,spring mvc就会controller返回的视图名称的基础上拼接上前缀和后缀,最后变成/jsp/index.jsp,然后返回给tomcat进行解析。

相当于以前在spring-mvc.xml中这样配置:

<mvc:view-resolvers>
    <mvc:jsp/>
</mvc:view-resolvers>

配置视图控制器

重写WebMvcConfigurer接口的addViewControllers()方法。

public void addViewControllers(ViewControllerRegistry registry) {
	registry.addViewController("/index2").setViewName("index");
}

适用于只返回视图,没有任何业务逻辑。

等价于在Controller中这么配置:

@RequestMapping("/index2")
public String index() {
	return "index";
}

静态资源映射器

重写WebMvcConfigurer接口的addResourceHandlers()方法。

public void addResourceHandlers(ResourceHandlerRegistry registry) {
	registry.addResourceHandler("/image/**")
			.addResourceLocations("classpath:/static/image/");
}

这样在浏览器输入http://localhost:8080/image/1.png,会去/static/image目录下寻找1.png文件。

默认的Servlet

重写WebMvcConfigurer接口的configureDefaultServletHandling()方法。

public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
	configurer.enable();
}

所有没法处理的URL最后都会被这个默认的Servlet处理,这样配置后,所有的静态资源如css、js等静态资源都能正常返回了。

例如在浏览器输入http://localhost:8080/static/image/1.png,会去/static/image目录下寻找1.png文件。

相当于以前在spring-mvc.xml中这样配置:

<mvc:default-servlet-handler/>

添加拦截器

重写WebMvcConfigurer接口的addInterceptors方法:

public void addInterceptors(InterceptorRegistry registry) {
	registry.addInterceptor(new MyInterceptor());
}

MyInterceptor需要实现HandlerInterceptor接口:

package com.morris.spring.mvc.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyInterceptor implements HandlerInterceptor {

    // 目标方法运行之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle" + request.getRequestURI());
        return true;
    }

    // 目标方法运行之后执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }

    // 页面响应后执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
}

注意: postHandle对@ResponseBody这种方法没啥用,因为在执行postHandle之前请求就已经返回给浏览器了,如果想在响应中追加一些参数,可以使用ResponseBodyAdvice。

相当于以前在spring-mvc.xml中这样配置:

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.morris.spring.mvc.interceptor.MyInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

源码分析

MVCConfig中的配置是怎么加载到Spring MVC中的呢?

@EnableWebMvc

@EnableWebMvc导入了DelegatingWebMvcConfiguration类。

@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

DelegatingWebMvcConfiguration

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();


	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		// 注入所有的WebMvcConfigurer,这里是MVCConfig类
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}
... ...
	@Override
	protected void addViewControllers(ViewControllerRegistry registry) {
		this.configurers.addViewControllers(registry);
	}

DelegatingWebMvcConfiguration持有我们实现了WebMvcConfigurer接口的类MVCConfig的引用,DelegatingWebMvcConfiguration中的所有方法的调用都会委托给MVCConfig,再来看一下DelegatingWebMvcConfiguration的方法什么时候被调用。

这里以DelegatingWebMvcConfiguration#addViewControllers为例:

看父类WebMvcConfigurationSupport:

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#viewControllerHandlerMapping

@Bean
@Nullable
public HandlerMapping viewControllerHandlerMapping(
	@Qualifier("mvcPathMatcher") PathMatcher pathMatcher,
	@Qualifier("mvcUrlPathHelper") UrlPathHelper urlPathHelper,
	@Qualifier("mvcConversionService") FormattingConversionService conversionService,
	@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
	ViewControllerRegistry registry = new ViewControllerRegistry(this.applicationContext);
	/**
		 * @see DelegatingWebMvcConfiguration#addViewControllers
		 */
	// 模板方法模式
	addViewControllers(registry);

	// SimpleUrlHandlerMapping
	AbstractHandlerMapping handlerMapping = registry.buildHandlerMapping();
	if (handlerMapping == null) {
		return null;
	}
	handlerMapping.setPathMatcher(pathMatcher);
	handlerMapping.setUrlPathHelper(urlPathHelper);
	handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
	handlerMapping.setCorsConfigurations(getCorsConfigurations());
	return handlerMapping;
}

viewControllerHandlerMapping()方法上面有@Bean注解,就会将方法的返回值注入到springmvc容器中,注入HandlerMapping时会调用子类的addViewControllers(),进而调用WebMvcConfigurer.addViewControllers()来实现自定义配置。

这样在DispatcherServlet#onRefresh初始化九大组件时从容器中取HandlerMapping,不会再读取DispatcherServlet.properties文件。

org.springframework.web.servlet.DispatcherServlet#initHandlerMappings

private void initHandlerMappings(ApplicationContext context) {
	this.handlerMappings = null;

	if (this.detectAllHandlerMappings) { // 默认为true
		// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
		// 开启@EnableWebmvc后直接从容器中取,不会再读取DispatcherServlet.properties
		Map<String, HandlerMapping> matchingBeans =
			BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerMappings = new ArrayList<>(matchingBeans.values());
			// We keep HandlerMappings in sorted order.
			AnnotationAwareOrderComparator.sort(this.handlerMappings);
		}
	}
	else {
		try {
			HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
			this.handlerMappings = Collections.singletonList(hm);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default HandlerMapping later.
		}
	}

	// Ensure we have at least one HandlerMapping, by registering
	// a default HandlerMapping if no other mappings are found.
	if (this.handlerMappings == null) {
		// 读取DispatcherServlet.properties
		// 默认BeanNameUrlHandlerMapping、RequestMappingHandlerMapping、RouterFunctionMapping
		this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		if (logger.isTraceEnabled()) {
			logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
						 "': using default strategies from DispatcherServlet.properties");
		}
	}
}

也正因为WebMvcConfigurationSupport中注入了大量的组件,所以有些组件不会再使用DispatcherServlet.properties文件中的默认值了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

morris131

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值