+
+根路径是在这里配的,tomcat的配置自己百度下,很简单
+
+
+
+## Java的SPI机制
+
+> 下面会用到SPI,这里先讲一下
+
+### 项目目录
+
+```java
+spi-demo
+├── api-db-impl-mysql/
+| ├── api-db-impl-mysql.iml
+| ├── pom.xml
+| ├── src/
+| | ├── main/
+| | | ├── java/
+| | | | └── com/
+| | | | └── imlql/
+| | | | └── mysql/
+| | | | └── MySQLSaveService.java
+| | | └── resources/
+| | | └── META-INF/
+| | | └── services/
+| | | └── cn.imlql.data.DataSaveService
+| | └── test/
+| | └── java/
+| └── target/
+| ├── classes/
+| | ├── com/
+| | | └── imlql/
+| | | └── mysql/
+| | | └── MySQLSaveService.class
+| | └── META-INF/
+| | └── services/
+| | └── cn.imlql.data.DataSaveService
+| └── generated-sources/
+| └── annotations/
+├── api-db-impl-redis/
+| ├── api-db-impl-redis.iml
+| ├── pom.xml
+| ├── src/
+| | ├── main/
+| | | ├── java/
+| | | | └── com/
+| | | | └── imlql/
+| | | | └── redis/
+| | | | └── RedisSaveService.java
+| | | └── resources/
+| | | └── META-INF/
+| | | └── services/
+| | | └── cn.imlql.data.DataSaveService
+| | └── test/
+| | └── java/
+| └── target/
+| ├── classes/
+| | ├── com/
+| | | └── imlql/
+| | | └── redis/
+| | | └── RedisSaveService.class
+| | └── META-INF/
+| | └── services/
+| | └── cn.imlql.data.DataSaveService
+| └── generated-sources/
+| └── annotations/
+├── api-db-interface/
+| ├── api-db-interface.iml
+| ├── pom.xml
+| ├── src/
+| | ├── main/
+| | | ├── java/
+| | | | └── com/
+| | | | └── imlql/
+| | | | └── data/
+| | | | └── DataSaveService.java
+| | | └── resources/
+| | └── test/
+| | └── java/
+| └── target/
+| ├── classes/
+| | └── com/
+| | └── imlql/
+| | └── data/
+| | └── DataSaveService.class
+| └── generated-sources/
+| └── annotations/
+├── app/
+| ├── app.iml
+| ├── pom.xml
+| ├── src/
+| | ├── main/
+| | | ├── java/
+| | | | └── com/
+| | | | └── imlql/
+| | | | └── redis/
+| | | | └── MainTest.java
+| | | └── resources/
+| | └── test/
+| | └── java/
+| └── target/
+| ├── classes/
+| | └── com/
+| | └── imlql/
+| | └── redis/
+| | └── MainTest.class
+| └── generated-sources/
+| └── annotations/
+├── pom.xml
+└── spi-demo.iml
+
+```
+
+### MainTest
+
+```java
+import cn.imlql.data.DataSaveService;
+
+import java.util.ServiceLoader;
+
+
+/**
+ * 1、 ServiceLoader:load()指定一个接口,
+ * 他就会加载当前系统里面所有的这个接口的【指定实现】
+ * 2、SPI(Service Provider Interface)
+ * 接口工程---提供接口
+ * ---- 实现工程1 : 实现接口 【META-INF/services 创建文件 接口名作为文件名 实现类全路径作为文件内容】
+ * ---- 实现工程2 : 实现接口
+ *
+ *
+ * 客户端----引用 工程1、或者 工程2
+ *
+ *
+ *
+ */
+public class MainTest {
+
+ public static void main(String[] args) {
+
+ //1、加载 可用的接口实现
+ ServiceLoader
+
+
+
+```java
+@HandlesTypes(WebApplicationInitializer.class)
+public class SpringServletContainerInitializer implements ServletContainerInitializer {...}
+```
+
+### ServletContainerInitializer#onStartup()
+
+```java
+public interface ServletContainerInitializer {
+
+ public void onStartup(Set
+
+
+
+
+
+
+
+
+
+相当于tomcat一启动会加载SpringServletContainerInitializer
+
+### SpringServletContainerInitializer#onStartup()
+
+```java
+
+/**
+ * Servlet 3.0 {@link ServletContainerInitializer} designed to support code-based
+ * configuration of the servlet container using Spring's {@link WebApplicationInitializer}
+ * SPI as opposed to (or possibly in combination with) the traditional
+ * {@code web.xml}-based approach.
+ *
+ * + * + * @author Chris Beams + * @author Juergen Hoeller + * @author Rossen Stoyanchev + * @since 3.1 + * @see #onStartup(Set, ServletContext) + * @see WebApplicationInitializer + */ +@HandlesTypes(WebApplicationInitializer.class) +public class SpringServletContainerInitializer implements ServletContainerInitializer { + + /** + * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer} + * implementations present on the application classpath. + *
Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)}, + * Servlet 3.0+ containers will automatically scan the classpath for implementations + * of Spring's {@code WebApplicationInitializer} interface and provide the set of all + * such types to the {@code webAppInitializerClasses} parameter of this method. + *
If no {@code WebApplicationInitializer} implementations are found on the classpath, + * this method is effectively a no-op. An INFO-level log message will be issued notifying + * the user that the {@code ServletContainerInitializer} has indeed been invoked but that + * no {@code WebApplicationInitializer} implementations were found. + *
Assuming that one or more {@code WebApplicationInitializer} types are detected,
+ * they will be instantiated (and sorted if the @{@link
+ * org.springframework.core.annotation.Order @Order} annotation is present or
+ * the {@link org.springframework.core.Ordered Ordered} interface has been
+ * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}
+ * method will be invoked on each instance, delegating the {@code ServletContext} such
+ * that each instance may register and configure servlets such as Spring's
+ * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},
+ * or any other Servlet API componentry such as filters.
+ * @param webAppInitializerClasses all implementations of
+ * {@link WebApplicationInitializer} found on the application classpath
+ * @param servletContext the servlet context to be initialized
+ * @see WebApplicationInitializer#onStartup(ServletContext)
+ * @see AnnotationAwareOrderComparator
+ */
+ public void onStartup(@Nullable Set All ServletContextListeners are notified of context
+ * initialization before any filters or servlets in the web
+ * application are initialized.
+ *
+ * @param sce the ServletContextEvent containing the ServletContext
+ * that is being initialized
+ *
+ * @implSpec
+ * The default implementation takes no action.
+ */
+ default public void contextInitialized(ServletContextEvent sce) {}
+
+ /**
+ * Receives notification that the ServletContext is about to be
+ * shut down.
+ *
+ * ......
+ */
+ default public void contextDestroyed(ServletContextEvent sce) {}
+}
+```
+
+## SpringMVC父子容器的启动过程
+
+### 保存父子容器相关信息
+
+#### SpringServletContainerInitializer#onStartup()
+
+
+
+接着在最底下的for循环执行所有实现了WebApplicationInitializer的类的onStartup(),然后就走到了我们的AppStarter
+
+
+
+到这一步,ioc容器都没有创建,我们给refresh()打个断点,看什么时候启动的ioc
+
+
+
+
+
+放行
+
+我看的时候debug断点没有看到从AppStarter的哪一步跳到refresh()的。然后我一步一步走的时候发现不是在这个方法里调用的,注意看下面的图,DispatcherServlet已经new完了,但是debug依然没有跳到refresh(),说明不是在new DispatcherServlet()的时候创建的容器
+
+
+
+不过我凭经验猜测Springmvc里最重要的是DispatcherServlet,会不会是DispatcherServlet的那一步启动了IOC,我们开始进行下面的尝试
+
+
+
+### Servlet相关
+
+1. tomcat会遵循sun公司的规范给每一个Servlet创建对象
+2. 所以DispatcherServlet肯定也会创建对象
+
+3. Servlet的规范
+ 1. Servlet创建对象
+ 2. Servlet调用Init初始化
+ 3. 每次请求调用service处理
+ 4. tomcat停止的时候调用destroy进行销毁
+
+
+
+4. Serlvet是被谁调用开始初始化的属于tomcat的源码,我们这里不研究,我们这里只需要知道,每一个Servlet都会被初始化就可以了。
+
+### DispatcherServlet
+
+spring-web中有一个叫DispatcherServlet的类,很明显他是一个Servlet,所以tomcat启动的时候就会加载它,加载它的话当然是从父类一层一层加载的
+
+
+
+
+
+1. 想要启动IOC容器,只可能是创建DispatcherServlet对象或者调用init()的时候来搞。上面我们也看到了,创建DispatcherServlet对象的时候debug调用栈并没有显示跳到了refresh方法,所以显然不是创建对象的时候
+2. 那就只有可能是调用init()的时候开始启动的IOC容器
+
+
+
+Servlet规范的init我看了下都是空的,从Spring的HttpServletBean才开始有东西,HttpServletBean的父类和接口对于init()都是空实现,下面我们就从HttpServletBean开始分析。
+
+
+
+### HttpServletBean#init()
+
+```java
+ public final void init() throws ServletException {
+
+ // Set bean properties from init parameters.
+ PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
+ if (!pvs.isEmpty()) {
+ try {
+ BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
+ ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
+ bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
+ initBeanWrapper(bw);
+ bw.setPropertyValues(pvs, true);
+ }
+ catch (BeansException ex) {
+ if (logger.isErrorEnabled()) {
+ logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
+ }
+ throw ex;
+ }
+ }
+
+ //模板方法模式。给子类留的喜欢干的事 Let subclasses do whatever initialization they like.
+ initServletBean();
+ }
+```
+
+
+
+### FrameworkServlet#initServletBean()
+
+```java
+ /** 追踪看web应用启动做了什么。
+ * Overridden method of {@link HttpServletBean}, invoked after any bean properties
+ * have been set. Creates this servlet's WebApplicationContext.
+ */
+ @Override
+ protected final void initServletBean() throws ServletException {
+ getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
+ if (logger.isInfoEnabled()) {
+ logger.info("Initializing Servlet '" + getServletName() + "'");
+ }
+ long startTime = System.currentTimeMillis();
+
+ try {
+ this.webApplicationContext = initWebApplicationContext(); //初始化WebIOC容器,那我们想一下大概率是在这里启动的IOC容器
+ initFrameworkServlet(); //这又是留给子类的
+ }
+ catch (ServletException | RuntimeException ex) {
+ logger.error("Context initialization failed", ex);
+ throw ex;
+ }
+
+ if (logger.isDebugEnabled()) {
+ String value = this.enableLoggingRequestDetails ?
+ "shown which may lead to unsafe logging of potentially sensitive data" :
+ "masked to prevent unsafe logging of potentially sensitive data";
+ logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
+ "': request parameters and headers will be " + value);
+ }
+
+ if (logger.isInfoEnabled()) {
+ logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
+ }
+ }
+```
+
+
+
+DispatcherServlet没有重写initFrameworkServlet()
+
+最后得到结论
+
+```java
+ this.webApplicationContext = initWebApplicationContext(); //初始化WebIOC容器,那我们想一下大概率是在这里启动的IOC容器
+```
+
+下面开始具体分析
+
+
+
+## 小补充
+
+### XML版如何配置父子容器关系?
+
+#### web.xml
+
+```xml
+
+
+### AbstractAnnotationConfigDispatcherServletInitializer能更快的整合Spring和SpringMVC
+
+
+
+
AbstractAnnotationConfigDispatcherServletInitializer能更快的整合Spring和SpringMVC
+
+> 后面的讲解都用这个测试类
+
+
+
+#### 测试类
+
+##### QuickAppStarter
+
+```java
+/**
+ * 最快速的整合注解版SpringMVC和Spring的
+ */
+public class QuickAppStarter extends AbstractAnnotationConfigDispatcherServletInitializer {
+ @Override //根容器的配置(Spring的配置文件===Spring的配置类)
+ protected Class>[] getRootConfigClasses() {
+ return new Class>[]{SpringConfig.class};
+ }
+
+ @Override //web容器的配置(SpringMVC的配置文件===SpringMVC的配置类)
+ protected Class>[] getServletConfigClasses() {
+ return new Class>[]{SpringMVCConfig.class};
+ }
+
+ @Override //Servlet的映射,DispatcherServlet的映射路径
+ protected String[] getServletMappings() {
+ return new String[]{"/"};
+ }
+
+ @Override
+ protected void customizeRegistration(ServletRegistration.Dynamic registration) {
+// super.customizeRegistration(registration);
+
+// registration.addMapping("");//
+ }
+}
+```
+
+##### SpringMVCConfig
+
+```java
+/**
+ * SpringMVC只扫描controller组件,可以不指定父容器类,让MVC扫所有。@Component+@RequestMapping就生效了
+ */
+@ComponentScan(value = "cn.imlql.web", includeFilters = {
+ @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)
+}, useDefaultFilters = false)
+public class SpringMVCConfig {
+ //SpringMVC的子容器,能扫描的Spring容器中的组件
+
+}
+```
+
+##### SpringConfig
+
+```java
+/**
+ * Spring不扫描controller组件
+ */
+@ComponentScan(value = "cn.imlql.web",excludeFilters = {
+ @ComponentScan.Filter(type= FilterType.ANNOTATION,value = Controller.class)
+})
+public class SpringConfig {
+ //Spring的父容器
+
+}
+```
+
+父子容器隔离,因为有这句话webloc.setParent(springloc),springmvc这个子容器可以扫描到父容器Spring里面的组件,反之Spring父容器扫描不到springmvc这个子容器的组件。
+
+
+
+##### HelloController
+
+```java
+//@Controller; 如果有父子容器 @Component,默认是在父容器中,还找不到
+//@Component+@RequestMapping
+@RestController
+public class HelloController {
+
+ public HelloController(){
+ System.out.println("HelloController.....");
+ }
+
+ @Autowired
+ HelloService helloService;
+
+ @GetMapping("/hello") // 所有的xxxMapping都是RequestMapping
+ public String sayHello(){
+
+ String mvc = helloService.say("MVC");
+ return mvc;
+ }
+
+}
+```
+
+##### HelloService
+
+```java
+@Service
+public class HelloService {
+
+ public HelloService(){
+ System.out.println("HelloService.....");
+ }
+
+ public String say(String name){
+ return "Hello,"+name;
+ }
+}
+```
+
+
+
+
+
+```java
+public interface ServletContextListener extends EventListener {
+
+ /**
+ * Receives notification that the web application initialization
+ * process is starting.【翻译:在当前web应用启动以后(Tomcat把web应用加载了以后),调用contextInitialized方法】
+ *
+ *
+
+
+
+```java
+ public void onStartup(@Nullable Set
+
+因为咱们的QuickAppStarter没有onStarup()所以就调用了父类AbstractDispatcherServletInitializer的,没想到AbstractDispatcherServletInitializer也是继续调用父类的
+
+
+
+```java
+ public void onStartup(ServletContext servletContext) throws ServletException {
+ super.onStartup(servletContext);
+ registerDispatcherServlet(servletContext);
+ }
+```
+
+
+
+#### AbstractContextLoaderInitializer#onStartup()
+
+
+
+
+
+```java
+public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
+
+ /** Logger available to subclasses. */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+
+ @Override //注册ContextLoaderListener;contextInitialized
+ public void onStartup(ServletContext servletContext) throws ServletException {
+ registerContextLoaderListener(servletContext);
+ }
+
+ protected void registerContextLoaderListener(ServletContext servletContext) {
+ WebApplicationContext rootAppContext = createRootApplicationContext(); //创建一个根容器
+ if (rootAppContext != null) {
+ ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
+ listener.setContextInitializers(getRootApplicationContextInitializers());
+ servletContext.addListener(listener);
+ }
+ else {
+ logger.debug("No ContextLoaderListener registered, as " +
+ "createRootApplicationContext() did not return an application context");
+ }
+ }
+
+}
+```
+
+
+
+
+
+
+
+#### AbstractAnnotationConfigDispatcherServletInitializer#createRootApplicationContext()创建Spring-IOC父容器
+
+```java
+ @Nullable //重写了爷爷类的创建根容器方法
+ protected WebApplicationContext createRootApplicationContext() {
+ Class>[] configClasses = getRootConfigClasses(); //获取根配置
+ if (!ObjectUtils.isEmpty(configClasses)) {
+ AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
+ context.register(configClasses); //创建了一个IOC容器并把配置类注册进来
+ return context;
+ }
+ else {
+ return null;
+ }
+ }
+```
+
+
+
+getRootConfigClasses()正好是咱们QuickAppStarter这个子类重写的,debug F7进入
+
+#### QuickAppStarter#getRootConfigClasses()拿自定义的配置类
+
+果不其然,调用了QuickAppStarter#getRootConfigClasses()
+
+
+
+
+
+继续往下走创建Web容器,这是Spring父容器,因为你看它getRootConfigClasses()获取的是父容器配置
+
+
+
+
+
+然后返回
+
+
+
+
+
+图片上面写错了。应该改为:**保存了上面创建的Spring-IOC父容器**。Web子容器的创建在下面
+
+#### ContextLoaderListener创建一个监听器准备后续启动容器进行回调
+
+```java
+public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
+ //在当前web应用启动以后(Tomcat把web应用加载了以后),调用contextInitialized方法
+ public ContextLoaderListener(WebApplicationContext context) {
+ super(context);
+ }
+
+ /**
+ * Initialize the root web application context.
+ */
+ @Override
+ public void contextInitialized(ServletContextEvent event) { //根容器初始化
+ initWebApplicationContext(event.getServletContext()); //初始化webioc容器
+ }
+}
+```
+
+上面是利用Servlet标准
+
+
+
+#### 返回到AbstractDispatcherServletInitializer#onStartup()
+
+接着就继续返回
+
+
+
+
+
+下面才是创建Web子容器(也叫做Servlet容器)
+
+```java
+ protected void registerDispatcherServlet(ServletContext servletContext) {
+ String servletName = getServletName();
+ Assert.hasLength(servletName, "getServletName() must not return null or empty");
+
+ WebApplicationContext servletAppContext = createServletApplicationContext(); //创建Servlet容器
+ Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
+
+ FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
+ Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
+ dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
+
+ ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
+ if (registration == null) {
+ throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
+ "Check if there is another servlet registered under the same name.");
+ }
+
+ registration.setLoadOnStartup(1);
+ registration.addMapping(getServletMappings()); //根据我们指定的DispatcherServlet的路径进行注册
+ registration.setAsyncSupported(isAsyncSupported());
+
+ Filter[] filters = getServletFilters();
+ if (!ObjectUtils.isEmpty(filters)) {
+ for (Filter filter : filters) {
+ registerServletFilter(servletContext, filter);
+ }
+ }
+ // 这个是留给我们自定义的方法,模板模式
+ customizeRegistration(registration);
+ }
+```
+
+
+
+
+
+#### AbstractAnnotationConfigDispatcherServletInitializer#createServletApplicationContext()创建Web子容器(Servelt容器)
+
+```java
+ protected WebApplicationContext createServletApplicationContext() {
+ //
+ AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
+ Class>[] configClasses = getServletConfigClasses(); //获取web应用的配置
+ if (!ObjectUtils.isEmpty(configClasses)) {
+ context.register(configClasses);
+ }
+ return context;
+ }
+```
+
+
+
+这里又new了一个容器,和上面那个容器一样都没有初始化。这里也是调用咱们QuickAppStarter重写的方法,因为这里调用的是getServletConfigClasses(),所以很明显这里的容器是Web子容器
+
+
+
+然后就一路往回返,走到这里
+
+
+
+继续F7进入
+
+
+
+这里就是保存咱们上面刚创建的Web子容器,然后再返回
+
+#### 返回到SpringServletContainerInitializer#onStartup()
+
+
+
+1. 这里应用就加载完了,接下来干嘛呢?
+2. 你往前看看,咱们的Spring容器和Web子容器都是只是创建完了,都还没有初始化,甚至都没有webloc.setParent(springloc)这样产生父子容器的关系
+3. 回想下前面讲的,是不是有一个监听器,那你说把容器保存到监听器里,它能是干嘛的?猜想一下不就是应用加载完之后,发送一个什么事件,然后根据这个事件触发监听器初始化两大容器等等。
+
+咱们就把那个监听器的初始化方法打个断点,然后F8放行试试呗
+
+
+
+### ContextLoaderListener回调-开始refresh刷新Spring的根容器(父容器)
+
+#### ContextLoaderListener#contextInitialized()
+
+
+
+
+
+#### 简单看一下Tomcat里的调用源码
+
+然后真的走到了这里。tomcat里的代码位置是乱的,乱的意思就是比如说上面写的是4766行的调用,但实际上那里是个`}`大括号。也不知道是什么问题,所以我们就大致看下tomcat的代码,不细究。
+
+
+
+这里的调用还是对的
+
+
+
+
+
+应该就是类似这样的调用
+
+
+
+
+
+走的应该是第一个if
+
+
+
+
+
+大致就是tomcat里的一个监听器基于事件回调的原理,咱们就不深究了。
+
+#### ContextLoader#initWebApplicationContext()
+
+
+
+
+
+F7进入,这里因为我重新启动了一次,所以你看到根容器是@3661
+
+
+
+终于要调用refresh了
+
+
+
+这里直接放行到容器refresh完毕看下父容器
+
+
+
+父容器只扫描了,springconfig和helloService,我们继续放行看下Web子容器.
+
+
+
+### Servlet初始化-进而开始refresh刷新Spring的web子容器
+
+#### HttpServletBean#init()
+
+跳到了这里,为什么会跳到这里呢?记不记得之前我们用DispatcherServlet保存了Web子容器,这里就要调用DispatcherServlet的相关初始化方法
+
+
+
+
+
+#### FrameworkServlet#initWebApplicationContext()
+
+一路放行
+
+
+
+
+
+上面父子容器关系形成了,并且父容器已经refresh完毕
+
+```java
+ protected WebApplicationContext initWebApplicationContext() {
+ WebApplicationContext rootContext =
+ WebApplicationContextUtils.getWebApplicationContext(getServletContext()); //父容器
+ WebApplicationContext wac = null; //先会获取之前的 WebApplicationContext(构建父子容器)
+
+ if (this.webApplicationContext != null) {
+ // A context instance was injected at construction time -> use it
+ wac = this.webApplicationContext; //当前的web-ioc容器
+ if (wac instanceof ConfigurableWebApplicationContext) {
+ ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
+ if (!cwac.isActive()) {
+ // The context has not yet been refreshed -> provide services such as
+ // setting the parent context, setting the application context id, etc
+ if (cwac.getParent() == null) {
+ // The context instance was injected without an explicit parent -> set
+ // the root application context (if any; may be null) as the parent
+ cwac.setParent(rootContext); //父子容器的体现,
+ }
+ configureAndRefreshWebApplicationContext(cwac); //配置并且刷新容器
+ }
+ }
+ }
+ if (wac == null) {
+ // No context instance was injected at construction time -> see if one
+ // has been registered in the servlet context. If one exists, it is assumed
+ // that the parent context (if any) has already been set and that the
+ // user has performed any initialization such as setting the context id
+ wac = findWebApplicationContext();
+ }
+ if (wac == null) {
+ // No context instance is defined for this servlet -> create a local one
+ wac = createWebApplicationContext(rootContext);
+ }
+
+ if (!this.refreshEventReceived) {
+ // Either the context is not a ConfigurableApplicationContext with refresh
+ // support or the context injected at construction time had already been
+ // refreshed -> trigger initial onRefresh manually here.
+ synchronized (this.onRefreshMonitor) {
+ onRefresh(wac);
+ }
+ }
+
+ if (this.publishContext) {
+ // Publish the context as a servlet context attribute.
+ String attrName = getServletContextAttributeName();
+ getServletContext().setAttribute(attrName, wac);
+ }
+
+ return wac;
+ }
+```
+
+
+
+#### FrameworkServlet#createWebApplicationContext()
+
+
+
+再次来到Web子容器的刷新
+
+
+
+1. 然后我们看到子容器只有它自己的东西
+2. 虽然子容器只有controller,但是因为它保存了父容器。所以它是可以拿到HelloService的,也就是我们可以在HelloController里装配HelloService
+
+```java
+public class HelloController {
+
+ @Autowired
+ HelloService helloService;
+
+}
+```
+
+3. 但是如果想在父容器里装配HelloController就会报错,父容器没有保存子容器。
+
+```java
+public class HelloService {
+
+ @Autowired
+ HelloController helloController;
+
+}
+```
+
+4. 这样就很好的实现容器隔离,避免Service乱引用Controller
+5. 实现父子容器隔离的前提就是前面写的SpringConfig不扫描controller,交给Web子容器
+
+```java
+/**
+ * Spring不扫描controller组件
+ */
+@ComponentScan(value = "cn.imlql.web",excludeFilters = {
+ @ComponentScan.Filter(type= FilterType.ANNOTATION,value = Controller.class)
+})
+public class SpringConfig {
+ //Spring的父容器
+
+}
+```
+
+
+
+```java
+/**
+ * SpringMVC只扫描controller组件,可以不指定父容器类,让MVC扫所有。@Component+@RequestMapping就生效了
+ */
+@ComponentScan(value = "cn.imlql.web", includeFilters = {
+ @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)
+}, useDefaultFilters = false)
+public class SpringMVCConfig {
+ //SpringMVC的子容器,能扫描的Spring容器中的组件
+
+}
+```
+
+注意找组件的过程是先找自己的容器,自己没有再到父容器里找
+
+
+
+> 1. 总的来说Spring父容器和SpringMVC子容器启动过程是来自于tomcat的两个回调
+> 2. Serlvet应用启动,监听器回调刷新Spring父容器
+> 3. Servlet初始化,回调刷新SpringMVC子容器
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/spring-sourcecode-v1/09.第9章-SpringMVC请求处理源码和HandlerMapping原理.md b/docs/spring-sourcecode-v1/09.第9章-SpringMVC请求处理源码和HandlerMapping原理.md
new file mode 100644
index 0000000..d9cd564
--- /dev/null
+++ b/docs/spring-sourcecode-v1/09.第9章-SpringMVC请求处理源码和HandlerMapping原理.md
@@ -0,0 +1,1245 @@
+---
+title: Spring源码系列-第9章-SpringMVC请求处理源码和HandlerMapping原理
+tags:
+ - Spring源码
+categories:
+ - Spring
+ - 源码V1
+keywords: Spring,框架,spring源码
+description: SpringMVC请求处理源码和HandlerMapping原理
+cover: 'https://npm.elemecdn.com/lql_static@latest/logo/spring.png'
+abbrlink: 6f2cef28
+date: 2022-06-21 12:01:02
+---
+
+# 第9章-SpringMVC请求处理源码和HandlerMapping原理
+
+## 请求的处理链路
+
+
+
+1. tomcat里面可以部署多个项目应用。/abc_test和mvc_test这种就是项目路径,用于区分多个项目
+2. 在以前的Servlet开发中,每一个路径都需要有一个Servlet来处理。比如上图所画
+3. 有了SpringMVC,整个Tomcat下面就不会有很多Servlet了,只会有一个DispatcherServlet来统一处理
+
+
+
+### Servlet继承树
+
+
+
+
+
+1. Servlet里是Service()方法处理请求
+2. GenericServlet也是Service()方法处理请求
+3. HttpServlet开始根据请求类型将处理方法分为doGet,doPost,doPut,doDelete等等
+4. HttpServletBean没有处理方法
+5. FrameworkServlet也是有处理方法分别为doGet,doPost,doPut,doDelete等等,但是这些方法最终调用的都是`processRequest(HttpServletRequest request, HttpServletResponse response)`
+6. DispatcherServlet处理方法是doService()
+
+
+
+### Debug调用栈
+
+
+
+### DispatcherServlet#doService()
+
+```java
+ @Override
+ protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
+ logRequest(request);
+
+ // Keep a snapshot of the request attributes in case of an include,
+ // to be able to restore the original attributes after the include.
+ Map
+
+
+
+
+
+### DispatcherServlet#checkMultipart()
+
+```java
+protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
+ //使用文件上传解析器来判断是否文件上传请求
+ if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
+ // ......
+ }
+ // If not returned before: return original request.
+ return request;
+}
+```
+
+
+
+咱们这里目前连解析器都没有,所以就直接返回了
+
+### StandardServletMultipartResolver#isMultipart()
+
+```java
+public boolean isMultipart(HttpServletRequest request) {
+ //所有文件上传请求头Content-Type都会有这个
+ return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
+}
+```
+
+
+
+### DispatcherServlet九大组件
+
+```java
+ //DispatcherServlet中的九大组件、全是接口,我们完全可以自定义实现。Spring默认也都准备好了这些组件的实现
+ /** MultipartResolver used by this servlet. */
+ @Nullable //文件上传解析器
+ private MultipartResolver multipartResolver;
+
+ /** LocaleResolver used by this servlet. */
+ @Nullable //国际化解析器 Locale(区域信息)
+ private LocaleResolver localeResolver;
+
+ /** ThemeResolver used by this servlet. */
+ @Nullable //主题解析器
+ private ThemeResolver themeResolver;
+
+ /** List of HandlerMappings used by this servlet. */
+ @Nullable //Handler(处理器、能处理请求的人(Controller))的映射:【保存的就是所有请求都由谁来处理的映射关系】
+ private List
+
+
+
+
+
+## 九大组件利用Spring事件机制进行初始化
+
+上面的图我们看到九大组件有八个已经有值了,我们现在看下他们是何时有值的。怎么找就很简单,你就找这些属性的setXXX方法,最后我找到了这里
+
+### DispatcherServlet#initStrategies()
+
+```java
+ protected void initStrategies(ApplicationContext context) { //初始化所有策略,九大组件在这里进行了初始化
+ initMultipartResolver(context); //容器中有就用,没有就是null
+ initLocaleResolver(context); //从容器中获取,没有用默认
+ initThemeResolver(context); //从容器中获取,没有用默认
+ initHandlerMappings(context); //从容器中获取,没有用默认
+ initHandlerAdapters(context); //从容器中获取,没有用默认
+ initHandlerExceptionResolvers(context); //从容器中获取,没有用默认
+ initRequestToViewNameTranslator(context); //Spring没有浓重说他,//从容器中获取,没有用默认
+ initViewResolvers(context); //从容器中获取,没有用默认
+ initFlashMapManager(context); //从容器中获取,没有用默认
+ }
+```
+
+代码几乎都是一样的逻辑,咱们就只看一个举个例子
+
+
+
+### DispatcherServlet#initLocaleResolver()
+
+```java
+ private void initLocaleResolver(ApplicationContext context) {
+ try { //容器中先来获取
+ this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
+ if (logger.isTraceEnabled()) {
+ logger.trace("Detected " + this.localeResolver);
+ }
+ else if (logger.isDebugEnabled()) {
+ logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
+ }
+ }
+ catch (NoSuchBeanDefinitionException ex) {
+ // We need to use the default. 容器中没有,读取默认配置文件进行加载
+ this.localeResolver = getDefaultStrategy(context, LocaleResolver.class); //获取默认策略
+ if (logger.isTraceEnabled()) {
+ logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
+ "': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
+ }
+ }
+ }
+```
+
+只要不满意九大组件的任何一个,自己可以实现指定的接口,这样就会放在容器中,SpringMVC
+
+
+
+### Debug调用栈
+
+
+
+
+
+1. Tomcat启动
+2. 触发DispatcherServlet的初始化
+3. DispatcherServlet初始化全部结束,容器会发送Spring的相关事件.
+4. 感知到容器准备好了的事件--初始化九大组件(底层是SourceFilteringListener, 把事件回调到DispatcherServlet的onRefresh方法)
+
+### DispatcherServlet#onRefresh()接收到事件开始准备初始化九大组件
+
+```java
+ protected void onRefresh(ApplicationContext context) {
+ initStrategies(context); //初始化九大组件
+ }
+```
+
+
+
+### DispatcherServlet#getDefaultStrategy() 获取默认初始化策略
+
+```java
+ protected
+
+
+
+#### DispatcherServlet.properties
+
+下面这些就是九大组件默认组件
+
+```properties
+# Default implementation classes for DispatcherServlet's strategy interfaces.
+# Used as fallback when no matching beans are found in the DispatcherServlet context.
+# Not meant to be customized by application developers.
+
+org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
+
+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.method.annotation.RequestMappingHandlerMapping,\
+ org.springframework.web.servlet.function.support.RouterFunctionMapping
+
+org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
+ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
+ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
+ org.springframework.web.servlet.function.support.HandlerFunctionAdapter
+
+
+org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
+ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
+ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
+
+org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
+
+org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
+
+org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
+```
+
+
+
+## HandlerMapping处理URL和Controller的映射关系
+
+
+
+### DispatcherServlet#doDispatch()请求派发
+
+```java
+ //SpringMVC处理请求的核心流程
+ protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
+ HttpServletRequest processedRequest = request;
+ HandlerExecutionChain mappedHandler = null; //handler(目标方法)的执行链
+ boolean multipartRequestParsed = false; //文件上传标志
+ //对异步请求的支持(Servlet3.0以后才有的,Webflux)
+ WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
+
+ try {
+ ModelAndView mv = null;
+ Exception dispatchException = null;
+
+ try {
+ processedRequest = checkMultipart(request); //检查当前是否文件上传请求
+ multipartRequestParsed = (processedRequest != request);
+
+ //构造出了【目标方法+拦截器整个链路】决定使用哪个Handler处理当前请求 Determine handler for the current request.
+ mappedHandler = getHandler(processedRequest);
+ if (mappedHandler == null) { //如果找不到人处理,就send 404
+ noHandlerFound(processedRequest, response);
+ return;
+ }
+
+ //适配器怎么找的、 Determine handler adapter for the current request.
+ HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
+
+ // Process last-modified header, if supported by the handler.
+ String method = request.getMethod();
+ boolean isGet = "GET".equals(method);
+ if (isGet || "HEAD".equals(method)) {
+ long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
+ if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
+ return;
+ }
+ }
+
+ if (!mappedHandler.applyPreHandle(processedRequest, response)) {
+ return;
+ }
+
+ // Actually invoke the handler.
+ mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
+
+ if (asyncManager.isConcurrentHandlingStarted()) {
+ return;
+ }
+
+ applyDefaultViewName(processedRequest, mv);
+ mappedHandler.applyPostHandle(processedRequest, response, mv);
+ }
+ processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
+ }
+
+ // ......
+ }
+```
+
+
+
+
+
+### DispatcherServlet#getHandler()根据请求拿Controller
+
+
+
+
+
+这里会有三个默认的HandlerMapping,就是在DispatcherServlet.properties写的那三个,在之前说九大组件初始化的时候如果我们自己没有写,就获取默认的
+
+
+
+1. BeanNameUrlHandlerMapping:bean的名字作为URL路径,进行映射
+2. RequestMappingHandlerMapping:@RequestMapping注解作为URL地址进行映射
+ 1. 默认用它,而且它里面保存了所有请求映射信息
+3. RouterFunctionMapping: 支持函数式处理以及WebFlux相关功能,未来可能这个用的多
+
+
+
+
+
+BeanNameUrlHandlerMapping里找不到映射关系,就直接下一个循环了。咱们主要看RequestMappingHandlerMapping怎么处理的
+
+## RequestMappingHandlerMapping处理流程
+
+
+
+
+
+RequestMappingHandlerMapping的父类有一个MappingRegistry属性保存了请求路径 ==> 请求Controller+方法的映射
+
+F7进入`mapping.getHandler(request)`
+
+
+
+### AbstractHandlerMapping#getHandler()开始根据请求URL查哪个Controler能处理
+
+```java
+ public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
+ Object handler = getHandlerInternal(request); //HandlerMapping的registry中找映射,返回HandlerMethod,真正执行当前请求的方法
+ 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);
+ }
+
+ // Ensure presence of cached lookupPath for interceptors and others
+ if (!ServletRequestPathUtils.hasCachedPath(request)) {
+ initLookupPath(request);
+ }
+ //找到前面的目标方法以后,还要构造一个处理器链;
+ HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("Mapped to " + handler);
+ }
+ else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
+ logger.debug("Mapped to " + executionChain.getHandler());
+ }
+
+ if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
+ CorsConfiguration config = getCorsConfiguration(handler, request);
+ if (getCorsConfigurationSource() != null) {
+ CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
+ config = (globalConfig != null ? globalConfig.combine(config) : config);
+ }
+ if (config != null) {
+ config.validateAllowCredentials();
+ }
+ executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
+ }
+
+ return executionChain;
+ }
+```
+
+
+
+F7进入`getHandlerInternal(request)`
+
+
+
+### RequestMappingInfoHandlerMapping#getHandlerInternal()
+
+```java
+ protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
+ request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
+ try {
+ return super.getHandlerInternal(request);
+ }
+ finally {
+ ProducesRequestCondition.clearMediaTypesAttribute(request);
+ }
+ }
+```
+
+继续F7进`super.getHandlerInternal(request)`
+
+### AbstractHandlerMethodMapping#getHandlerInternal()
+
+```java
+ protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
+ String lookupPath = initLookupPath(request);
+ this.mappingRegistry.acquireReadLock();
+ try {
+ HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); //寻找当前请求谁能处理
+ return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
+ }
+ finally {
+ this.mappingRegistry.releaseReadLock();
+ }
+ }
+```
+
+
+
+
+
+F7进入`lookupHandlerMethod(lookupPath, request)`
+
+
+
+### AbstractHandlerMethodMapping#lookupHandlerMethod()真正根据URL查Controller
+
+
+
+
+
+可以看到还是从mappingRegistry里获取。我们后面再看mappingRegistry是什么时候被放入东西的,这里我们接着返回
+
+
+
+### 返回到AbstractHandlerMethodMapping#getHandlerInternal()
+
+
+
+这个时候是已经找到了由哪个处理器处理,接着返回
+
+### 返回到AbstractHandlerMapping#getHandler()
+
+返回到这一步,准备执行`getHandlerExecutionChain(handler, request)`
+
+
+
+
+
+### AbstractHandlerMapping#getHandlerExecutionChain()开始构造处理器链
+
+```java
+ protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
+ HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
+ (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
+ //把系统中所有的拦截器拿过来
+ for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
+ if (interceptor instanceof MappedInterceptor) {
+ MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
+ if (mappedInterceptor.matches(request)) {
+ chain.addInterceptor(mappedInterceptor.getInterceptor());
+ }
+ }
+ else {
+ chain.addInterceptor(interceptor); //所有拦截器加进去
+ }
+ }
+ return chain;
+ }
+```
+
+
+
+- 咱们没写拦截器,就没有。
+- 继续往回返
+
+### 返回到DispatcherServlet#getHandler()
+
+
+
+这里就是责任链模式,有能处理的handler就直接返回
+
+### 返回到DispatcherServlet#doDispatch()
+
+
+
+自此RequestMappingHandlerMapping处理结束
+
+## RequestMappingHandlerMapping如何保存映射关系
+
+上面我们看到了实际上是从MappingRegistry里拿到URL-->XXXController的映射关系的,那么下面就来演讲MappingRegistry是何时保存的这映射关系,又是怎样保存的
+
+### AbstractHandlerMethodMapping#registerMapping()
+
+我们这里还是靠猜测,猜它调用哪个方法。找到了如下方法,给第一行打断点,重启。
+
+```java
+ //分析所有的Controller;里面的每一个@RequestMapping 注解才能知道这个事情
+ public void registerMapping(T mapping, Object handler, Method method) { //哪个请求由哪个方法处理会通过这个进行注册
+ if (logger.isTraceEnabled()) {
+ logger.trace("Register \"" + mapping + "\" to " + method.toGenericString());
+ }
+ this.mappingRegistry.register(mapping, handler, method);
+ }
+```
+
+重启的时候没有发现没跑到这里,然后我就看了下这个MappingRegistry,发现它是个AbstractHandlerMethodMapping的内部类
+
+
+
+
+
+#### MappingRegistry(URL=>XXXController的映射中心)
+
+```java
+class MappingRegistry {
+
+ private final Map
+
+
+
+意料之中,启动应用的时候从init初始化那里调用过来了
+
+#### 流程概述
+
+1. `DispatcherServlet#onRefresh()`开始初始化九大组件,就会开始初始化HandlerMapping
+2. 首先是创建`DispatcherServlet.properties`里指定的三个HandlerMapping实现类的对象。还是用createBean来创建HandlerMapping的
+3. 其中RequestMappingHandlerMapping创建完对象后,因为它实现了InitializingBean,所以会调用RequestMappingHandlerMapping#afterPropertiesSet()
+4. 接着就是拿到Web子容器的所有组件,for循环处理。看是不是有@Controller注解或者@RequestMapping注解
+5. 最后把分析到的RequestMapping信息放到HandlerMapping的registry中
+
+
+
+### RequestMappingHandlerMapping#afterPropertiesSet()
+
+```java
+public void afterPropertiesSet() {
+
+ this.config = new RequestMappingInfo.BuilderConfiguration();
+ this.config.setTrailingSlashMatch(useTrailingSlashMatch());
+ this.config.setContentNegotiationManager(getContentNegotiationManager());
+
+ if (getPatternParser() != null) {
+ this.config.setPatternParser(getPatternParser());
+ Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch,
+ "Suffix pattern matching not supported with PathPatternParser.");
+ }
+ else {
+ this.config.setSuffixPatternMatch(useSuffixPatternMatch());
+ this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
+ this.config.setPathMatcher(getPathMatcher());
+ }
+
+ super.afterPropertiesSet();
+}
+```
+
+
+
+### AbstractHandlerMethodMapping#afterPropertiesSet()
+
+```java
+public void afterPropertiesSet() {
+ initHandlerMethods(); //初始化HandlerMethods。
+}
+```
+
+
+
+### AbstractHandlerMethodMapping#initHandlerMethods()初始化HandlerMethods
+
+
+
+
+
+可以看到这里只拿了Web子容器的所有组件进行for循环
+
+### AbstractHandlerMethodMapping#processCandidateBean()
+
+```java
+ protected void processCandidateBean(String beanName) {
+ Class> beanType = null;
+ try {
+ beanType = obtainApplicationContext().getType(beanName);
+ }
+ catch (Throwable ex) {
+ // An unresolvable bean type, probably from a lazy bean - let's ignore it.
+ if (logger.isTraceEnabled()) {
+ logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
+ }
+ }
+ if (beanType != null && isHandler(beanType)) {
+ detectHandlerMethods(beanName); //分析当前bean的HandlerMethods
+ }
+ }
+```
+
+
+
+### RequestMappingHandlerMapping#isHandler()判断当前bean是不是Handler
+
+```java
+ protected boolean isHandler(Class> beanType) {
+ return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
+ AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
+ }
+```
+
+1. 这里首先会过滤Bean,只有你Bean也就是类上标注了@Controller注解或者@RequestMapping注解,才会给这个类走下面的流程
+2. 那这个是什么意思呢?
+3. 平常我们写XXXController就是写一个@Controller注解就完事了【注意@RestController值是@Controller的复合注解,包含了@Controller】
+4. 实际上我们可以不写@Controller注解,直接@Component+@RequestMapping也是一样的效果
+
+### AbstractHandlerMethodMapping#detectHandlerMethods()探查容器里满足条件的方法
+
+1. 然后那个detectHandlerMethods就是探查容器里满足条件的方法
+2. 下面看下怎么探索的
+
+```java
+ protected void detectHandlerMethods(Object handler) {
+ Class> handlerType = (handler instanceof String ?
+ obtainApplicationContext().getType((String) handler) : handler.getClass());
+
+ if (handlerType != null) {
+ Class> userType = ClassUtils.getUserClass(handlerType);
+ Map
+
+
+
+### RequestMappingHandlerMapping#getMappingForMethod()
+
+```java
+ protected RequestMappingInfo getMappingForMethod(Method method, Class> handlerType) {
+ RequestMappingInfo info = createRequestMappingInfo(method); //为每一个方法尝试创建 RequestMappingInfo
+ if (info != null) {
+ RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
+ if (typeInfo != null) {
+ info = typeInfo.combine(info);
+ }
+ String prefix = getPathPrefix(handlerType);
+ if (prefix != null) {
+ info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
+ }
+ }
+ return info;
+ }
+```
+
+### RequestMappingHandlerMapping#createRequestMappingInfo(AnnotatedElement )
+
+```java
+ @Nullable
+ private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
+ // 找每一个标有@RequestMapping注解的方法
+ RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
+ RequestCondition> condition = (element instanceof Class ?
+ getCustomTypeCondition((Class>) element) : getCustomMethodCondition((Method) element));
+ return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
+ }
+```
+
+自此一切就明了了,原理就是上面写的流程概述。for循环组件,找@RequestMapping注解
+
+
+
+### 小总结
+
+Q:功能增强的时候什么时候用后置处理器BeanPostProcessor,什么时候用生命周期InitializingBean呢?
+
+A:
+
+1. 如果是所有组件都可能会用到的增强功能,那就实现后置处理器BeanPostProcessor来增强
+2. 如果是单组件增强,最好用InitializingBean。可以看到mvc的RequestMappingHandlerMapping这里用的就是InitializingBean
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+