概述
本节我们继续分析HandlerMapping另一个实现类BeanNameUrlHandlerMapping,从类的名字可知,该类会根据请求的url与spring容器中定义的bean的name属性值进行匹配。
本系列文章是基于Spring5.0.5RELEASE。
类图
类的继承关系,如下图:
红框的类就是我们本章要分析的类。
与SimpleUrlHandlerMapping类图对比,BeanNameUrlHandlerMapping类继承自AbstractDetectingUrlHandlerMapping抽象类,其又继承自AbstractUrlHandlerMapping抽象类,再往上继承关系与SimpleUrlHandlerMapping一致。
创建/初始化
上一章我们分析了SimpleUrlHandlerMapping的创建初始化过程,BeanNameUrlHandlerMapping的创建初始化过程与SimpleUrlHandlerMapping一样,方法的入口在抽象类AbstractDetectingUrlHandlerMapping中的initApplicationContext()方法。调用原理参考https://segmentfault.com/a/1190000014951551
分析
- AbstractDetectingUrlHandlerMapping
通过在应用程序上下文中对所有已定义的bean,检测handler与URL的映射。主要代码如下:
// 初始化容器上下文时调用
@Override
public void initApplicationContext() throws ApplicationContextException {
// 调用父类AbstractHandlerMapping初始化拦截器,与SimpleUrlHandlerMapping一样
super.initApplicationContext();
// 处理url和bean name,具体注册调用父类AbstractUrlHandlerMapping类完成
detectHandlers();
}
protected void detectHandlers() throws BeansException {
// 获取应用上下文
ApplicationContext applicationContext = obtainApplicationContext();
if (logger.isDebugEnabled()) {
logger.debug("Looking for URL mappings in application context: " + applicationContext);
}
// 获取上下文中定义的bean
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
applicationContext.getBeanNamesForType(Object.class));
// Take any bean name that we can determine URLs for.
for (String beanName : beanNames) {
// 通过模板方法模式调用BeanNameUrlHandlerMapping子类处理
String[] urls = determineUrlsForHandler(beanName);
if (!ObjectUtils.isEmpty(urls)) {
// 调用父类AbstractUrlHandlerMapping将url与handler存入map
registerHandler(urls, beanName);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
}
}
}
}
- BeanNameUrlHandlerMapping
实现HandlerMapping接口,将url与handler bean进行映射,bean的name属性需以"/"开头,源码如下:
@Override
protected String[] determineUrlsForHandler(String beanName) {
List<String> urls = new ArrayList<>();
if (beanName.startsWith("/")) {
urls.add(beanName);
}
String[] aliases = obtainApplicationContext().getAliases(beanName);
for (String alias : aliases) {
if (alias.startsWith("/")) {
urls.add(alias);
}
}
return StringUtils.toStringArray(urls);
}
实战
- pom文件
引入Spring MVC支持,代码如下:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
- spring配置文件
新建spring MVC配置文件,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"
default-autowire="byName">
<!-- 配置HandlerMapping映射处理器 -->
<bean id="beanNameUrlHandlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
</bean>
<!-- 自定义Handler -->
<bean id="/demo" class="com.github.dalianghe.controller.DemoController"/>
</beans>
- web部署描述文件
配置Spring MVC 前端控制器DispatcherServlet,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>Archetype Created Web Application</display-name>
<servlet>
<!-- Servlet名称,可任意定义,但必须与servlet-mapping中对应 -->
<servlet-name>dispatcher</servlet-name>
<!-- 指定Spring MVC核心控制类,即J2EE规范中的前端控制器(Front Controller) -->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定Spring MVC配置文件,默认在WEB-INF目录下,切名字为[servlet-name]-servlet.xml,此文件中配置web相关内容,比如:指定扫描Controller路径、配置逻辑视图前缀后缀、上传文件等等 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-servlet.xml</param-value>
</init-param>
<!-- 该参数控制是否使用自定义的HandlerMapping
true 从Spring上下文环境中加载HandlerMapping类型的bean
false 加载bean名称为handlerMapping的bean -->
<init-param>
<param-name>detectAllHandlerMappings</param-name>
<param-value>false</param-value>
</init-param>
<!-- 此配置的值为正整数时,表示容器启动时初始化,即调用Servlet的init方法 -->
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<!-- 定义servlet映射 -->
<servlet-mapping>
<!-- 与servlet中servlet-name对应 -->
<servlet-name>dispatcher</servlet-name>
<!-- 映射所有的url -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
- Handler控制器
编写Controller控制器,代码如下:
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DemoController implements Controller{
@Nullable
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
request.getServletContext().log("进入Controller(Handler)处理器。。。" + this);
return null;
}
}
至此,代码编写完毕。
测试
启动程序,访问地址http://localhost:8087/demo,在控制台看到日志信息,说明验证成功。如下图:
总结
本文分析了BeanNameUrlHandlerMapping类,如果看过上篇文章就发现,SimpleUrlHandlerMapping与BeanNameUrlHandlerMapping都实现HandlerMapping接口,即处理url与handler的映射,只是处理的策略不同而已。
BeanNameUrlHanderlMapping有如下不足:
- 处理器bean的id/name为一个url请求路径,前面有"/",怪怪的;
- 如果多个url映射同一个处理器bean,那么就需要定义多个bean,导致容器创建多个处理器实例,占用内存空间;
- 处理器bean定义与url请求耦合在一起。
最后创建了qq群方便大家交流,可扫描加入,同时也可加我qq:276420284,共同学习、共同进步,谢谢!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。