⭐表示重要。

第一章:提出问题

  • 目前情况:DispatchServlet 加载 springmvc.xml ,此时整个 WEB 应用只创建一个 IOC 容器。将来整合 Mybatis 、配置声明式事务等,全都在 springmvc.xml 配置文件中配置其实也是可以的。但是,这样会导致配置文件太长,不容易维护。
  • 希望将配置文件分开:
    • 处理浏览器请求相关:springmvc.xml 配置文件。
    • 声明式事务和整合 Mybatis 相关:spring-persist.xml 配置文件。
  • 配置文件分开之后,可以让 DispatcherServlet 加载多个配置文件:
  1. <servlet>
  2. <servlet-name>dispatcherServlet</servlet-name>
  3. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  4. <init-param>
  5. <param-name>contextConfigLocation</param-name>
  6. <param-value>classpath:spring*.xml</param-value>
  7. </init-param>
  8. <load-on-startup>1</load-on-startup>
  9. </servlet>
  • 但是,如果希望上面的两个配置文件使用不同的机制来加载:
    • DispatchServlet 加载 springmvc.xml 配置文件:处理浏览器请求相关。
    • ContextLoaderListener 加载 spring-persist.xml 配置文件:不需要处理浏览器请求,需要配置持久化层相关功能。
  • 此时,会带来一个新的问题:在 WEB 应用中就会出现两个 IOC 容器:
    • DispatchServlet 创建一个 IOC 容器。
    • ContextLoaderListener 创建一个 IOC 容器。

注意:这个技术方案并不是 『必须』 这样做,而仅仅是 『可以』 这样做。

第二章:配置 ContextLoaderListener

2.1 创建 spring-persist.xml

创建 spring-persist.xml 文件.png

