赞
踩
Servlet3.0规范中允许在定义servlet、Filter与Listener三大组件时使用注解,而不用在web.xml进行注册了。Servlet3.0规范允许Web项目没有web.xml配置文件。
主要注解就是
public class MyWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) { // 创建容器 AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext(); //注册配置类 ac.register(AppConfig.class); //必须执行这一步,否则会报错 ac.setServletContext(servletContext); //刷新容器 ac.refresh(); // 创建和注册DispatcherServlet DispatcherServlet servlet = new DispatcherServlet(ac); ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet); registration.setLoadOnStartup(1); registration.addMapping("/"); //接下来可以创建一些过滤器 。。。 } }
@Configuration @ComponentScan("com.shy.ssm") @EnableWebMvc //这个注解没有也行,但是可能会出现问题 public class AppConfig implements WebMvcConfigurer { //配置视图解析器 @Bean public ViewResolver viewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/pages/"); resolver.setSuffix(".jsp"); return resolver; } //配置json转换器 @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(new FastJsonHttpMessageConverter()); } }
public class MyApplicationContext { public static void run() { //指定tomcat的工作目录 //是操作系统的临时目录 File base = new File(System.getProperty("java.io.tmpdir")); Tomcat tomcat = new Tomcat(); tomcat.setPort(80); //告诉tomcat我是一个web项目 //如果你想加入下一行代码,你就必须搞一个jsp依赖 //tomcat.addWebapp("/",base.getAbsolutePath()); //这个其实就是发布项目 tomcat.addContext("/",base.getAbsolutePath()); try { //启动tomcat tomcat.start(); Server server = tomcat.getServer(); AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext(); ac.register(WebConfig.class); ac.refresh(); DispatcherServlet servlet = new DispatcherServlet(ac); //ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet); //registration.setLoadOnStartup(1); //registration.addMapping("/"); Wrapper wrapper = tomcat.addServlet("/", "app", servlet); wrapper.setLoadOnStartup(1); wrapper.addMapping("/"); tomcat.getServer().await(); } catch (LifecycleException e) { e.printStackTrace(); } } }
这种方式其实就是,springmvc帮我们自己提取出来的了一个类,我们只需要进行javaConfig的配置就行了
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[]{RootConfig.class}; } @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[]{AppConfig.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } }
说明:这种方式其实是我把spring自定义的方式提取出来了,就是注册了一个监听器
public class MyWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) { //先注册RootConfig.class AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); rootAppContext.register(RootConfig.class); //注册spring的监听器 //这一步是必须的,因为当注册了这个监听器的话,当spring容器启动的时候就会进行自动配置 //后面会说一下原理的 ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); //listener.setContextInitializers(null); servletContext.addListener(listener); // 开始注册springmvc AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext(); ac.register(AppConfig.class); DispatcherServlet servlet = new DispatcherServlet(ac); //servlet.setContextInitializers(null); //添加根servlet ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet); registration.setLoadOnStartup(1); registration.addMapping("/"); } }
其实总的来说就是应用了servlet3.0的规范,先想一个问题?
为什么当一个类实现了 WebApplicationInitializer接口,就能被容器所进行调用,可以看一下WebApplicationInitializer接口:
package org.springframework.web;
public interface WebApplicationInitializer {
/**
* Configure the given {@link ServletContext} with any servlets, filters, listeners
* context-params and attributes necessary for initializing this web application. See
* examples {@linkplain WebApplicationInitializer above}.
* @param servletContext the {@code ServletContext} to initialize
* @throws ServletException if any call against the given {@code ServletContext}
* throws a {@code ServletException}
*/
void onStartup(ServletContext servletContext) throws ServletException;
}
我们发现一个问题,这个接口并不是servlet的接口,仅仅是spring定义的一个接口,那么这个接口是怎么被servlet调用的呢。
然后想一下servlet3.0的SPA规范,我们可以在项目的根目录下定义一个META-INF/services文件目录,并且添加一个文件javax.servlet.ServletContainerInitializer,当容器启动时候,会调用这个里面的onStartup方法,spring在这里实现了servlet的一个接口
//这个就扣的意思是所有实现了WebApplicationInitializer.class接口的类会统计起来并执行下面的方法 @HandlesTypes(WebApplicationInitializer.class) //下面就是spring实现的servlet的接口,它里面只有一个方法onStartup public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { //用来存放所有的实现了WebApplicationInitializer接口的类 //以后统称为启动类 List<WebApplicationInitializer> initializers = new LinkedList<>(); if (webAppInitializerClasses != null) { //进行遍历执行 for (Class<?> waiClass : webAppInitializerClasses) { // Be defensive: Some servlet containers provide us with invalid classes, // no matter what @HandlesTypes says... if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { initializers.add((WebApplicationInitializer) ReflectionUtils.accessibleConstructor(waiClass).newInstance()); } catch (Throwable ex) { throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); } } } } if (initializers.isEmpty()) { servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); return; } servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); //进行排序, //这里面有一个spring 的接口Ordered AnnotationAwareOrderComparator.sort(initializers); for (WebApplicationInitializer initializer : initializers) { //开始分别执行 initializer.onStartup(servletContext); } } }
看完了以上代码,我们应该明白了,为什么实现了spring的接口会被调用了吧。
当我们看完了自动启动原理,我们看一下spring监听器的原理是什么。
下面就是spring创建的监听器
需要传递相当于spring的配置文件
//创建spring的监听器
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
//需要把监听器注册当servletContext中
servletContext.addListener(listener);
那么我们想一下,spring的监听器什么时候执行的呢?
我们先看一下监听器的源码
//接口说明:ServletContextListener接口,用于接收关于ServletContext生命周期更改的通知事件。<p>为了接收这些通知事件,实现类必须在web应用程序的部署描述符中声明,使用或通过定义的addListener方法之一注册 //ContextLoader:这个是spring自己定义的一个类就是把一些方法抽取出去 public class ContextLoaderListener extends ContextLoader implements ServletContextListener { public ContextLoaderListener() { } public ContextLoaderListener(WebApplicationContext context) { super(context); } /** * 当onStartup方法执行之后,会进行执行 * 这个是接口的方法,实现的servlet的监听器 */ @Override public void contextInitialized(ServletContextEvent event) { //我们发现并没有这个方法就说明在父类中,我们接下来会进到这个父类中进行看看\ //父类很多 我们先看initWebApplicationContext方法 initWebApplicationContext(event.getServletContext()); } /** * 当容器销毁的时候会执行这个方法 */ @Override public void contextDestroyed(ServletContextEvent event) { closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); } }
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { ...抛出异常 } //日志打印 Log logger = LogFactory.getLog(ContextLoader.class); servletContext.log("Initializing Spring root WebApplicationContext"); if (logger.isInfoEnabled()) { logger.info("Root WebApplicationContext: initialization started"); } //记录开始时间 long startTime = System.currentTimeMillis(); try { // 将上下文存储在本地实例变量中,以保证这一点 // 它在ServletContext关闭时可用。 if (this.context == null) { this.context = createWebApplicationContext(servletContext); } //转换我传进来的spring容器 if (this.context instanceof ConfigurableWebApplicationContext) { //转换我传进来的spring容器 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; //按照我们的注册方式,这里会进来的 if (!cwac.isActive()) { // 上下文还没有被刷新——>提供了诸如此类的服务 // 设置父上下文,设置应用程序上下文id,等等 if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> // determine parent for root web application context, if any. //上下文实例是在没有显式父类的情况下注入的——如果有根web应用程序上下文, //则由确定父类。 ApplicationContext parent = loadParentContext(servletContext); //一般情况下为空 cwac.setParent(parent); } //这里是核心,进行spring的刷新功能Refresh //我接下来会说一下这个方法 configureAndRefreshWebApplicationContext(cwac, servletContext); } } ...下面的没什么用 }
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null) { wac.setId(idParam); } else { wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath())); } } //重要 wac.setServletContext(sc); String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (configLocationParam != null) { wac.setConfigLocation(configLocationParam); } //wac环境的#initPropertySources将在任何情况下调用上下文 // 刷新;在这里确保servlet属性源是合适的吗 // 用于#refresh之前发生的任何后处理或初始化 ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(sc, null); } customizeContext(sc, wac); //刷新容器 wac.refresh(); }
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) { //这里和spring容器基本一样的,也是设计 AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader(beanFactory); ClassPathBeanDefinitionScanner scanner = getClassPathBeanDefinitionScanner(beanFactory); BeanNameGenerator beanNameGenerator = getBeanNameGenerator(); if (beanNameGenerator != null) { reader.setBeanNameGenerator(beanNameGenerator); scanner.setBeanNameGenerator(beanNameGenerator); beanFactory.registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator); } ScopeMetadataResolver scopeMetadataResolver = getScopeMetadataResolver(); if (scopeMetadataResolver != null) { reader.setScopeMetadataResolver(scopeMetadataResolver); scanner.setScopeMetadataResolver(scopeMetadataResolver); } //这里进行了执行 if (!this.annotatedClasses.isEmpty()) { if (logger.isInfoEnabled()) { logger.info("Registering annotated classes: [" + StringUtils.collectionToCommaDelimitedString(this.annotatedClasses) + "]"); } //注册传进来配置文件 reader.register(ClassUtils.toClassArray(this.annotatedClasses)); } //这里也没有被执行到 if (!this.basePackages.isEmpty()) { if (logger.isInfoEnabled()) { logger.info("Scanning base packages: [" + StringUtils.collectionToCommaDelimitedString(this.basePackages) + "]"); } scanner.scan(StringUtils.toStringArray(this.basePackages)); } //这里没有被执行到 String[] configLocations = getConfigLocations(); if (configLocations != null) { for (String configLocation : configLocations) { try { Class<?> clazz = ClassUtils.forName(configLocation, getClassLoader()); if (logger.isInfoEnabled()) { logger.info("Successfully resolved class for [" + configLocation + "]"); } reader.register(clazz); } catch (ClassNotFoundException ex) { ... int count = scanner.scan(configLocation); ... } } } }
功能:
总结:
完成这个方法会新添加6个忽略接口,2个Bean后置处理器,还有3个单例对象
配置spring和springmvc的区别:
spring这个方法什么都没有实现,但是springmvc却实现了,并进行了一些重要的设置
功能:
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { //将ServletContext传递给实现的bean的实现 beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig)); //添加了两个忽略接口 beanFactory.ignoreDependencyInterface(ServletContextAware.class); beanFactory.ignoreDependencyInterface(ServletConfigAware.class); //注册web环境的范围 //并且添加了4个注册可解析依赖项 WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext); //注册环境 //这里挺重要的,因为里面设置了几个单例对象 WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig); } public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, @Nullable ServletContext sc) { beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope()); beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope()); if (sc != null) { ServletContextScope appScope = new ServletContextScope(sc); beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope); // Register as ServletContext attribute, for ContextCleanupListener to detect it. sc.setAttribute(ServletContextScope.class.getName(), appScope); } // beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory()); beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory()); beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory()); beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory()); if (jsfPresent) { FacesDependencyRegistrar.registerFacesDependencies(beanFactory); } }
其实这个方法之前学习spring源码的时候,已经重点的讲过了,spring的组件扫描什么的都是在这里执行的,其实这个就相当于配置咱们的工厂,里面会执行各种各样的BeanFactory后置处理器,主要完成的功能就是组件扫描,干扰工厂的执行,解析并注册所有的BeabDefinition。需要说明一下,这里所有的后置处理器都会先实例化一下就是调用getSingleton
ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
功能:
先来说一下servlet的启动说明:
当把一个servlet的启动级别设置为>=0的时候(registration.setLoadOnStartup(1);)这个servlet会在tomcat启动之后,并且监听器执行完之后,执行这个servlet的init方法,而我们的springmvc核心servlet是这样的,当我们执行完ContextLoaderListener的这个spring监听器之后,会执行init方法,可能DispatchServlet中没有,他是在HttpServletBean中进行的定义。这个类才是真正的类似于我们自己写的servlet继承HttpServlet的servlet。
public final void init() throws ServletException { ... // Set bean properties from init parameters. ... // Let subclasses do whatever initialization they like. // 好了,没用的东西干掉之后,就剩下这一个东西了 // 继续进去看看把 其实也是很简单的一个方法 initServletBean(); if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } } protected final void initServletBean() throws ServletException { ...日志打印 long startTime = System.currentTimeMillis(); try { //主要就是这里有用了 //这里才是重新启动spring的容器进行注册配置类的扫描配置 // this.webApplicationContext = initWebApplicationContext(); //这个是一个空的方法没得用 initFrameworkServlet(); } ..日志和异常信息 }
总结:其实我们都用之前创建的一个spring容器就可以了, spring就会进行所有的扫描了, 其实对于spring来说,你先要配置springmvc主要就是配置spring的监听器,配置完监听器之后,所有的功能都好使了,但是对于DispatcherServlet来说,init方法还是要执行的,当时会初始化一些spring的容器,如果你把所有的配置文件一起加载的时候。
它们的BeanDefinition类型:ConfigurationClassBeanDefinition
requestMappingHandlerMapping:RequestMappingHandlerMapping.class
mvcPathMatcher:AntPathMatcher.class
mvcUrlPathHelper:UrlPathHelper.class
mvcContentNegotiationManager:ContentNegotiationManager.class
viewControllerHandlerMapping:HandlerMapping.class
beanNameHandlerMapping:BeanNameUrlHandlerMapping.class
resourceHandlerMapping:HandlerMapping.class
mvcResourceUrlProvider:ResourceUrlProvider.class
defaultServletHandlerMapping:HandlerMapping.class
requestMappingHandlerAdapter:RequestMappingHandlerAdapter.class
mvcConversionService:FormattingConversionService.class
mvcValidator:Validator.class
mvcUriComponentsContributor:CompositeUriComponentsContributor.class
httpRequestHandlerAdapter:HttpRequestHandlerAdapter.class
simpleControllerHandlerAdapter:SimpleControllerHandlerAdapter.class
handlerExceptionResolver:HandlerExceptionResolver.class
mvcViewResolver:ViewResolver.class
mvcHandlerMappingIntrospector:HandlerMappingIntrospector.class
protected void registerListeners() { // Register statically specified listeners first. // 拿到所有的监听器 for (ApplicationListener<?> listener : getApplicationListeners()) { getApplicationEventMulticaster().addApplicationListener(listener); } // Do not initialize FactoryBeans here: We need to leave all regular beans // uninitialized to let post-processors apply to them! String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false); for (String listenerBeanName : listenerBeanNames) { getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName); } // Publish early application events now that we finally have a multicaster... Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents; this.earlyApplicationEvents = null; if (earlyEventsToProcess != null) { for (ApplicationEvent earlyEvent : earlyEventsToProcess) { getApplicationEventMulticaster().multicastEvent(earlyEvent); } } }
添加监听器:
第一个实例化的额类就是这个类,当实例化这个类的时候在执行invokeInitMethods初始化方法的时候会执行
((InitializingBean) bean).afterPropertiesSet();
然后会走到initHandlerMethods方法
protected void initHandlerMethods() { 。。。 //这里会拿到所有的beanName //controller加上springmvc自定一配置的一些bean一共18个再加上我们自己定义的bean //再加上系统默认 String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) : obtainApplicationContext().getBeanNamesForType(Object.class)); //遍历,就是查找出被@RequestMapping和@Comtroller标注的类 for (String beanName : beanNames) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { Class<?> beanType = null; try { // beanName对应的类型 beanType = obtainApplicationContext().getType(beanName); } ...(异常) //这里才是判断那些类事@Controller或者@RequestMapping标注的类 //看 一下这个方法 /* 方法其实很简单,就是判断一下这个类有没有被这两个注解进行标注 可能有人会问,那既然是@Controller,为什么还有看一下RequestMapping.class注解呢 我最开始看的时候也有点觉得没必要,但是可能是这样的情况,我们把一个Controller类加了 @Component,那么他也会被加载到容器中,那么问题来了,我们都知道,我们的controller类, 类上面也是可以标注注解RequestMapping的,如果这样的话 就不能进行判断了。 所以说想要标注为一个Controller类,你可以不用Controller注解,但是你就必须要弄一个 RequestMapping注解,当然 你要是直接标注为COntroller注解就更没有问题了 return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)); */ if (beanType != null && isHandler(beanType)) { //检测所有的handlerMapping标注的方法 //我们接下来就会进到这个方法去看看 detectHandlerMethods(beanName); } } } //里面居然是空的方法 handlerMethodsInitialized(getHandlerMethods()); }
detectHandlerMethods方法分两步:首先从传入的处理器中找到符合要求的方法,然后使用registerHandlerMethod进行注册(也就是保存到Map中)。从这里可以看出spring其实是将处理请求的方法所在的类看作处理器了,而不是处理请求的方法,不过很多地方需要使用处理请求的方法作为处理器来理解才容易理解,而且spring本身在有些场景中也将处理请求的方法HandlerMethod作为处理器了,比如, AbstractHandlerMethodMapping的getHandlerInternal方法返回的处理器就是HanderMethod类型,在RequestMappingHandlerAdapter中判断是不是支持的Handler时也是通过检查是不是HandlerMethod类型来判断的。本书中所说的处理器随当时的语境可能是指处理请求的方法也可能指处理请求方法所在的类。从Handler里面获取可以处理请求的Method的方法使用了Handler-MethodSelectorselectMethods,这个方法可以遍历传入的Handler的所有方法,然后根据第二个MethodFilter类型的参数筛选出合适的方法。这里的MethodFilter使用了匿名类,具体判断的逻辑是通过在匿名类里调用getMappingForMethod方法获取Method的匹配条件,如果可以获取到则认为符合要求,否则不符合要求,另外如果符合要求则会将匹配条件保存到mappings里面,以备后面使用。getMappingForMethod是模板方法,具体实现在RequestMappingHandlerMapping里,它是根据@RequestMapping注释来找匹配条件的,如果没有@RequestMapping注释则返回null,如果有则根据注释的内容创建RequestMappingInfo类型的匹配条件并返回,代码如下:
protected void detectHandlerMethods(Object handler) { //获取handler的类型 Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) { //如果是cglib代理的子对象类型,则返回父类型 Class<?> userType = ClassUtils.getUserClass(handlerType); //下面是这个方法,我们进去看一下看 // 获取当前bean里所有符合handler要求的Method Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> { try { return getMappingForMethod(method, userType); } //这个没有执行,应该是进行代理的时候会执行把 methods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); // 将符合要求的Method注册起来,也就是保存到map中 registerHandlerMethod(handler, invocableMethod, mapping); }); } } public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) { final Map<Method, T> methodMap = new LinkedHashMap<>(); Set<Class<?>> handlerTypes = new LinkedHashSet<>(); Class<?> specificHandlerType = null; //肯定不是代理类,除非我们自已进行了切面 if (!Proxy.isProxyClass(targetType)) { specificHandlerType = ClassUtils.getUserClass(targetType); handlerTypes.add(specificHandlerType); } //添加所有的handlerTypes handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType)); //遍历 for (Class<?> currentHandlerType : handlerTypes) { final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType); //这里就是拿到方法,我们还是进去看看 ReflectionUtils.doWithMethods(currentHandlerType, method -> { //。。。我也说不明白,不过正常情况下的类就会简单的直接返回 Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); //这个终于调用了上面传进来的lambda表达式 //getMappingForMethod(method, userType); T result = metadataLookup.inspect(specificMethod); if (result != null) { Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) { methodMap.put(specificMethod, result); } } }, ReflectionUtils.USER_DECLARED_METHODS); } return methodMap; } public static void doWithMethods(Class<?> clazz, MethodCallback mc, @Nullable MethodFilter mf) { //拿到这个类种的所有方法 Method[] methods = getDeclaredMethods(clazz); for (Method method : methods) { if (mf != null && !mf.matches(method)) { continue; } try { //这个就是用的Lambda传进来的参数 //这个需要去上面看看 mc.doWith(method); } ... } //这个很容易理解的,就是找到他的父类,并且递归进行调用 if (clazz.getSuperclass() != null) { doWithMethods(clazz.getSuperclass(), mc, mf); } //拿到接口,请注意这里就是说明,controller类你只能有父类但是不能和接口同时共存 else if (clazz.isInterface()) { for (Class<?> superIfc : clazz.getInterfaces()) { doWithMethods(superIfc, mc, mf); } } } protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { //进去看看把,哎 心态崩了,怎么这么深啊 //里面就是把RequestMapping方法解析,出来,这个封装了很多东西,当然全是空的,最重要的就是映射 //出来的地址例如:/user/list RequestMappingInfo info = createRequestMappingInfo(method); if (info != null) { RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); if (typeInfo != null) { info = typeInfo.combine(info); } } return info; }
就是工具类,没啥可说的
工具类
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
但是这是在哪里调用的呢?跟踪代码我们发现
AbstractApplicationContext.refresh().finishRefresh().publishEvent(new ContextRefreshedEvent(this));
protected void publishEvent(Object event, @Nullable ResolvableType eventType) { // Decorate event as an ApplicationEvent if necessary ApplicationEvent applicationEvent; if (event instanceof ApplicationEvent) { applicationEvent = (ApplicationEvent) event; } ...这个可不是和上面的是一起的 else { //这里才是重要的调用DispatcherServlet初始化 getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); } // Publish event via parent context as well... // 这个时候还需要初始化一下父类的 if (this.parent != null) { if (this.parent instanceof AbstractApplicationContext) { ((AbstractApplicationContext) this.parent).publishEvent(event, eventType); } else { this.parent.publishEvent(event); } } }
/** * Initialize the strategy objects that this servlet uses. * <p>May be overridden in subclasses in order to initialize further strategy objects. */ protected void initStrategies(ApplicationContext context) { //初始化文件上传解析器 //里面其实就是实例化一下文件上传的组件 //怎么说呢,就是当我们配置一个文件上传类之后,这里会获取到然后并设置进servlet initMultipartResolver(context); //初始化语言环境解析器(就是国际化支持) //同上一样 initLocaleResolver(context); //初始化主题解析器 //同上一样 initThemeResolver(context); //初始化所有的HandlerMappings //这个就不一样了,他会把所有的handlerMapping整合到handlerMappings这个集合种 //一共有handlerMapping,这五个是springmvc帮忙自动注册的 //RequestMappingHandlerMapping //BeanNameUrlHandlerMapping //还剩下三个就是WebMvcConfigurationSupport.EmptyHandlerMapping initHandlerMappings(context); //初始化所有的HandlerAdapters //一共三个HandlerAdapters //RequestMappingHandlerAdapter //HttpRequestHandlerAdapter //SimpleControllerHandlerMapping initHandlerAdapters(context); //初始化所有的异常解析器 //就一个HandlerExceptionResolverComposite //ExceptionHandlerExceptionResolver //ResponseStatusExceptionResolver //DefaultHandlerExceptionResolver initHandlerExceptionResolvers(context); //初始化所有的请求查看名称转换器 //这个只有一个默认的DefaultRequestToViewNameTranslator initRequestToViewNameTranslator(context); //初始化试图解析器 //算上我们自己定义的视图解析器一共两个 //默认的是ViewResolversComposite:这个就是所有的ViewResolvers添加到这里,里面有一个集合 //我们自己定义的就是InternalResourceViewResolver initViewResolvers(context); //Flash集合管理器 initFlashMapManager(context); }
直接上代码和注释吧
package com.shy.ssm.controller; @Component public class MyHandlerMapping implements HandlerMapping , Ordered { @Override public HandlerExecutionChain getHandler(HttpServletRequest request) { String s = request.getRequestURI(); System.out.println(s); return null; } /** * 这个接口是spring提供的排序接口,当这个值越小说明这个这个越先被调用或者初始化 * 如果说我们自己定义了一个HandlerMapping但是没有设置这个值的话,他会排序到最后 * 所以说当你不想使用默认的RequestMapping,你可以先默认使用这个,并且值设置为0, * 但是还需要注意的是,因为springmvc的hanflermapping会通过WebMvcConfiguration * WebMvcConfigurationSupport所Import导入进来的,所以说能,你想让它先于RequestMapping执行 * 就需要先把它通过springmvc进行扫描,为什么呢?因为如果spring容器进行扫描的话,这个类会实例化在 * 父容器种,而spring会先调用当前容器,所以说必须spring进行扫描,并且还要在Import注入之前,所以说只能扫描 * 并且放在controlelr包中 当然如果你想让自己的起作用的话 那就一定不能返回空 * @return */ @Override public int getOrder() { return 0; } }
接口继承图
AbstractHandlerMapping是HandlerMapping抽象实现,AbstractHandlerMapping采用模板模式设计了HandlerMapping实现的整体结构,子类只需要通过模板方法提供一些初始值或者具体的算法即可。将AbstractHandlerMapping分析透对整个HandlerMapping实现方式的理解至关重要。在Spring MVC中有很多组件都是采用的这种模式-首先使用一个抽象实现采用模板模式进行整体设计,然后在子类通过实现模板方法具体完成业务,所以在分析Spring MVC源码的过程中尤其要重视对组件接口直接实现的抽象实现类的分析。HandlerMapping的作用是根据request查找Handler和Interceptors。获取Handler的过程通过模板方法getHandlerInternal交给了子类。AbstractHandlerMapping中保存了所用配置的Interceptor,在获取到Handler后会自己根据从request提取的lookupPath将相应的Interceptors装配上去,当然子类也可以通过getHandlerlnternal方法设置自己的Interceptor,getHandlerinternal的返回值为Object类型。
AbstractHandlerMapping的初始化:
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
当我们初始化RequestMappingHandlerMapping的时候,会执行ApplicationContextAwareProcessor后置处理器
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException { AccessControlContext acc = null; if (System.getSecurityManager() != null && (bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware || bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware || bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) { acc = this.applicationContext.getBeanFactory().getAccessControlContext(); } if (acc != null) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { invokeAwareInterfaces(bean); return null; }, acc); } else { //主要是这里 invokeAwareInterfaces(bean); } return bean; } //初始化AbstractHamdlerMapping protected void initApplicationContext() throws BeansException { //extendlnterceptors是模板方法, //用于给子类提供一个添加(或者修改) Interceptors的入口, //不过在现有Spring MVC的实现中并没有使用。 extendInterceptors(this.interceptors); //detectMappedinterceptors方法用于将Spring MVC容器及父容器中的所有MappedInterceptor类型的 //Bean添加到mappedInterceptors属性,代码如下: /* mappedInterceptors.addAll( BeanFactoryUtils.beansOfTypeIncludingAncestors( obtainApplicationContext(), MappedInterceptor.class, true, false).values()); */ detectMappedInterceptors(this.adaptedInterceptors); //这个方法很简单 initInterceptors(); }
AbstractHandlerMapping中的Interceptor有三个List类型的属性: interceptors, mappedInterceptors和adaptedInterceptors,分别解释如下:
AbstractHandlerMapping的创建其实就是初始化这三个Interceptor
HandlerMapping是通过getHandler方法来获取处理器Handler和拦截器Interceptor的,下面看一下在AbstractHandlerMapping中的实现方法。
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //寻找handler //我们接下来看一下这个方法把 Object handler = getHandlerInternal(request); //如果没有得到的话,就使用默认的 if (handler == null) { handler = getDefaultHandler(); } if (handler == null) { return null; } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } //用于添加拦截器 HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (CorsUtils.isCorsRequest(request)) { CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request); CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; }
这个就是AbstractUrlHandlerMapping中,后面还会进行详细介绍。
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { //返回给定请求的映射查找路径,如果适用,在当前servlet映射中,或者在web应用程序中。 //检测在RequestDispatcher include中调用的包含请求URL。 //其实就是/list/user String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); //这个就是加锁,防止多线程问题 this.mappingRegistry.acquireReadLock(); try { //开始得到将要执行的方法 //其实里面挺简单的,还是不看了 //当然最先拿出来的是HandlerMappingInfo HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); ...打印日志 //这里是用过beanName从容器中拿出来对象然后重新创建一个handlerMethod return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { //这个是解开锁 this.mappingRegistry.releaseReadLock(); } }
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); for (HandlerInterceptor interceptor : this.adaptedInterceptors) { if (interceptor instanceof MappedInterceptor) { MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); } } else { chain.addInterceptor(interceptor); } } return chain; }
AbstractUrlHandlerMapping系列都继承白AbstractUrlHandlerMapping,从名字就可以看出它是通过url来进行匹配的。此系列大致原理是将url与对应的Hander保存在一个Map中,在getHandlerlnternal方法中使用url从Map中获取Handler, AbstractUrlHandlerMapping中实现了具体用url从Map中获取Handler的过程,而Map的初始化则交给了具体的子孙类去完成。这里的Map就是定义在AbstractUrlHandlerMapping中的handlerMap,另外还单独定义了处理“/"请求的处理器rootHandler,定义如下:
@Nullable
private Object rootHandler;
private boolean useTrailingSlashMatch = false;
private boolean lazyInitHandlers = false;
//维护得保存Handler集合
private final Map<String, Object> handlerMap = new LinkedHashMap<>();
查找给定请求的URL路径的处理程序。
@Override @Nullable protected Object getHandlerInternal(HttpServletRequest request) throws Exception { String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); //用于使用lookupPath从Map中查找Handler,但是很多时候并不能直接从Map中进行get, //因为很多handler都是用了Pattern得匹配模式,如"/user/list/*",这里得*表示得是任意内容而不是* //如果Pattern中包含PathVariable Object handler = lookupHandler(lookupPath, request); if (handler == null) { // We need to care for the default handler directly, since we need to // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well. Object rawHandler = null; if ("/".equals(lookupPath)) { rawHandler = getRootHandler(); } if (rawHandler == null) { rawHandler = getDefaultHandler(); } if (rawHandler != null) { // Bean name or resolved handler? if (rawHandler instanceof String) { String handlerName = (String) rawHandler; rawHandler = obtainApplicationContext().getBean(handlerName); } validateHandler(rawHandler, request); handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); } } if (handler != null && logger.isDebugEnabled()) { logger.debug("Mapping [" + lookupPath + "] to " + handler); } else if (handler == null && logger.isTraceEnabled()) { logger.trace("No handler mapping found for [" + lookupPath + "]"); } return handler; }
lookupHandler方法用于使用lookupPath从Map中查找Handler,不过很多时候并不能直接从Map中get到,因为很多Handler都是用了Pattern的匹配模式,如"/show/articl/",这里的星号可以代表任意内容而不是真正匹配url中的星号,如果Pattern中包含Path Variable也不能直接从Map中获取到。另外,一个url还可能跟多个Pattern相匹配,这时还需要选择其中最优的,所以查找过程其实并不是直接简单地从Map里获取,单独写一个方法来做也是应该的。lookupHandler的代码如下:
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception { // Direct match? // 直接从map中进行获取 Object handler = this.handlerMap.get(urlPath); if (handler != null) { // Bean name or resolved handler? // 如果是String类型则直接从容器中进行获取 if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); return buildPathExposingHandler(handler, urlPath, urlPath, null); } // Pattern匹配 match? // Pattern匹配,比如使用带*号得模式与url进行匹配 List<String> matchingPatterns = new ArrayList<>(); for (String registeredPattern : this.handlerMap.keySet()) { // 这里会是使用AntPathMatcher里面得doMatch方法进行得,就是对字符串得各种匹配 if (getPathMatcher().match(registeredPattern, urlPath)) { matchingPatterns.add(registeredPattern); } else if (useTrailingSlashMatch()) { if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) { matchingPatterns.add(registeredPattern + "/"); } } } String bestMatch = null; Comparator<String> patternComparator = getPathMatcher(). getPatternComparator(urlPath); if (!matchingPatterns.isEmpty()) { //进行排序 matchingPatterns.sort(patternComparator); ...(打印日志) bestMatch = matchingPatterns.get(0); } //当 if (bestMatch != null) { handler = this.handlerMap.get(bestMatch); if (handler == null) { if (bestMatch.endsWith("/")) { handler = this.handlerMap.get( bestMatch.substring(0, bestMatch.length() - 1)); } if (handler == null) { ...抛出异常 } } // Bean name or resolved handler? // 如果是String类型得直接从容器中获取 if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath); // 可能有多种“最佳模式”, // 让我们确保有正确的URI模板变量 // 为了他们所有人 // 之前是通过sort方法进行排序,然后拿第一个作为bestPatternMatch得,不过可能有多个顺序相同, // 也就是sort方法返回0,这里就是处理这种情况 Map<String, String> uriTemplateVariables = new LinkedHashMap<>(); for (String matchingPattern : matchingPatterns) { if (patternComparator.compare(bestMatch, matchingPattern) == 0) { Map<String, String> vars = getPathMatcher().extractUriTemplateVariables( matchingPattern, urlPath); Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables( request, vars); uriTemplateVariables.putAll(decodedVars); } } ...(打印日志) return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables); } // No handler found... return null; }
buildPathExposingHandler方法用于给查找到的Handler注册两个拦截器PathExposingHandlerlnterceptor和 UriTremplateVariablesHandlerInterceptor,这是两个内部拦截器,主要作用是将与当前url实际匹配的Pattern、匹配条件(后面介绍)和url模板参数等设置到request的属性里,这样在后面的处理过程中就可以直接从request属性中获取,而不需要再重新查找一遍了,代码如下:
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern, String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) { HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler); //注册拦截器 chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping)); if (!CollectionUtils.isEmpty(uriTemplateVariables)) { //注册拦截器 chain.addInterceptor( new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables)); } // return chain; } /*接下来就是执行PathExposingHandlerInterceptor他的拦截方法了,里面会设置一些request属性*/ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { //在映射中暴露路径 //呃呃呃 其实就是向request中设置了两个值 exposePathWithinMapping(this.bestMatchingPattern, this.pathWithinMapping, request); request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, handler); request.setAttribute(INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings()); return true; } /**UriTemplateVariablesHandlerInterceptor*/ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 也是设置request属性 exposeUriTemplateVariables(this.uriTemplateVariables, request); return true; }
Map的初始化,它通过registerHandler方法进行,这个方法承担AbstractUrlHandlerMapping的创建工作,不过和之前的创建不同的是这里的registerHandler方法并不是自己调用,也不是父类调用,而是子类调用。这样不同的子类就可以通过注册不同的Handler将组件创建出来。这种思路也值得我们学习和借鉴。AbstractUrlHandlerMapping中有两个registerHandler方法,第一个方法可以注册多个url到一个处理器,处理器用的是String类型的beanName,这种用法在前面已经多次见到过,它可以使用beanName到spring的容器里找到真实的bean做处理器。在这个方法里只是遍历了所有的url,然后调用第二个registerHandler方法具体将Hander注册到Map上。第二个registerHandler方法的具体注册过程也非常简单,首先看Map里原来有没有传入的url,如果没有就put进去,如果有就看一下原来保存的和现在要注册的Handler是不是同一个,如果不是同一个就有问题了,总不能相同的url有两个不同的Handler吧(这个系列只根据url查找Handler)!这时就得抛异常。往Map里放的时候还需要看一下url是不是处理"/“或者“*”,如果是就不往Map里放了,而是分别设置到rootHandler和defaultHandler,具体代码如下:
protected void registerHandler(String[] urlPaths, String beanName)
throws BeansException, IllegalStateException {
Assert.notNull(urlPaths, "URL path array must not be null");
for (String urlPath : urlPaths) {
registerHandler(urlPath, beanName);
}
}
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException { Assert.notNull(urlPath, "URL path must not be null"); Assert.notNull(handler, "Handler object must not be null"); Object resolvedHandler = handler; // Eagerly resolve handler if referencing singleton via name. //如果Handler是String类型而且没有设置1azyInitHandlers则从SpringMvc容器中获取Handler if (!this.lazyInitHandlers && handler instanceof String) { String handlerName = (String) handler; ApplicationContext applicationContext = obtainApplicationContext(); if (applicationContext.isSingleton(handlerName)) { resolvedHandler = applicationContext.getBean(handlerName); } } Object mappedHandler = this.handlerMap.get(urlPath); if (mappedHandler != null) { if (mappedHandler != resolvedHandler) { ...(抛出异常信息) } } else { if (urlPath.equals("/")) { ...(打印日志) setRootHandler(resolvedHandler); } else if (urlPath.equals("/*")) { ...(打印日志) setDefaultHandler(resolvedHandler); } else { this.handlerMap.put(urlPath, resolvedHandler); ...(打印日志) } } } }
SimpleUrlHandlerMapping定义了一个Map变量(自己定义一个Map主要有两个作用,第-是方便配置,第二是可以在注册前做一些预处理,如确保所有url都以“/"开头),将所有的url和Handler的对应关系放在里面,最后注册到父类的Map中;而AbstractDetectingUrlHandlerMapping则是将容器中的所有bean都拿出来,按一定规则注册到父类的Map中。下面分别来看一下SimpleUrlHandlerMapping在创建时通过重写父类的initApplicationContext方法调用了registerHandlers方法完成Handler的注册, registerHandlers内部又调用了AbstractUrlHandlerMapping的registerHandler方法将我们配置的urlMap注册到AbstractUrlHandlerMapping的Map中(如果要使用SimpleUrlHandlerMapping就需要在注册时给它配置urlMap),代码如下
//这里就是自己维护得集合
private final Map<String, Object> urlMap = new LinkedHashMap<>();
//除了超类的初始化之外,还调用{@link #registerHandlers}方法。
@Override
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
registerHandlers(this.urlMap);
}
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException { if (urlMap.isEmpty()) { logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping"); } else { urlMap.forEach((url, handler) -> { // Prepend with slash if not already present. // 如果没有出现,在前面加上斜杠。 if (!url.startsWith("/")) { url = "/" + url; } // Remove whitespace from handler bean name. // 从处理程序bean名中删除空格。 if (handler instanceof String) { handler = ((String) handler).trim(); } registerHandler(url, handler); }); } }
AbstractDetectingUrlHandlerMapping也是通过重写initApplicationContext来注册Handler的,里面调用了detectHandlers方法,在detectHandlers中根据配置的detectHand-lersInAncestorContexts参数从Spring MVC容器或者Spring MVC及其父容器中找到所有bean的beanName,然后用determineUrlsForHandler方法对每个beanName解析出对应的urls,如果解析结果不为空则将解析出的urls和beanName (作为Handler)注册到父类的Map,注册方法依然是调用AbstractUrlHandlerMapping的registerHandler方法。使用beanName解析urls的determineUrlsForHandler方法是模板方法,交给具体子类实现。AbstractDetectingUrlHandlerMapping类非常简单,代码如下:
@Override
public void initApplicationContext() throws ApplicationContextException {
//调用父类得初始化方法
super.initApplicationContext();
detectHandlers();
}
protected void detectHandlers() throws BeansException { ApplicationContext applicationContext = obtainApplicationContext(); ...(打印日志) // 从容器中获取所有得beanName String[] beanNames = (this.detectHandlersInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) : applicationContext.getBeanNamesForType(Object.class)); // 接受任何我们可以确定url的bean名称。 // 对每个beanName解析url,如果能解析到就注册到父类得Map中 for (String beanName : beanNames) { //我们进去看看这个方法 // 使用beanName解析url,是模板方法,子类具体实现 String[] urls = determineUrlsForHandler(beanName); if (!ObjectUtils.isEmpty(urls)) { // URL paths found: Let's consider it a handler. // 找到URL路径:让我们把它看作一个处理程序。 // 这里会调用父类得方法 registerHandler(urls, beanName); } else { ...(打印日志) } } } 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); }
AbstractDetectingUrlHandlerMapping现在只有一个子类了,就是BeanNameUrlHandlerMapping
//就只有这一个方法
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);
}
从前面HandlerMapping的结构图可以看出, AbstractHandlerMethodMapping系列的结构非常简单,只有三个类:
这三个类依次继承, AbstractHandlerMethodMapping直接继承自AbstractHandlerMapping。虽然看起来这里的结构要比UrlHandlerMapping简单很多,但是并没有因为类结构简单而降低复杂度。AbstractHandlerMethodMapping系列是将Method作为Handler来使用的,这也是我们现在用得最多的一种Handler,比如经常使用的@RequestMapping所注释的方法就是这种Handler,它专门有一个类型HandlerMethod,也就是Method类型的Handler这个系列还是比较复杂的,所以使用“器用分析法”进行分析。
相比于之前的版本,这里仅仅维护了一个内部集合类
//这个就是一个内部类,我们看一下
private final MappingRegistry mappingRegistry = new MappingRegistry();
class MappingRegistry { // 这里的泛型就是来自当前类的定义 // 官方解析 //T - the mapping for a HandlerMethod containing the conditions needed to match the // handler method to incoming request. //T - HandlerMethod的映射,其中包含将处理程序方法匹配到传入请求所需的条件 //也就是用来代表匹配Handler的条件 //专门使用的一种类,这里的条件就不只是url了,还可以有很多其他条件,如request的类型. (Get, Post等)、 //请求的参数、Header等都可以作为匹配HandlerMethod的条件。默认使用的是RequestMappingInfo,从 //RequestMappingInfoHandlerMapping的定义就可以看出。 /*public abstract class RequestMappingInfoHandlerMapping //可以看出来确实是RequestMappingInfo extends AbstractHandlerMethodMapping<RequestMappingInfo> */ private final Map<T, MappingRegistration<T>> registry = new HashMap<>(); // private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>(); // private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>(); private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>(); private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>(); private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); }
RequestMappingHandlerMapping
RequestMappingInfo实现了RequestCondition接口,此接口专门用于保存从request提取出的用于匹配Handler的条件,结构如图所示。
抽象实现AbstractRequestCondition中重写了equals, hashCode和toString三个方法,有8个子类,除了CompositeRequestCondition外每一个子类表示一种匹配条件。比如, PatternsRequestCondition使用url做匹配, RequestMethodsRequestCondition使用RequestMethod做匹配等。CompositeRequestCondition本身并不实际做匹配,而是可以将多个别的RequestCondition封装到自己的一个变量里,在用的时候遍历封装RequestCondition的那个变量里所有的RequestCondition进行匹配,也就是大家所熟悉的责任链模式,这种模式在Spring MVC中非常常见,类名一般是CompositeXXX或者xXXComposite的形式,它们主要的作用就是为了方便调用。
RequestCondition的另一个实现就是这里要用的RequestMappinglnfo,它里面其实是用七个变量保存了七个RequestCondition,在匹配时使用那七个变量进行匹配,这也就是可以在@RequestMapping中给处理器指定多种匹配方式的原因。
private final String name; private final PatternsRequestCondition patternsCondition; private final RequestMethodsRequestCondition methodsCondition; private final ParamsRequestCondition paramsCondition; private final HeadersRequestCondition headersCondition; private final ConsumesRequestCondition consumesCondition; private final ProducesRequestCondition producesCondition; private final RequestConditionHolder customConditionHolder;
介绍一些集合的作用
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
mappingLookup:保存着匹配条件(也就是RequestCondition)和HandlerMethod的对应关系。
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
urlLookup :保存着url与匹配条件(也就是RequestCondition)的对应关系,当然这里的url是Pattern式的,可以使用通配符。另外,这里使用的Map并不是普通的Map,而是MultiValueMap,这是一种一个key对应多个值的Map,其实它的value是一个List类型的值,看MultiValueMap的定义就能明白, MultiValueMap定义如下:
public interface MultiValueMap<K, V> extends Map<K, List<V>>
由于RequestCondition可以同时使用多种不同的匹配方式而不只是url一种,所以反过来说同一个url就可能有多个RequestCondition与之对应。这里的RequestCondition其实就是在@RequestMapping中注释的内容。
private final Map<String, List> nameLookup = new ConcurrentHashMap<>();
nameLookup :这个Map是Spring MVC4新增的,它保存着name与HandlerMethod的对应关系,也就是说一个name可以有多个HandlerMethod。这里的name是使用HandlerMethodMappingNamingStrategy策略的实现类从HandlerMethod中解析出来的,默认使用RequestMappingInfoHandlerMethodMappingNamingStrategy实现类,解析规则是:类名里的大写字母组合+ “#” +方法名。这个Map在正常的匹配过程并不需要使用,它主要用在MvcUriComponentsBuilder里面,可以用来根据name获取相应的url,比如:
MvcUricomponentsBuilder.MethodArgumentBuilder uriComponents = MvcUricomponentsBuilder.fromMappingName (“GC#index”);
string uri = uricomponents.build();
这样就可以构造出url——http://ocalhost:8080/index,也就是前面定义的GoController的index方法对应的url, GC是GoController中的大写字母。它可以直接在jsp中通过spring的标签来使用,如
<a href="$ {s:mvcUrl’ GCHindex ') }"Go</a>
理解了这三个Map再来看AbstractHandlerMethodMapping系列的创建就容易多了,AbstractHandlerMethodMapping实现了InitializingBean接口,所以spring容器会自动调用其afterPropertiesSet方法, afterPropertiesSet又交给initHandlerMethods方法完成具体的初始化,代码如下:
@Override public void afterPropertiesSet() { initHandlerMethods(); } protected void initHandlerMethods() { ...打印日志 // 从容器中获得所有的beanName String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) : obtainApplicationContext().getBeanNamesForType(Object.class)); //遍历所有的bean判断是不是被@Controller或者@RequestMapping标注了 for (String beanName : beanNames) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { Class<?> beanType = null; try { beanType = obtainApplicationContext().getType(beanName); } ...(抛出异常) //这里进行的判断 if (beanType != null && isHandler(beanType)) { //讲查找到的handler保存到map中 detectHandlerMethods(beanName); } } } handlerMethodsInitialized(getHandlerMethods()); }
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//根据request获取请求路径
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
...打印日志
try {
//
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
}
这里省略了日志相关代码,可以看到实际就做了三件事:
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { // Match是内部类,保存匹配条件和Handler List<Match> matches = new ArrayList<>(); //首先根据lookupPath获取匹配条件 List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null) { // 找到的匹配条件添加到matches addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { //如果不能直接使用lookupPath得到匹配条件,则将所有匹配条件加入matches // No choice but to go through all mappings... addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); } if (!matches.isEmpty()) { // 将包含匹配条件和Handler的matches排序, // 并取第一个作为bestMatch,如果前面两个排序相同则她出异常 Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); matches.sort(comparator); ...日志 Match bestMatch = matches.get(0); if (matches.size() > 1) { if (CorsUtils.isPreFlightRequest(request)) { return PREFLIGHT_AMBIGUOUS_MATCH; } Match secondBestMatch = matches.get(1); if (comparator.compare(bestMatch, secondBestMatch) == 0) { Method m1 = bestMatch.handlerMethod.getMethod(); Method m2 = secondBestMatch.handlerMethod.getMethod(); ...抛出异常 } } request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod); // 这个方法其实就是为request设置一些属性 handleMatch(bestMatch.mapping, lookupPath, request); return bestMatch.handlerMethod; } else { return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); } }
详细过程已经做了注释,比较容易理解,整个过程中是使用Match作为载体的, Match是个内部类,封装了匹配条件和HandlerMethod两个属性。handleMatch方法是在返回前做一些处理,默认实现是将lookupPath设置到request的属性,子类RequestMappinglnfoHandlerMapping中进行了重写,将更多的参数设置到了request的属性,主要是为了以后使用时方便,跟Mapping没有关系。.
可以看到HandlerAdapter的结构非常简单,一共有4类Adapter,其中只有RequestMappingHandlerAdapter有两层,别的都是只有一层,也就是直接实现的HandlerAdapter接口。在这四类Adapter中RequestMappingHandlerAdapter的实现非常复杂,而其他三个则非常简单,因为其他三个Handler的格式都是固定的,只需要调用固定的方法就可以了,但是RequestMappingHandlerAdapter所处理的Handler可以是任意的方法,没有任何约束,这就极大地增加了难度。其实调用并不算难,大家应该都可以想到,使用反射就可以了,关键是参数值的解析,参数可能有各种各样的类型,而且有几个参数也不确定,如果让我们去做,我们会怎么做呢?大家可以先自己思考一下。前面已经讲过HandlerAdapter的接口了,里面一共有三个方法,一个用来判断是否支持传入的Handler,一个用来使用Handler处理请求,还有一个用来获取资源的LastModified值。
org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception{ ((HttpRequestHandler) handler).handleRequest(request, response); return null; } org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return ((Controller) handler).handleRequest(request, response); } org.springframework.web.servlet.handler.SimpleServletHandlerAdapter public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((Servlet) handler).service(request, response); return null; }
RequestMappingHandlerAdapter继承自AbstractHandlerMethodAdapter,后者非常简单,三个接口方法分别调用了三个模板方法supportsInternal, handleInternal和getLastModifiedInternal,在supports方法中除了supportsInternal还增加了个条件-Handler必须是HandlerMethod类型。另外实现了Order接口,可以在配置时设置顺序,代码如下:
public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
private int order = Ordered.LOWEST_PRECEDENCE;
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
...
}
接下来看RequestMappingHandlerAdapter, RequestMappingHandlerAdapter可以说是整个Spring MVC中最复杂的组件。它的supportsInternal直接返回true,也就是不增加判断Handler的条件,只需要满足父类中的HandlerMethod类型的要求就可以了; getLastModifiedInternal直接返回-1 ;最重要的就是handlelnternal方法,就是这个方法实际使用Handler处理请求。具体处理过程大致可以分为三步:
这三步里面第2步是最简单的,直接使用反射技术调用处理器执行就可以了,第3步也还算简单,最麻烦的是第1步,也就是参数的准备工作,这一步根据处理器的需要设置参数,而参数的类型、个数都是不确定的,所以难度非常大,另外这个过程中使用了大量的组件,这也是这一步的代码不容易理解的重要原因之一。
要想理解参数的绑定需要先想明白三个问题:
需要绑定的参数当然是根据方法确定了,不过这里的方法除了实际处理请求的处理器之外还有两个方法的参数需要绑定,那就是跟当前处理器相对应的注释了@ModelAttribute和注释了@InitBinder的方法。
参数来源有6个:
参数具体解析是使用HandlerMethodArgumentResolver类型的组件完成的,不同类型的参数使用不同的ArgumentResolver来解析。有的Resolver内部使用了WebDataBinder,可以通过注释了@InitBinder的方法来初始化。注释了@InitBinder的方法也需要绑定参数,而且也是不确定的,所以@InitBinder注释的方法本身也需要ArgumentResolver来解析参数,但它使用的和Handler使用的不是同一套ArgumentResolver,另外,注释了@ModelAttribute的方法也需要绑定参数,它使用的和Handler使用的是同一套ArgumentResolver.
注解的作用:
从字面意思可以看出这个的作用是给Binder做初始化的,被此注解的方法可以对WebDataBinder初始化。webDataBinder是用于表单到方法的数据绑定的!
@InitBinder只在@Controller中注解方法来为这个控制器注册一个绑定器初始化方法,方法只对本控制器有效。
代码演示:
对数据绑定进行设置
WebDataBinder中有很多方法可以对数据绑定进行具体的设置:比如我们设置name属性为非绑定属性(也可以设置绑定值setAllowedFields):
在Controller中添加一个方法:
@InitBinder
public void initBinder(WebDataBinder binder) {
//也就是说当界面传递过来的name属性不需要进行自动绑定
binder.setDisallowedFields("name");
}
注册已有的编辑器
WebDataBinder是用来绑定请求参数到指定的属性编辑器.由于前台传到controller里的值是String类型的,当往Model里Set这个值的时候,如果set的这个属性是个对象,Spring就会去找到对应的editor进行转换,然后再set进去!Spring自己提供了大量的实现类(如下图所示的在org.springframwork.beans.propertyEditors下的所有editor),诸如CustomDateEditor ,CustomBooleanEditor,CustomNumberEditor等许多,基本上够用。 在平时使用SpringMVC时,会碰到javabean中有Date类型参数,表单中传来代表日期的字符串转化为日期类型,SpringMVC默认不支持这种类型的转换。我们就需要手动设置时间格式并在webDateBinder上注册这个编辑器!
@InitBinder
public void initBinder(WebDataBinder binder) {
CustomDateEditor editor = new CustomDateEditor(
new SimpleDateFormat("yyyy-MM-dd"),true);
//这个就是注册一个编辑器
//也就是说可以把传递进来的spring类型的字符串转换为指定的类型
binder.registerCustomEditor(Date.class, editor);
}
注册自定义编辑器
使用自定义编辑器就是在第二个的基础上添加个自定义编辑器就行了,自定义的编辑器类需要继承
// 这个就是自定一的编辑器,想要进行注册就可以直接在上面的初始化方法中注册一下就行了
public class DoubleEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (text == null || text.equals("")) {
text = "0";
}
setValue(Double.parseDouble(text));
}
@Override
public String getAsText() {
return getValue().toString();
}
}
设置属性的前缀可以实现参数绑定
name:@InitBinder("user") public void init1(WebDataBinder binder) { binder.setFieldDefaultPrefix("u."); } @InitBinder("stu") public void init2(WebDataBinder binder) { binder.setFieldDefaultPrefix("s."); } @RequestMapping("/testBean") public ModelAndView testBean(User user, @ModelAttribute("stu") Student stu) { System.out.println(stu); System.out.println(user); String viewName = "success"; ModelAndView modelAndView = new ModelAndView(viewName); modelAndView.addObject("user", user); modelAndView.addObject("student", stu); return modelAndView; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。