Spring MVC默认通过父子容器实现Web层与非Web组件的隔离。但在实际项目中,若未明确控制组件的扫描路径与加载规则,表现层的Controller
、业务层的Service
与数据层的Repository
往往会被“一刀切”地扫描到同一上下文中。例如,业务层的Service
被意外注册到Spring MVC的Web上下文中,或数据源DataSource
等基础设施Bean被表现层的组件直接依赖。这种混乱的加载方式不仅可能破坏分层架构的纯净性,还会导致事务管理失效、依赖注入冲突,甚至引发性能隐患。
加载控制
关键点:
在标准的 Spring MVC 应用中,存在 两个独立的上下文:
上下文类型 | 加载方式 | 典型组件 | Bean 可见性 |
---|---|---|---|
根上下文 (Root) | ContextLoaderListener | Service、Repository、DataSource | 对 Web 上下文可见 |
Web 上下文 (Servlet) | DispatcherServlet | Controller、Interceptor、ViewResolver | 仅 Web 上下文内部可见 |
-
父子关系:Web 上下文是根上下文的子上下文,因此 Web 上下文可以访问根上下文的 Bean,但根上下文无法访问 Web 上下文的 Bean。
-
隔离性:若未正确分层,Web 上下文将无法获取业务层组件。
方案一:精确扫描
@Configuration @ComponentScan("com.cc.controller") public class SpringMvcConfig { }
方案二:过滤
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.stereotype.Controller; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service;@Configuration @ComponentScan(value = "com.cc" , //明确扫描哪个包 value同等于basePackageexcludeFilters = @ComponentScan.Filter( //表示排除哪些包type = FilterType.ANNOTATION,classes = Controller.class),includeFilters = @ComponentScan.Filter( //表示精确扫描哪些包type = FilterType.ANNOTATION,classes = {Service.class, Repository.class}) ) public class SpringConfig { }
测试:首先将spriongMvcConfig中的注解注释,仅让SpringConfig生效。写一个测试类看是否能获取UserController(在扫描SpringConfig时排除了Controller注解(见上面的代码),所以不能获取到):
import com.cc.config.SpringConfig; import com.cc.controller.UserController; import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class App {public static void main(String[] args) {AnnotationConfigApplicationContext a = new AnnotationConfigApplicationContext(SpringConfig.class);System.out.println(a.getBean(UserController.class));} }
接着回到SpringConfig,将排除的部分注释,如果能获取到则说明该方法可行:
简化开发
回到servlet容器,在springmvc环境和pring加载不同的配置:
public class ServletConfig extends AbstractDispatcherServletInitializer {//加载springmvc容器配置@Overrideprotected WebApplicationContext createServletApplicationContext() {AnnotationConfigWebApplicationContext app = new AnnotationConfigWebApplicationContext();app.register(SpringMvcConfig.class);return app;}//设置哪些请求归属springMVC处理@Overrideprotected String[] getServletMappings() {return new String[]{"/"}; //所有请求}//加载spring容器配置@Overrideprotected WebApplicationContext createRootApplicationContext() {AnnotationConfigWebApplicationContext app = new AnnotationConfigWebApplicationContext();app.register(SpringConfig.class);return app;}
}
该方法可以从继承AbstractDispatcherServletInitializer换继承AbstractAnnotationConfigDispatcherServletInitializer:
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class[]{SpringConfig.class};}@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class[]{SpringMvcConfig.class};}@Overrideprotected String[] getServletMappings() {return new String[]{"/"};} }