2.2 配置 ContextLoaderListener

  • web.xml
  1. <!-- 配置监听器 -->
  2. <listener>
  3. <!-- 指定全类名 -->
  4. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  5. </listener>
  6. <!-- 通过全局初始化参数指定 Spring 配置文件的位置 -->
  7. <context-param>
  8. <param-name>contextConfigLocation</param-name>
  9. <param-value>classpath:spring-persist.xml</param-value>
  10. </context-param>
  • 完整的 web.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  4. xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
  5. http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  6. version="4.0">
  7. <filter>
  8. <filter-name>CharacterEncodingFilter</filter-name>
  9. <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  10. <init-param>
  11. <param-name>encoding</param-name>
  12. <param-value>UTF-8</param-value>
  13. </init-param>
  14. <init-param>
  15. <param-name>forceRequestEncoding</param-name>
  16. <param-value>true</param-value>
  17. </init-param>
  18. <init-param>
  19. <param-name>forceResponseEncoding</param-name>
  20. <param-value>true</param-value>
  21. </init-param>
  22. </filter>
  23. <filter-mapping>
  24. <filter-name>CharacterEncodingFilter</filter-name>
  25. <url-pattern>/*</url-pattern>
  26. </filter-mapping>
  27. <filter>
  28. <filter-name>hiddenHttpMethodFilter</filter-name>
  29. <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
  30. </filter>
  31. <filter-mapping>
  32. <filter-name>hiddenHttpMethodFilter</filter-name>
  33. <url-pattern>/*</url-pattern>
  34. </filter-mapping>
  35. <!-- 配置监听器 -->
  36. <listener>
  37. <!-- 指定全类名 -->
  38. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  39. </listener>
  40. <!-- 通过全局初始化参数指定 Spring 配置文件的位置 -->
  41. <context-param>
  42. <param-name>contextConfigLocation</param-name>
  43. <param-value>classpath:spring-persist.xml</param-value>
  44. </context-param>
  45. <!-- 配置SpringMVC中负责处理请求的核心Servlet,也被称为SpringMVC的前端控制器 -->
  46. <servlet>
  47. <servlet-name>dispatcherServlet</servlet-name>
  48. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  49. <init-param>
  50. <param-name>contextConfigLocation</param-name>
  51. <param-value>classpath:springmvc.xml</param-value>
  52. </init-param>
  53. <load-on-startup>1</load-on-startup>
  54. </servlet>
  55. <servlet-mapping>
  56. <servlet-name>dispatcherServlet</servlet-name>
  57. <url-pattern>/</url-pattern>
  58. </servlet-mapping>
  59. </web-app>

2.3 ContextLoaderListener

ContextLoaderListener.png

方法名 执行时机 作用
contextInitialized() Web 应用启动时执行 创建并初始化 IOC 容器
contextDestroyed() Web 应用卸载时执行 关闭 IOC 容器

2.4 ContextLoader

  • ContextLoader 是 ContextLoaderListener 的父类。

  • ① 指定配置文件位置的参数名:

  1. public class ContextLoader {
  2. ...
  3. /**
  4. * Name of servlet context parameter (i.e., {@value}) that can specify the
  5. * config location for the root context, falling back to the implementation's
  6. * default otherwise.
  7. * @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION
  8. */
  9. public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
  10. }
  • ② 初始化 IOC 容器:
  1. public class ContextLoader {
  2. ...
  3. /**
  4. * Initialize Spring's web application context for the given servlet context,
  5. * using the application context provided at construction time, or creating a new one
  6. * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
  7. * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
  8. * @param servletContext current servlet context
  9. * @return the new WebApplicationContext
  10. * @see #ContextLoader(WebApplicationContext)
  11. * @see #CONTEXT_CLASS_PARAM
  12. * @see #CONFIG_LOCATION_PARAM
  13. */
  14. public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
  15. if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
  16. throw new IllegalStateException(
  17. "Cannot initialize context because there is already a root application context present - " +
  18. "check whether you have multiple ContextLoader* definitions in your web.xml!");
  19. }
  20. servletContext.log("Initializing Spring root WebApplicationContext");
  21. Log logger = LogFactory.getLog(ContextLoader.class);
  22. if (logger.isInfoEnabled()) {
  23. logger.info("Root WebApplicationContext: initialization started");
  24. }
  25. long startTime = System.currentTimeMillis();
  26. try {
  27. // Store context in local instance variable, to guarantee that
  28. // it is available on ServletContext shutdown.
  29. if (this.context == null) {
  30. this.context = createWebApplicationContext(servletContext);
  31. }
  32. if (this.context instanceof ConfigurableWebApplicationContext) {
  33. ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
  34. if (!cwac.isActive()) {
  35. // The context has not yet been refreshed -> provide services such as
  36. // setting the parent context, setting the application context id, etc
  37. if (cwac.getParent() == null) {
  38. // The context instance was injected without an explicit parent ->
  39. // determine parent for root web application context, if any.
  40. ApplicationContext parent = loadParentContext(servletContext);
  41. cwac.setParent(parent);
  42. }
  43. configureAndRefreshWebApplicationContext(cwac, servletContext);
  44. }
  45. }
  46. servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
  47. ClassLoader ccl = Thread.currentThread().getContextClassLoader();
  48. if (ccl == ContextLoader.class.getClassLoader()) {
  49. currentContext = this.context;
  50. }
  51. else if (ccl != null) {
  52. currentContextPerThread.put(ccl, this.context);
  53. }
  54. if (logger.isInfoEnabled()) {
  55. long elapsedTime = System.currentTimeMillis() - startTime;
  56. logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
  57. }
  58. return this.context;
  59. }
  60. catch (RuntimeException | Error ex) {
  61. logger.error("Context initialization failed", ex);
  62. servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
  63. throw ex;
  64. }
  65. }
  66. }
  • ③ 创建 IOC 容器:
  1. public class ContextLoader {
  2. ...
  3. /**
  4. * Instantiate the root WebApplicationContext for this loader, either the
  5. * default context class or a custom context class if specified.
  6. * <p>This implementation expects custom contexts to implement the
  7. * {@link ConfigurableWebApplicationContext} interface.
  8. * Can be overridden in subclasses.
  9. * <p>In addition, {@link #customizeContext} gets called prior to refreshing the
  10. * context, allowing subclasses to perform custom modifications to the context.
  11. * @param sc current servlet context
  12. * @return the root WebApplicationContext
  13. * @see ConfigurableWebApplicationContext
  14. */
  15. protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
  16. Class<?> contextClass = determineContextClass(sc);
  17. if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
  18. throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
  19. "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
  20. }
  21. return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
  22. }
  23. }

