⭐表示重要。
第一章:提出问题
- 目前情况:DispatchServlet 加载 springmvc.xml ,此时整个 WEB 应用只创建一个 IOC 容器。将来整合 Mybatis 、配置声明式事务等,全都在 springmvc.xml 配置文件中配置其实也是可以的。但是,这样会导致配置文件太长,不容易维护。
- 希望将配置文件分开:
- 处理浏览器请求相关:springmvc.xml 配置文件。
- 声明式事务和整合 Mybatis 相关:spring-persist.xml 配置文件。
- 配置文件分开之后,可以让 DispatcherServlet 加载多个配置文件:
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring*.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
- 但是,如果希望上面的两个配置文件使用不同的机制来加载:
- DispatchServlet 加载 springmvc.xml 配置文件:处理浏览器请求相关。
- ContextLoaderListener 加载 spring-persist.xml 配置文件:不需要处理浏览器请求,需要配置持久化层相关功能。
- 此时,会带来一个新的问题:在 WEB 应用中就会出现两个 IOC 容器:
- DispatchServlet 创建一个 IOC 容器。
- ContextLoaderListener 创建一个 IOC 容器。
注意:这个技术方案并不是
『必须』
这样做,而仅仅是『可以』
这样做。
第二章:配置 ContextLoaderListener
2.1 创建 spring-persist.xml
2.2 配置 ContextLoaderListener
- web.xml
<!-- 配置监听器 -->
<listener>
<!-- 指定全类名 -->
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 通过全局初始化参数指定 Spring 配置文件的位置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-persist.xml</param-value>
</context-param>
- 完整的 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置监听器 -->
<listener>
<!-- 指定全类名 -->
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 通过全局初始化参数指定 Spring 配置文件的位置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-persist.xml</param-value>
</context-param>
<!-- 配置SpringMVC中负责处理请求的核心Servlet,也被称为SpringMVC的前端控制器 -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
2.3 ContextLoaderListener
方法名 | 执行时机 | 作用 |
---|---|---|
contextInitialized() | Web 应用启动时执行 | 创建并初始化 IOC 容器 |
contextDestroyed() | Web 应用卸载时执行 | 关闭 IOC 容器 |
2.4 ContextLoader
ContextLoader 是 ContextLoaderListener 的父类。
① 指定配置文件位置的参数名:
public class ContextLoader {
...
/**
* Name of servlet context parameter (i.e., {@value}) that can specify the
* config location for the root context, falling back to the implementation's
* default otherwise.
* @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION
*/
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
}
- ② 初始化 IOC 容器:
public class ContextLoader {
...
/**
* Initialize Spring's web application context for the given servlet context,
* using the application context provided at construction time, or creating a new one
* according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
* "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
* @param servletContext current servlet context
* @return the new WebApplicationContext
* @see #ContextLoader(WebApplicationContext)
* @see #CONTEXT_CLASS_PARAM
* @see #CONFIG_LOCATION_PARAM
*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
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 ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
}
- ③ 创建 IOC 容器:
public class ContextLoader {
...
/**
* Instantiate the root WebApplicationContext for this loader, either the
* default context class or a custom context class if specified.
* <p>This implementation expects custom contexts to implement the
* {@link ConfigurableWebApplicationContext} interface.
* Can be overridden in subclasses.
* <p>In addition, {@link #customizeContext} gets called prior to refreshing the
* context, allowing subclasses to perform custom modifications to the context.
* @param sc current servlet context
* @return the root WebApplicationContext
* @see ConfigurableWebApplicationContext
*/
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
}
第三章:探讨两个 IOC 容器之间的关系
- 打印两个 IOC 容器对象的 toString() 方法:
package com.github.fairy.era.handler;
import com.github.fairy.era.service.HelloWorldService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.FrameworkServlet;
import javax.servlet.ServletContext;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-20 07:18
*/
@Controller
public class HelloWorldHandler {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private HelloWorldService helloWorldService;
@Autowired
private ServletContext servletContext;
@GetMapping("/hello/world")
public String helloWorld(Model model) {
String message = helloWorldService.getMessage();
model.addAttribute("message", message);
Object springMVCIOC = servletContext.getAttribute(FrameworkServlet.SERVLET_CONTEXT_PREFIX + "dispatcherServlet");
logger.info("springMVCIOC = {}", springMVCIOC);
Object springIOC = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
logger.info("springIOC = {}", springIOC);
return "target";
}
}
- 日志如下:
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]
[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()
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
...
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = this.getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
} else {
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(this.getEnvironment());
// 设置父子关系
wac.setParent(parent);
String configLocation = this.getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
this.configureAndRefreshWebApplicationContext(wac);
return wac;
}
}
}
第四章:两个 IOC 容器之间 bean 的互相访问
- EmpDao.java
package com.github.fairy.era.dao;
import org.springframework.stereotype.Repository;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-20 18:07
*/
@Repository
public class EmpDao {
public String getMessage() {
return "你好啊";
}
}
- EmpService.java
package com.github.fairy.era.service;
import com.github.fairy.era.dao.EmpDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-20 18:08
*/
@Service
public class EmpService {
@Autowired
private EmpDao empDao;
public String getMessage() {
return empDao.getMessage();
}
}
- EmpHandler.java
package com.github.fairy.era.handler;
import com.github.fairy.era.service.EmpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-20 18:08
*/
@Controller
public class EmpHandler {
@Autowired
private EmpService empService;
@GetMapping("/message")
public String message(Model model) {
model.addAttribute("message", empService.getMessage());
return "target";
}
}
- springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<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" xmlns="http://www.springframework.org/schema/beans"
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">
<!-- 自动扫描包 -->
<context:component-scan base-package="com.github.fairy.era.handler"></context:component-scan>
<!-- 配置视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 物理视图:视图前缀+逻辑视图+视图后缀 -->
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8"/>
</bean>
</property>
</bean>
</property>
</bean>
<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
<mvc:view-controller path="/" view-name="portal"/>
</beans>
- spring-presist.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"
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">
<context:component-scan base-package="com.github.fairy.era.service,com.github.fairy.era.dao"/>
</beans>
- bean 所属 IOC 容器的关系:
- 父容器:
- EmpService。
- EmpDao。
- 子容器:
- EmpController。
- 父容器:
- 结论:子容器中的 EmpHandler 装配父容器中的 EmpService 能够正常工作,说明子容器可以访问父容器中的 bean 。
- 分析:
子可父用,父不能子用
的根本原因是子容器中有一个属性getParent()
方法可以获取到父容器的这个对应的引用。 - 源码分析:
- ① 在 AbstractApplicationContext 类中,有 parent 属性。
- ② 在 AbstractApplicationContext 类中,有获取 parent 属性的 getParent() 方法。
- ③ 子容器可以通过 getParent() 方法获取到父容器对象的引用,进而调用父容器中类似 “getBean()” 这样的方法获取到需要的 bean 完成装配。
- ④ 父容器中并没有类似 getChildren() 这样的方法,所以没法拿到子容器对象的引用。
第五章:提出和解决重复创建对象的问题
5.1 提出有可能重复创建对象的问题
- 修改 logback.xml ,将日志级别改为 DEBUG
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
<!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
<root level="DEBUG">
<!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
<appender-ref ref="STDOUT" />
</root>
<!-- 根据特殊需求指定局部日志级别 -->
<logger name="org.springframework.web.servlet.DispatcherServlet" level="DEBUG" />
</configuration>
- spring-persist.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"
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">
<context:component-scan base-package="com.github.fairy.era"/>
</beans>
- springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<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" xmlns="http://www.springframework.org/schema/beans"
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">
<!-- 自动扫描包 -->
<context:component-scan base-package="com.github.fairy.era"></context:component-scan>
<!-- 配置视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 物理视图:视图前缀+逻辑视图+视图后缀 -->
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8"/>
</bean>
</property>
</bean>
</property>
</bean>
<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
<mvc:view-controller path="/" view-name="portal"/>
</beans>
- 日志:
...
[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']
[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']
[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']
...
[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']
[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']
[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 用不上。
- springmvc.xml:仅配置和处理请求相关的功能,所以不能给 service 类附加声明式事务功能。
5.3 解决重复创建对象的问题
5.3.1 解决方案一(建议使用)
- 让两个配置文件配置自动扫描的包时,各自扫描各自的组件:
- ① SpringMVC 就扫描 XxxHandler、XXXController 。
- ② Spring 扫描 XxxService 和 XxxDao 。
5.3.2 解决方案二
- 如果由于某种原因,必须扫描同一个包,确实存在重复创建对象的问题,可以采取下面的办法处理。
- springmvc.xml 配置文件在整体扫描的基础上进一步配置:仅包含被 @Controller 注解标记的类。
- spring-persist.xml 配置在整体扫描的基础上进一步配置:排除被 @Controller 注解标记的类。
- 具体 springmvc.xml 配置文件中的配置方式如下:
<!-- 两个Spring的配置文件扫描相同的包 -->
<!-- 为了解决重复创建对象的问题,需要进一步制定扫描组件时的规则 -->
<!-- 目标:『仅』包含@Controller注解标记的类 -->
<!-- use-default-filters="false"表示关闭默认规则,表示什么都不扫描,此时不会把任何组件加入IOC容器;
再配合context:include-filter实现“『仅』包含”效果 -->
<context:component-scan base-package="com.github.spring.component" use-default-filters="false">
<!-- context:include-filter标签配置一个“扫描组件时要包含的类”的规则,追加到默认规则中 -->
<!-- type属性:指定规则的类型,根据什么找到要包含的类,现在使用annotation表示基于注解来查找 -->
<!-- expression属性:规则的表达式。如果type属性选择了annotation,那么expression属性配置注解的全类名 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
- 具体 spring-persist.xml 配置文件中的配置方式如下:
<!-- 两个Spring的配置文件扫描相同的包 -->
<!-- 在默认规则的基础上排除标记了@Controller注解的类 -->
<context:component-scan base-package="com.github.spring.component">
<!-- 配置具体排除规则:把标记了@Controller注解的类排除在扫描范围之外 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>