Java @RestController bean在根上下文中注册,尽管在excludeFilters中被排除

Java @RestController bean在根上下文中注册,尽管在excludeFilters中被排除,java,spring,rest,Java,Spring,Rest,因此,我使用了基于Java的配置来启动tomcat容器,并创建了spring上下文。以下是我的配置类的外观: @Configuration public class WebAppInitializer implements WebApplicationInitializer { private static final Logger LOGGER = LoggerFactory.getLogger(WebAppInitializer.class); @Override publi

因此,我使用了基于Java的配置来启动tomcat容器,并创建了spring上下文。以下是我的配置类的外观:

@Configuration
public class WebAppInitializer implements WebApplicationInitializer {

  private static final Logger LOGGER = LoggerFactory.getLogger(WebAppInitializer.class);

  @Override
  public void onStartup(ServletContext servletContext) throws ServletException {
    LOGGER.debug("Starting Spring Container");
    WebApplicationContext rootContext = createRootContext(servletContext);
    configureSpringMvc(servletContext, rootContext);
  }

  private WebApplicationContext createRootContext(ServletContext servletContext) {
    LOGGER.debug("Creating Root Context");
    AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
    rootContext.register(RootConfig.class);

    servletContext.addListener(new ContextLoaderListener(rootContext));
    LOGGER.debug("Created Root Context");
    return rootContext;
  }

  private void configureSpringMvc(ServletContext servletContext, WebApplicationContext rootContext) {
    LOGGER.debug("Creating Child Context");
    AnnotationConfigWebApplicationContext mvcContext = new AnnotationConfigWebApplicationContext();
    mvcContext.register(WebMvcConfig.class);
    mvcContext.setParent(rootContext);

    ServletRegistration.Dynamic appServlet = servletContext.addServlet("dispatcher", new DispatcherServlet(mvcContext));
    FilterRegistration.Dynamic authFilter = servletContext.addFilter("authFilter", AuthenticationFilter.class);
    authFilter.addMappingForServletNames(EnumSet.of(DispatcherType.REQUEST), true, "dispatcher");
    appServlet.setLoadOnStartup(1);
    appServlet.addMapping("/");
    LOGGER.debug("Created Child Context");
  }
}
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan(basePackages = { "a.b.controller" })
public class WebMvcConfig extends WebMvcConfigurerAdapter {
}

@Configuration
@Import(value = { PropertiesConfig.class, AppConfig.class })
@ComponentScan(basePackages = "a.b", excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = RestController.class))
public class RootConfig {
}
如您所见,
RootConfig.class
构成我的根应用程序上下文,
WebMvcConfig
构成子应用程序上下文

这些类看起来像:

@Configuration
public class WebAppInitializer implements WebApplicationInitializer {

  private static final Logger LOGGER = LoggerFactory.getLogger(WebAppInitializer.class);

  @Override
  public void onStartup(ServletContext servletContext) throws ServletException {
    LOGGER.debug("Starting Spring Container");
    WebApplicationContext rootContext = createRootContext(servletContext);
    configureSpringMvc(servletContext, rootContext);
  }

  private WebApplicationContext createRootContext(ServletContext servletContext) {
    LOGGER.debug("Creating Root Context");
    AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
    rootContext.register(RootConfig.class);

    servletContext.addListener(new ContextLoaderListener(rootContext));
    LOGGER.debug("Created Root Context");
    return rootContext;
  }

  private void configureSpringMvc(ServletContext servletContext, WebApplicationContext rootContext) {
    LOGGER.debug("Creating Child Context");
    AnnotationConfigWebApplicationContext mvcContext = new AnnotationConfigWebApplicationContext();
    mvcContext.register(WebMvcConfig.class);
    mvcContext.setParent(rootContext);

    ServletRegistration.Dynamic appServlet = servletContext.addServlet("dispatcher", new DispatcherServlet(mvcContext));
    FilterRegistration.Dynamic authFilter = servletContext.addFilter("authFilter", AuthenticationFilter.class);
    authFilter.addMappingForServletNames(EnumSet.of(DispatcherType.REQUEST), true, "dispatcher");
    appServlet.setLoadOnStartup(1);
    appServlet.addMapping("/");
    LOGGER.debug("Created Child Context");
  }
}
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan(basePackages = { "a.b.controller" })
public class WebMvcConfig extends WebMvcConfigurerAdapter {
}

@Configuration
@Import(value = { PropertiesConfig.class, AppConfig.class })
@ComponentScan(basePackages = "a.b", excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = RestController.class))
public class RootConfig {
}
注意这两个类上的
@ComponentScan
。包
a.b.controller
包含我的
@RestController
s。我希望它们只在子上下文中初始化