第三章:探讨两个 IOC 容器之间的关系

  • 打印两个 IOC 容器对象的 toString() 方法:
  1. package com.github.fairy.era.handler;
  2. import com.github.fairy.era.service.HelloWorldService;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.stereotype.Controller;
  7. import org.springframework.ui.Model;
  8. import org.springframework.web.bind.annotation.GetMapping;
  9. import org.springframework.web.context.WebApplicationContext;
  10. import org.springframework.web.servlet.FrameworkServlet;
  11. import javax.servlet.ServletContext;
  12. /**
  13. * @author 许大仙
  14. * @version 1.0
  15. * @since 2021-11-20 07:18
  16. */
  17. @Controller
  18. public class HelloWorldHandler {
  19. private Logger logger = LoggerFactory.getLogger(this.getClass());
  20. @Autowired
  21. private HelloWorldService helloWorldService;
  22. @Autowired
  23. private ServletContext servletContext;
  24. @GetMapping("/hello/world")
  25. public String helloWorld(Model model) {
  26. String message = helloWorldService.getMessage();
  27. model.addAttribute("message", message);
  28. Object springMVCIOC = servletContext.getAttribute(FrameworkServlet.SERVLET_CONTEXT_PREFIX + "dispatcherServlet");
  29. logger.info("springMVCIOC = {}", springMVCIOC);
  30. Object springIOC = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
  31. logger.info("springIOC = {}", springIOC);
  32. return "target";
  33. }
  34. }
  • 日志如下:
  1. 07:51:13.727] [INFO ] [http-nio-8080-exec-4] [com.github.fairy.era.handler.HelloWorldHandler] [springMVCIOC = WebApplicationContext for namespace 'dispatcherServlet-servlet', started on Sat Nov 20 07:51:08 CST 2021, parent: Root WebApplicationContext]
  2. [07:51:13.727] [INFO ] [http-nio-8080-exec-4] [com.github.fairy.era.handler.HelloWorldHandler] [springIOC = Root WebApplicationContext, started on Sat Nov 20 07:51:08 CST 2021]
  • 结论:两个组件分别创建的 IOC 容器是父子关系。
    • 父容器:ContextLoaderListener 创建的 IOC 容器。
    • 子容器:DispatchServlet 创建的 IOC 容器。
  • 父子关系是如何确定的?
    • Tomcat 在读取 web.xml 之后,加载组件的顺序就是监听器、过滤器、Servlet。
    • ContextLoaderListener 初始化时如果检查到有已经存在的根级别 IOC 容器,那么会抛出异常。
    • DispatcherServlet 创建的 IOC 容器会在初始化时先检查当前环境下是否存在已经创建好的 IOC 容器。
      • 如果有:则将已存在的这个 IOC 容器设置为自己的父容器。
      • 如果没有:则将自己设置为 root 级别的 IOC 容器。
  • DispatcherServlet 创建的 IOC 容器设置父容器的源码:
    • 所在类:org.springframework.web.servlet.FrameworkServlet
    • 所在方法:createWebApplicationContext()
  1. public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
  2. ...
  3. protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
  4. Class<?> contextClass = this.getContextClass();
  5. if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
  6. throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
  7. } else {
  8. ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
  9. wac.setEnvironment(this.getEnvironment());
  10. // 设置父子关系
  11. wac.setParent(parent);
  12. String configLocation = this.getContextConfigLocation();
  13. if (configLocation != null) {
  14. wac.setConfigLocation(configLocation);
  15. }
  16. this.configureAndRefreshWebApplicationContext(wac);
  17. return wac;
  18. }
  19. }
  20. }

