本周我将整个 webapp 从 xml config 切换到 JavaConfig。我注意到一些奇怪的行为。我正在使用带有集成 tomcat 的 Netbeans。当我部署我的应用程序时,我在日志中注意到了这一点:
Mar 13, 2014 7:55:48 AM org.apache.catalina.core.ApplicationContext log
INFO: Spring WebApplicationInitializers detected on classpath: [x.configuration.SecurityInitializer@703f4126, x.configuration.WebAppInitializer@48ce5f5c]
Mar 13, 2014 7:55:49 AM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring root WebApplicationContext
Mar 13, 2014 7:56:20 AM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring FrameworkServlet 'dispatcher'
Mar 13, 2014 7:56:55 AM org.apache.catalina.core.ApplicationContext log
INFO: Destroying Spring FrameworkServlet 'dispatcher'
Mar 13, 2014 7:56:55 AM org.apache.catalina.core.ApplicationContext log
INFO: Closing Spring root WebApplicationContext
Mar 13, 2014 7:57:04 AM org.apache.catalina.core.ApplicationContext log
INFO: Spring WebApplicationInitializers detected on classpath: [x.configuration.SecurityInitializer@5379ce53, x.configuration.WebAppInitializer@61d8d5f5]
Mar 13, 2014 7:57:05 AM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring root WebApplicationContext
Mar 13, 2014 7:57:33 AM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring FrameworkServlet 'dispatcher'
还有其他运行两次的东西......我有一个在我的调度程序 servlet 上下文中侦听 ContextRefreshedEvents 的类。当我调试 Web 应用程序时,我看到日志语句就好像这段代码正在执行一样,但它从未遇到我设置的任何断点……第一次。在那个神秘的上下文似乎完全初始化然后显然被破坏之后,它再次被初始化。这一次,我的断点被命中,我可以单步执行我的代码。我认为我的所有配置文件都是根据我阅读的所有内容正确配置的,并且我的根/调度程序组件扫描互斥而不是扫描相同的包。
这正在成为一个更大的问题,因为现在我正在尝试设置 BCryptPasswordEncoder。我遇到的问题与我上面描述的问题非常相似——尽管我来自我的 CustomUserDetailsService,但我不再能够调试或查看来自我的 CustomAuthenticationProvider 的日志记录语句。我开始认为它以某种方式使用了我的 CustomAuthenticationProvider 的缓存实例,该实例来自创建的初始幽灵上下文或其他东西。任何解决此问题的帮助将不胜感激。
我的包设置得很扁平,即:x.controllers、x.services、x.domain、x.dao、x.configuration 等,所以我必须使用一些 excludeFilters 来确保正确的上下文正在扫描正确的组件. 无论如何,这是我的配置:
AbstractAnnotationConfigDispatcherServletInitializer:
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{ SecurityConfig.class, ServiceConfig.class, PersistenceConfig.class, Log4jConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class, Initializer.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{
"/rest/*",
"/index.html",
"/login.html",
"/admin.html",
"/index/*",
"/login/*",
"/admin/*"
};
}
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);
OpenEntityManagerInViewFilter openEntityManagerInViewFilter = new OpenEntityManagerInViewFilter();
openEntityManagerInViewFilter.setBeanName("openEntityManagerInViewFilter");
openEntityManagerInViewFilter.setPersistenceUnitName("HSQL");
return new javax.servlet.Filter[]{characterEncodingFilter, openEntityManagerInViewFilter};
}
}
服务配置:
@Configuration
@ComponentScan(basePackages = "x",
excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class),
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Configuration.class)
}
)
public class ServiceConfig {
@Bean
public DefaultAnnotationHandlerMapping defaultAnnotationHandlerMapping() {
DefaultAnnotationHandlerMapping handlerMapping = new DefaultAnnotationHandlerMapping();
handlerMapping.setAlwaysUseFullPath(true);
handlerMapping.setDetectHandlersInAncestorContexts(true);
return handlerMapping;
}
}
安全初始化器:
@Order(1)
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {}
安全配置:
@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
AuthenticationProvider rememberMeAuthenticationProvider = rememberMeAuthenticationProvider();
TokenBasedRememberMeServices tokenBasedRememberMeServices = tokenBasedRememberMeServices();
List<AuthenticationProvider> authenticationProviders = new ArrayList<AuthenticationProvider>(2);
authenticationProviders.add(rememberMeAuthenticationProvider);
authenticationProviders.add(customAuthenticationProvider);
AuthenticationManager authenticationManager = authenticationManager(authenticationProviders);
http
.csrf().disable()
.headers().disable()
.addFilter(new RememberMeAuthenticationFilter(authenticationManager, tokenBasedRememberMeServices))
.rememberMe().rememberMeServices(tokenBasedRememberMeServices)
.and()
.authorizeRequests()
.antMatchers("/js/**", "/css/**", "/img/**", "/login", "/processLogin").permitAll()
.antMatchers("/index.jsp", "/index.html", "/index").hasRole("USER")
.antMatchers("/admin", "/admin.html", "/admin.jsp", "/js/saic/jswe/admin/**").hasRole("ADMIN")
.and()
.formLogin().loginProcessingUrl("/processLogin").loginPage("/login").permitAll()
.and().exceptionHandling().accessDeniedPage("/login").and()
.logout().permitAll();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**", "/css/**", "/img/**");
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(List<AuthenticationProvider> authenticationProviders) {
return new ProviderManager(authenticationProviders);
}
@Bean
public TokenBasedRememberMeServices tokenBasedRememberMeServices() {
return new TokenBasedRememberMeServices("testKey", userDetailsService);
}
@Bean
public AuthenticationProvider rememberMeAuthenticationProvider() {
return new org.springframework.security.authentication.RememberMeAuthenticationProvider("testKey");
}
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
}
MVC 配置:
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan(basePackages = "x.controllers")
public class SpringMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJacksonHttpMessageConverter());
converters.add(marshallingMessageConverter());
super.configureMessageConverters(converters);
}
@Bean
public InternalResourceViewResolver setupViewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
@Bean
public JacksonAnnotationIntrospector jacksonAnnotationIntrospector() {
return new JacksonAnnotationIntrospector();
}
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(jacksonAnnotationIntrospector());
mapper.registerModule(new JodaModule());
mapper.registerModule(new Hibernate4Module());
return mapper;
}
@Bean
public MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() {
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
messageConverter.setObjectMapper(objectMapper());
return messageConverter;
}
@Bean(name = "marshaller")
public Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("some.path");
return marshaller;
}
@Bean
public MarshallingHttpMessageConverter marshallingMessageConverter() {
return new MarshallingHttpMessageConverter(
jaxb2Marshaller(),
jaxb2Marshaller()
);
}
}
我的上下文刷新了侦听器:
@Component
@Configuration
public class Initializer implements ApplicationListener<ContextRefreshedEvent>
{
@Override
public void onApplicationEvent(ContextRefreshedEvent e) {
<do some intialization stuff between several services/daos after spring is done building everything>
}
}
我将省略我的持久性和日志记录配置,因为我认为它不相关......我的 PersistenceConfig 类注释为:
@Configuration
@EnableTransactionManagement
@PropertySource("classpath:database.properties")
并且 Log4jConfig 注释为:
@Order(2)
@Configuration
您可能会注意到一件奇怪的事情,那就是我的 Initializer 实现了使用 @Configuration 注释的 ApplicationListener 类。我这样做只是为了让根 ServiceConfig 在它的组件扫描期间不会拾取它,它只会在调度程序 servlet 上下文中创建。
在经历了很多痛苦和痛苦之后,我认为问题不在于我的 Spring 配置,而是我在 AbstractAnnotationConfigDispatcherServletInitializer 类中缺少的东西......不知何故,不知何故,有些东西使它加倍工作。我应该覆盖受保护的 WebApplicationContext createRootApplicationContext() 还是 public void onStartup(ServletContext servletContext) throws ServletException 或手动创建上下文的东西?
更新
我有一种怀疑,即同时使用 AbstractAnnotationConfigDispatcherServletInitializer 扩展的 WebApplicationInitializer 和 AbstractSecurityWebApplicationInitializer 扩展的 WebApplicationListener 是造成这种情况的根本原因。尽管我知道它会导致 spring security 失败,因为它没有注册 spring security 过滤器链,但我还是决定删除这个类,看看它是否能解决问题——没有区别。
出于沮丧,我决定下载 glassfish 并将其部署到那里。我第一次部署它时,它只运行了一次!它部署了,但很明显,我无法登录,因为弹簧安全性现在被破坏了。作为测试,我重新添加了类“public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {}”,看看会发生什么。正如我所料,问题再次出现。一切都经历了两次。所以,我再次删除它,现在我遇到了完全相同的问题。它仍在构建上下文,运行我的初始化,破坏上下文,然后重新开始。就像第二个注册的 WebApplicationInitializer 以某种方式卡住或缓存在 Web 服务器中。这是我能想到的唯一解释......所以我