目录

Spring-MVC中的Controller加载控制与Bean加载控制详解

Spring MVC中的Controller加载控制与Bean加载控制详解

Spring MVC默认通过父子容器实现Web层与非Web组件的隔离。但在实际项目中,若未明确控制组件的扫描路径与加载规则,表现层的 Controller 、业务层的 Service 与数据层的 Repository 往往会被“一刀切”地扫描到同一上下文中。例如,业务层的 Service 被意外注册到Spring MVC的Web上下文中,或数据源 DataSource 等基础设施Bean被表现层的组件直接依赖。这种混乱的加载方式不仅可能破坏分层架构的纯净性,还会导致事务管理失效、依赖注入冲突,甚至引发性能隐患。

加载控制

关键点:

在标准的 Spring MVC 应用中,存在 两个独立的上下文

上下文类型加载方式典型组件Bean 可见性
根上下文 (Root)ContextLoaderListenerService、Repository、DataSource对 Web 上下文可见
Web 上下文 (Servlet)DispatcherServletController、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同等于basePackage
        excludeFilters = @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));
        
    }
}

https://i-blog.csdnimg.cn/direct/b11d09eaa6984ada943ce197644b44f7.png

接着回到SpringConfig,将排除的部分注释,如果能获取到则说明该方法可行:

https://i-blog.csdnimg.cn/direct/09c1538edf774182af8cb5733ec4ebf5.png

https://i-blog.csdnimg.cn/direct/77e3be2079aa456d94c1cc5abbb1d692.png

简化开发

回到servlet容器,在springmvc环境和pring加载不同的配置:

public class ServletConfig extends AbstractDispatcherServletInitializer {

    //加载springmvc容器配置
    @Override
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext app = new AnnotationConfigWebApplicationContext();
        app.register(SpringMvcConfig.class);
        return app;
    }

    //设置哪些请求归属springMVC处理
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};  //所有请求
    }

    //加载spring容器配置
    @Override
    protected WebApplicationContext createRootApplicationContext() {
        AnnotationConfigWebApplicationContext app = new AnnotationConfigWebApplicationContext();
        app.register(SpringConfig.class);
        return app;
    }
}

该方法可以从继承AbstractDispatcherServletInitializer换继承AbstractAnnotationConfigDispatcherServletInitializer:

public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {


    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}