第四章:两个 IOC 容器之间 bean 的互相访问

  • EmpDao.java
  1. package com.github.fairy.era.dao;
  2. import org.springframework.stereotype.Repository;
  3. /**
  4. * @author 许大仙
  5. * @version 1.0
  6. * @since 2021-11-20 18:07
  7. */
  8. @Repository
  9. public class EmpDao {
  10. public String getMessage() {
  11. return "你好啊";
  12. }
  13. }
  • EmpService.java
  1. package com.github.fairy.era.service;
  2. import com.github.fairy.era.dao.EmpDao;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.stereotype.Service;
  5. /**
  6. * @author 许大仙
  7. * @version 1.0
  8. * @since 2021-11-20 18:08
  9. */
  10. @Service
  11. public class EmpService {
  12. @Autowired
  13. private EmpDao empDao;
  14. public String getMessage() {
  15. return empDao.getMessage();
  16. }
  17. }
  • EmpHandler.java
  1. package com.github.fairy.era.handler;
  2. import com.github.fairy.era.service.EmpService;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.stereotype.Controller;
  5. import org.springframework.ui.Model;
  6. import org.springframework.web.bind.annotation.GetMapping;
  7. /**
  8. * @author 许大仙
  9. * @version 1.0
  10. * @since 2021-11-20 18:08
  11. */
  12. @Controller
  13. public class EmpHandler {
  14. @Autowired
  15. private EmpService empService;
  16. @GetMapping("/message")
  17. public String message(Model model) {
  18. model.addAttribute("message", empService.getMessage());
  19. return "target";
  20. }
  21. }
  • springmvc.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns:context="http://www.springframework.org/schema/context"
  4. xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns="http://www.springframework.org/schema/beans"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
  6. <!-- 自动扫描包 -->
  7. <context:component-scan base-package="com.github.fairy.era.handler"></context:component-scan>
  8. <!-- 配置视图解析器 -->
  9. <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
  10. <property name="order" value="1"/>
  11. <property name="characterEncoding" value="UTF-8"/>
  12. <property name="templateEngine">
  13. <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
  14. <property name="templateResolver">
  15. <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
  16. <!-- 物理视图:视图前缀+逻辑视图+视图后缀 -->
  17. <!-- 视图前缀 -->
  18. <property name="prefix" value="/WEB-INF/templates/"/>
  19. <!-- 视图后缀 -->
  20. <property name="suffix" value=".html"/>
  21. <property name="templateMode" value="HTML5"/>
  22. <property name="characterEncoding" value="UTF-8"/>
  23. </bean>
  24. </property>
  25. </bean>
  26. </property>
  27. </bean>
  28. <mvc:annotation-driven/>
  29. <mvc:default-servlet-handler/>
  30. <mvc:view-controller path="/" view-name="portal"/>
  31. </beans>
  • spring-presist.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
  6. <context:component-scan base-package="com.github.fairy.era.service,com.github.fairy.era.dao"/>
  7. </beans>
  • bean 所属 IOC 容器的关系:
    • 父容器:
      • EmpService。
      • EmpDao。
    • 子容器:
      • EmpController。
  • 结论:子容器中的 EmpHandler 装配父容器中的 EmpService 能够正常工作,说明子容器可以访问父容器中的 bean 。
  • 分析:子可父用,父不能子用的根本原因是子容器中有一个属性 getParent() 方法可以获取到父容器的这个对应的引用。
  • 源码分析:
    • ① 在 AbstractApplicationContext 类中,有 parent 属性。
    • ② 在 AbstractApplicationContext 类中,有获取 parent 属性的 getParent() 方法。
    • ③ 子容器可以通过 getParent() 方法获取到父容器对象的引用,进而调用父容器中类似 “getBean()” 这样的方法获取到需要的 bean 完成装配。
    • ④ 父容器中并没有类似 getChildren() 这样的方法,所以没法拿到子容器对象的引用。

两个 IOC 容器之间 bean 的互相访问.png

第五章:提出和解决重复创建对象的问题

5.1 提出有可能重复创建对象的问题

  • 修改 logback.xml ,将日志级别改为 DEBUG
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <configuration debug="true">
  3. <!-- 指定日志输出的位置 -->
  4. <appender name="STDOUT"
  5. class="ch.qos.logback.core.ConsoleAppender">
  6. <encoder>
  7. <!-- 日志输出的格式 -->
  8. <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
  9. <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
  10. <charset>UTF-8</charset>
  11. </encoder>
  12. </appender>
  13. <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
  14. <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
  15. <root level="DEBUG">
  16. <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
  17. <appender-ref ref="STDOUT" />
  18. </root>
  19. <!-- 根据特殊需求指定局部日志级别 -->
  20. <logger name="org.springframework.web.servlet.DispatcherServlet" level="DEBUG" />
  21. </configuration>
  • spring-persist.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
  6. <context:component-scan base-package="com.github.fairy.era"/>
  7. </beans>
  • springmvc.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns:context="http://www.springframework.org/schema/context"
  4. xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns="http://www.springframework.org/schema/beans"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
  6. <!-- 自动扫描包 -->
  7. <context:component-scan base-package="com.github.fairy.era"></context:component-scan>
  8. <!-- 配置视图解析器 -->
  9. <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
  10. <property name="order" value="1"/>
  11. <property name="characterEncoding" value="UTF-8"/>
  12. <property name="templateEngine">
  13. <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
  14. <property name="templateResolver">
  15. <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
  16. <!-- 物理视图:视图前缀+逻辑视图+视图后缀 -->
  17. <!-- 视图前缀 -->
  18. <property name="prefix" value="/WEB-INF/templates/"/>
  19. <!-- 视图后缀 -->
  20. <property name="suffix" value=".html"/>
  21. <property name="templateMode" value="HTML5"/>
  22. <property name="characterEncoding" value="UTF-8"/>
  23. </bean>
  24. </property>
  25. </bean>
  26. </property>
  27. </bean>
  28. <mvc:annotation-driven/>
  29. <mvc:default-servlet-handler/>
  30. <mvc:view-controller path="/" view-name="portal"/>
  31. </beans>
  • 日志:
  1. ...
  2. [20:37:48.766] [DEBUG] [RMI TCP Connection(3)-127.0.0.1] [org.springframework.beans.factory.support.DefaultListableBeanFactory] [Creating shared instance of singleton bean 'empDao']
  3. [20:37:48.772] [DEBUG] [RMI TCP Connection(3)-127.0.0.1] [org.springframework.beans.factory.support.DefaultListableBeanFactory] [Creating shared instance of singleton bean 'empHandler']
  4. [20:37:48.791] [DEBUG] [RMI TCP Connection(3)-127.0.0.1] [org.springframework.beans.factory.support.DefaultListableBeanFactory] [Creating shared instance of singleton bean 'empService']
  5. ...
  6. [20:37:49.185] [DEBUG] [RMI TCP Connection(3)-127.0.0.1] [org.springframework.beans.factory.support.DefaultListableBeanFactory] [Creating shared instance of singleton bean 'empDao']
  7. [20:37:49.186] [DEBUG] [RMI TCP Connection(3)-127.0.0.1] [org.springframework.beans.factory.support.DefaultListableBeanFactory] [Creating shared instance of singleton bean 'empHandler']
  8. [20:37:49.187] [DEBUG] [RMI TCP Connection(3)-127.0.0.1] [org.springframework.beans.factory.support.DefaultListableBeanFactory] [Creating shared instance of singleton bean 'empService']