因此,在我的根上下文中,我从组件扫描中排除了
@RestController
注释类。所以我想那些控制器不应该在那里注册。然而,这并没有发生。我的所有rest控制器都将在根上下文中注册,然后在子上下文中注册(这将覆盖根上下文中的一个)

我不确定为什么会发生这种情况,我很幸运能解决它。我的应用程序中没有任何web.xml,因为一切都由java配置负责。这是一个问题

第二个问题是,即使RestController注册了两次,但在子上下文中加载时,我无法解析这些控制器中的
@Value
注释属性。但是,当从根上下文加载时,属性正在得到解析

下面是一个例子:

@RestController
public class PropertyLessController {

  @Value("${prop1}")
  private String prop1;

  @PostConstruct
  public void init() {
    LOGGER.debug("Property loaded: {}", prop1);
  }
}
我正在
init()
方法中记录属性。该日志出现两次:

Property loaded: someActualValue
Property loaded: @{prop1}
第一个来自根上下文(具有已解析属性),第二个来自子上下文(具有未解析属性)。为什么会这样


我正在使用Spring4.1.0.RELEASE。

这有点棘手

@RestController
@Controller
是元注释<代码>@RestController用
@Controller
注释,
@Controller
@Component
注释

通过此元注释属性,组件扫描过程将查找您的
propertylesController
类型。它为
@RestController
忽略它,但为
@Controller
(或
@Component
)找到它,然后处理程序映射堆栈注册它,因为它找到
@Controller
@RequestMapping

解决方法是在
excludeFilters
中列出
@组件
@控制器
,或将
useDefaultFilters
设置为
false
。显然,这可能不适用于您,因为您可能希望通过这些注释找到其他类型

“适当”的解决办法是将你的东西分开包装

关于财产决议。您配置的
PropertyPlaceHolderConfigure
PropertySourcesPlaceholderConfigurer
(尚未看到您的
PropertiesConfig
)是
BeanFactory后处理器。这种类型的处理器只处理其包含的
BeanFactory
中的bean。换句话说,子上下文的
BeanFactory
不会在父上下文(afaik)中使用
BeanFactoryPostProcessor
。由于
PropertiesConfig
是在
RootConfig
中声明的,因此只有在那里定义的bean才能获得属性解析


您可以在控制器的
init
方法中设置断点。您会注意到,
@value
注释字段的值对于在
DispatcherServlet
的上下文(子上下文)中初始化的bean是无法解析的。

问题在于
根配置的
@ComponentScan
,尽管这不包括
@RestController
,但它扫描整个
a.b
包,包括其所有子包。这反过来也会检测
WebMvcConfig
,后者会扫描
a.b.controller
包,但不会排除任何内容。因此,您的控制器被实例化两次,基本上所有与web相关的东西(也是Spring@MVC)都被加载两次

@配置
也是一个
@组件
,因此会自动检测到。要修复此问题,请在
RootConfig
上为
@Configuration
类添加一个排除项

@Configuration
@Import(value = { PropertiesConfig.class, AppConfig.class })
@ComponentScan( basePackages = "a.b", 
                excludeFilters = {
                    @ComponentScan.Filter(type = FilterType.ANNOTATION, value = RestController.class), 
                    @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Configuration.class})
public class RootConfig {}

那么,将
@Component
添加到excludeFilters也将排除我的另一个@Component注释bean(如您所说)。但是
useDefaultFilters
到底能做什么呢?我知道包装结构有问题。basePackages应该更细粒化,但我将继续更改它作为我的最后一个选项。目前正在寻找一些简单的工作:(我还用正则表达式尝试了excludeFilter,将
a\\\.b\\.controller\\\..*
作为我的模式传递,但也没有成功。)work@RohitJain
useDefaultFilters
指示是否应搜索所有默认注释
@组件
@服务
@存储库
@控制器
。这是
真的默认情况下为de>。将其设置为
false
或多或少相当于在
excludeFilters
中列出注释。那么有什么方法可以读取控制器中的属性吗?我甚至尝试了
Environment\getProperty()
方法,但它返回
null
@RohitJain在子上下文中声明一个
propertyPlaceHolderConfiger
,并使用适当的属性源。您的
WebMvcConfig
在哪个包中?
WebMvcConfig
RootConfig
都在包
a.b.config
也在收集
WebMvcConfig
,它反过来会扫描其余的类,因为额外的
@ComponentScan
@Configuration
类也是
@Component
s.@M.Deinum哦,亲爱的!!!这完全在我的脑海中闪过,甚至没有被注意到..忘了