天天看點

SpringMVC源碼解讀 --- 處理器擴充卡 -- 1 (HandlerAdapter)的使用(以及這些處理器與擴充卡等是怎樣被SpringMVC自動加載注入到容器中的)

1、HandlerAdapter的使用:

       我們在前一章講了Handler(從HanlderMapping擷取的是Handler),現在我們來梳理來HandlerAdapter是怎樣被使用以及怎樣被加載到Spring的bean容器中的(其他沒有提到的内容一般也是本文下面介紹的幾種加載方法)

    1、SimpleServletHandlerAdapter

    xml檔案配置

<?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:context="http://www.springframework.org/schema/context"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.2.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">

    <bean class="org.springframework.web.servlet.handler.SimpleServletHandlerAdapter"/>

    <bean id="beanNameUrlHandlerMapping"
          class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
    </bean>
    <bean id="myServlet" name="/myServletName" class="web.servlet.MyServlet"/>
    <alias name="myServlet" alias="/myServletAlias"/>
</bean>
           

  然後簡單實作一個Serlvet

public class MyServlet extends HttpServlet{
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Hello MyServlet");
    }
}
           

我們在浏覽器輸入位址:http://localhost:8080/myServletName  或http://localhost:8080/myServletAlias ,然後在背景就會列印"Hello MyServlet"

   2、SimpleControllerHandlerAdapter

     xml配置:

<?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:context="http://www.springframework.org/schema/context"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.2.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="jspViewResolver">
        <property value="org.springframework.web.servlet.view.JstlView" name="viewClass"/>
        <property value="/WEB-INF/" name="prefix"/>
        <property value=".jsp" name="suffix"/>
    </bean>
    <bean id="welcomeController"
          class="web.SimpleController" />
</bean>
           

實作一個Controller接口: 

public class SimpleController implements Controller {
   public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
      ModelAndView modelAndView = new ModelAndView();
      modelAndView.setViewName("springMethod");
      return modelAndView;
   }
}
           

jsp頁面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8" isELIgnored="false"%>

<h1>Hello World</h1>
           

不過其實這裡要注意的是,你應該也可以将這個Controller實作當做Servlet去用(也了解如果web容器用的是tomcat,其就不能脫離tomcat的标準,最終都會調用到tomcat那套标準)(不需要使用到視圖的話,其實你是可以将handler(處理器)與View(視圖)分開來看,視圖是可有可無的),因為這裡的入參就是httpServletRequest、httpServletResponse。如果将handler、與View分開來看的話,在上面的xml就不用注入InternalResourceViewResolver(視圖解析器)。

    例如:

<?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:context="http://www.springframework.org/schema/context"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.2.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">

    <bean id="beanNameUrlHandlerMapping"
          class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
    </bean>
    <bean id="welcomeController" name="/simpleController"
          class="web.SimpleController" />
</bean>
           
public class SimpleController implements Controller {
   public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
      PrintWriter out = httpServletResponse.getWriter();
      out.println("Hello SimpleController");
      out.close();
      return null;
   }
}
           

  然後在頁面請求: http://localhost:8080/simpleController,就會在頁面輸出"Hello SimpleController"。

  3、RequestMappingHandlerAdapter

    這個是與前面HandlerMapping的應的。其用法:

    xml檔案:

<?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:context="http://www.springframework.org/schema/context"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.2.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <context:component-scan base-package="web" />   //對應java檔案掃描的包名
    <mvc:annotation-driven/>
</bean
           
@RestController
public class SpringController{

    @RequestMapping(value = "/spring/{name}", method = RequestMethod.GET)
    public String springMethod(@PathVariable("name") String name) {
        return "springMethod23";
    }
}
           

然後在包web下面建立這個java檔案就可以了。

請求就會再頁面輸出"springMethod23",因為加的@RestController,如果是@Controller,你也可以與視圖解析器一起使用來處理視圖(就像前面使用一樣,可以将擴充卡與視圖解析器分開來看,不一定都需要視圖解析器)。

2、HandlerAdapter、HandlerMappng等容器本身的類是怎樣被注入到SpringMVC bean容器中的

    1、我們在前面章節将DispatcherServlet的init方法講過BeanDefinitionParser接口,這裡再來粗略回顧下其的用法。

public class MvcNamespaceHandler extends NamespaceHandlerSupport {

   @Override
   public void init() {
      registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
      registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
      registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
      registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
      registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
      registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
      registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
      registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
      registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());
      registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
      registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
      registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
      registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());
   }
}
           

      簡單來說,就是你在xml配置的<mvc:annotation-driven/>、<context:component-scan base-package="web" />标簽,在進行xml檔案節點解析的時候,如果掃描到例如"annotation-driven",就會運用AnnotationDrivenBeanDefinitionParser去處理這個标簽,并且去注冊一些與之相關聯的類到容器中。這裡的RequestMappingHandlerAdapter就是通過這種方式注入的:

class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {

   @Override
   @Nullable
   public BeanDefinition parse(Element element, ParserContext parserContext) {
      ..........
   RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
  .  ..............
    readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME , handlerAdapterDef); 
    ........
}
           

  2、但我們講SimpleControllerHandlerAdapter的時候,在xml中并沒有加一些特殊的标簽内容,那其是怎樣注入的呢?

   隻是因為DispatcherServlet類主動加載了一個DispatcherServlet.properties檔案:

public class DispatcherServlet extends FrameworkServlet {
     ...........
   private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
       ...........
   static {
      try {
         ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
         defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
      }
        ........
}
           

檔案中的部分内容:

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
   org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
   org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
   org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
           

然後通過這個注入裡面的内容到容器中:

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
   String key = strategyInterface.getName();
   String value = defaultStrategies.getProperty(key);
   if (value != null) {
      String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
      List<T> strategies = new ArrayList<>(classNames.length);
      for (String className : classNames) {
            ......
            Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
            Object strategy = createDefaultStrategy(context, clazz);
             .........
        }
}
protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
   return context.getAutowireCapableBeanFactory().createBean(clazz);
}
public <T> T createBean(Class<T> beanClass) throws BeansException {
   RootBeanDefinition bd = new RootBeanDefinition(beanClass);
   bd.setScope(SCOPE_PROTOTYPE);
   bd.allowCaching = ClassUtils.isCacheSafe(beanClass, getBeanClassLoader());
   return (T) createBean(beanClass.getName(), bd, null);
}
           

    3、第三種。就是像前面的SimpleServletHandlerAdapter,一些冷門的類,SpringMVC就不會主動通過DispatcherServlet.properties檔案去注入,就需要我們主動配置到xml檔案中來完成注入。