5.2 重复创建对象的问题

  • ① 浪费内存空间。
  • ② 两个 IOC 容器能力是不同的:
    • springmvc.xml:仅配置和处理请求相关的功能,所以不能给 service 类附加声明式事务功能。
      • 结论:基于 springmvc.xml 配置文件创建的 EmpService 的 bean 不带有声明式事务的功能。
      • 影响:DispatcherServlet 处理浏览器请求时会调用自己创建的 EmpController,然后再调用自己创建的EmpService,而这个 EmpService 是没有事务的,所以处理请求时 没有事务功能的支持
    • spring-persist.xml:配置声明式事务,所以可以给 service 类附加声明式事务功能。
      • 结论:基于 spring-persist.xml 配置文件创建的 EmpService 有声明式事务的功能。
      • 影响:由于 DispatcherServlet 的 IOC 容器会优先使用自己创建的 EmpController,进而装配自己创建的EmpService,所以基于 spring-persist.xml 配置文件创建的有声明式事务的 EmpService 用不上。

5.3 解决重复创建对象的问题

5.3.1 解决方案一(建议使用)

  • 让两个配置文件配置自动扫描的包时,各自扫描各自的组件:
  • ① SpringMVC 就扫描 XxxHandler、XXXController 。
  • ② Spring 扫描 XxxService 和 XxxDao 。

5.3.2 解决方案二

  • 如果由于某种原因,必须扫描同一个包,确实存在重复创建对象的问题,可以采取下面的办法处理。
    • springmvc.xml 配置文件在整体扫描的基础上进一步配置:仅包含被 @Controller 注解标记的类。
    • spring-persist.xml 配置在整体扫描的基础上进一步配置:排除被 @Controller 注解标记的类。
  • 具体 springmvc.xml 配置文件中的配置方式如下:
  1. <!-- 两个Spring的配置文件扫描相同的包 -->
  2. <!-- 为了解决重复创建对象的问题,需要进一步制定扫描组件时的规则 -->
  3. <!-- 目标:『仅』包含@Controller注解标记的类 -->
  4. <!-- use-default-filters="false"表示关闭默认规则,表示什么都不扫描,此时不会把任何组件加入IOC容器;
  5. 再配合context:include-filter实现“『仅』包含”效果 -->
  6. <context:component-scan base-package="com.github.spring.component" use-default-filters="false">
  7. <!-- context:include-filter标签配置一个“扫描组件时要包含的类”的规则,追加到默认规则中 -->
  8. <!-- type属性:指定规则的类型,根据什么找到要包含的类,现在使用annotation表示基于注解来查找 -->
  9. <!-- expression属性:规则的表达式。如果type属性选择了annotation,那么expression属性配置注解的全类名 -->
  10. <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
  11. </context:component-scan>
  • 具体 spring-persist.xml 配置文件中的配置方式如下:
  1. <!-- 两个Spring的配置文件扫描相同的包 -->
  2. <!-- 在默认规则的基础上排除标记了@Controller注解的类 -->
  3. <context:component-scan base-package="com.github.spring.component">
  4. <!-- 配置具体排除规则:把标记了@Controller注解的类排除在扫描范围之外 -->
  5. <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
  6. </context:component-scan>