getServletConfigClasses() vs getRootConfigClasses() when extending AbstractAnnotationConfigDispatcherServletInitializer - java

What is the difference between getServletConfigClasses() vs getRootConfigClasses() when extending AbstractAnnotationConfigDispatcherServletInitializer.
I've been reading a lot sources since this morning but I haven't get any clear understanding on the differences yet :
Please have look at these two configurations :
1).
public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { ConServlet.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
....
....
}
The ConServlet.class is refering to
#EnableWebMvc
#Configuration
#ComponentScan({ "com" })
#Import({ SecurityConfig.class })
public class ConServlet {
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/pages/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
2).
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
.....
}
the WebConfig.class is refering to
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = { "....." })
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
I see both ConServlet & WebConfig (more or less) doing the same things like initializating view :
But why :
ConServlet is returned in getRootConfigClasses()
while WebConfig is returned in getServletConfigClasses()
I read the documentation
both getRootConfigClasses() & getServletConfigClasses() is for
Specify #Configuration and/or #Component classes to be provided to..
(their differences )
the root application context for getRootConfigClasses()
the dispatcher servlet application context for getServletConfigClasses()
but why then ConServlet & WebConfig doing same things (like initizialising view), maybe I'm the one misunderstood it. What's are actually root context and dispatcher servlets (I know this one) in the simple term/example
Thank you!

A Bit on ApplicationContext Hierarchies
Spring's ApplicationContext provides the capability of loading multiple (hierarchical) contexts, allowing each to be focused on one particular layer, such as the web layer of an application or middle-tier services.
One of the canonical examples of using hierarchical ApplicationContext is when we have multiple DispatcherServlets in a web application and we're going to share some of the common beans such as datasources between them. This way, we can define a root ApplicationContext that contain all the common beans and multiple WebApplicationContexts that inherit the common beans from the root context.
In the Web MVC framework, each DispatcherServlet has its own WebApplicationContext, which inherits all the beans already defined in the root WebApplicationContext. These inherited beans can be overridden in the servlet-specific scope, and you can define new scope-specific beans local to a given Servlet instance.
Typical context hierarchy in Spring Web MVC (Spring Documentation)
If you're living in a single DispatherServlet world, it is also possible to have just one root context for this scenario:
Single root context in Spring Web MVC (Spring Documentation)
Talk is cheap, Show me the code!
Suppose we're developing a web application and we're going to use Spring MVC, Spring Security and Spring Data JPA. For this simple scenario, we would have at least three different config files. A WebConfig that contains all our web related configurations, such as ViewResolvers, Controllers, ArgumentResolvers, etc. Something like following:
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = "com.so.web")
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
final boolean DO_NOT_USE_SUFFIX_PATTERN_MATCHING = false;
configurer.setUseSuffixPatternMatch(DO_NOT_USE_SUFFIX_PATTERN_MATCHING);
}
}
Here I'm defining a ViewResolver to resolve my plain old jsps, poor life decisions, basically. We would need a RepositoryConfig, which contains all the data access facilities such as DataSource, EntityManagerFactory, TransactionManager, etc. It probably would be like following:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "com.so.repository")
public class RepositoryConfig {
#Bean
public DataSource dataSource() { ... }
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { ... }
#Bean
public PlatformTransactionManager transactionManager() { ... }
}
And a SecurityConfig which contains all the security related stuff!
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
#Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception { ... }
#Override
protected void configure(HttpSecurity http) throws Exception { ... }
}
For gluing all these together, we have two options. First, we can define a typical hierarchical ApplicationContext, by adding RepositoryConfig and SecurityConfig in root context and WebConfig in their child context:
public class ServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RepositoryConfig.class, SecurityConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
Since we have a single DispatcherServlet here, we can add the WebConfig to the root context and make the servlet context empty:
public class ServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RepositoryConfig.class, SecurityConfig.class, WebConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
Further Reading
Skaffman did a great job on explaining ApplicationContext hierarchies in this answer, which is highly recommended. Also, you can read Spring Documentation.

Root Config Classes are actually used to Create Beans which are Application Specific and which needs to be available for Filters (As Filters are not part of Servlet).
Servlet Config Classes are actually used to Create Beans which are DispatcherServlet specific such as ViewResolvers, ArgumentResolvers, Interceptor, etc.
Root Config Classes will be loaded first and then Servlet Config Classes will be loaded.
Root Config Classes will be the Parent Context and it will create a ApplicationContext instace. Where as Servlet Config Classes will be the Child Context of the Parent Context and it will create a WebApplicationContext instance.
In your ConServlet Configuration, You don't need to specify the #EnableWebMvc as well the InternalResourceViewResolver bean as they are only required at the WebConfig.

Related

Why does #ComponentScan behave differently for different configuration files?

I'm relatively new to Spring, and was following some examples.
During one of the examples I noticed that Spring wasn't mapping URI to methods.
I discovered that I put my #ComponentScan annotation on the wrong configuration class and fixed my problem.
So my question is why #ComponentScan works for one of these classes and not with the other?
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"org.zerock.controller"}) // This Works.
public class ServletConfig implements WebMvcConfigurer {
#Bean
public MultipartResolver multipartResolver(){
return new StandardServletMultipartResolver();
}
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
registry.viewResolver(resolver);
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
}
#Configuration
//#ComponentScan(basePackages = {"org.zerock.controller"}) This Doesn't Work
public class RootConfig {
}
// How the two configuration classes are initialized
public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{ServletConfig.class};
}
I've read that root config classes and servlet classes are set up differently in the application context hierarchy.
I suspect that has something to do with this, but I fail to see how that would cause this.
Javadoc for AbstractAnnotationConfigDispatcherServletInitializer recommend to implement:
getRootConfigClasses() -- for "root" application context (non-web infrastructure) configuration.
getServletConfigClasses() -- for DispatcherServlet application context (Spring MVC infrastructure) configuration.
If an application context hierarchy is not required, applications may return all configuration via getRootConfigClasses()
So a #ComponentScan on the RootConfig should work if there are no duplication on the ServletConfig level.
Could you post the error you get and all classes?
I recommend you to place the RootConfig in the root of you packages and use #ComponentScan without specifying base package.

spring mvc no xml configuration 404 error

I am trying to develop a simple Spring MVC with no XML application .its basically show a simple home page. I am using tomcat on JetBrains IDE for development and problem is that when I run it on tomcat I see 404 error this is url http://localhost:8080/MySpringSecurityApp_war/
this is a controller
#Component
public class DemoController {
#GetMapping("/")
public String showHome(){
return "home";
}
}
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.luv2code.springsecurity.demo")
public class DemoAppConfig {
//define a bean for view resolver
#Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver viewResolver=new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
public class MySpringMvcDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {DemoAppConfig.class};
}
#Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
this is error log
9-Jun-2020 13:32:07.511 WARNING [http-nio-8080-exec-1] org.springframework.web.servlet.PageNotFound.noHandlerFound No mapping found for HTTP request with URI [/MySpringSecurityApp_war/] in DispatcherServlet with name 'dispatcher'
09-Jun-2020 13:32:07.604 WARNING [http-nio-8080-exec-4] org.springframework.web.servlet.PageNotFound.noHandlerFound No mapping found for HTTP request with URI [/MySpringSecurityApp_war/] in DispatcherServlet with name 'dispatcher'
this is also how my project structure
You need to define a resource path if you adding something into your URL (after host part basically in your case MySpringSecurityApp_war) you are calling localhost:8080/MySpringSecurityApp_war/ but you didn't define the resource path anywhere so I guess what you need to do is either add #RequestMapping("/MySpringSecurityApp_war/") at class level or just call localhost:8080/ without any resource path
You can also use #RestController in place of #Component.
I hope it will work.

#EnableJdbcHttpSession in Standard Spring Config (no Boot)

I have a spring application and Multi-Node Server with Sticky Sessions. I want to keep the session in the database so as not to make replication sessions.
I tried to implement the jdbc session store using #EnableJdbcHttpSession but I did not manage to create a configuration so that the sessions in the database would work.
I have spring-session-jdbc in pom.xml:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
<version>2.0.2.RELEASE</version>
<type>pom</type>
</dependency>
also have :
#Configuration
#EnableTransactionManagement
#EnableJdbcHttpSession
public class AppConfig{
#Autowired
private AppEnvironment env;
#Bean(name = "dataSource")
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(env.getDriverClassName());
dataSource.setUrl(env.getDatabaseUrl());
dataSource.setUsername(env.getDatabaseUserName());
dataSource.setPassword(env.getDatabasePassword());
dataSource.setValidationQuery(env.getDatabaseValidationQuery());
dataSource.setTestOnBorrow(env.isTomcatTestOnBorrow());
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter jpaAdapter = new HibernateJpaVendorAdapter();
jpaAdapter.setShowSql(Boolean.valueOf(env.isJpaShowSql()));
jpaAdapter.setDatabase(Database.SQL_SERVER);
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPersistenceProvider(new HibernatePersistenceProvider());
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "com.humanbizz.web.entities" });
em.setPersistenceUnitName("persistenceUnit");
em.setJpaVendorAdapter(jpaAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", env.getHibernateDialect());
properties.setProperty("hibernate.default_catalog", env.getDefaultCatalog());
properties.setProperty("hibernate.hbm2ddl.auto", env.getHbm2ddlAuto());
properties.setProperty("hibernate.id.new_generator_mappings", String.valueOf(env.isIdNewGeneratorMappings()));
properties.setProperty("hibernate.default_schema", env.getDefaultSchema());
properties.setProperty("hibernate.connection.useUnicode", String.valueOf(env.isConnectionUseUnicode()));
properties.setProperty("hibernate.connection.charSet", env.getConnectionCharSet());
return properties;
}
#Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
My HttpSessionApplicationInitializer:
public class HttpSessionApplicationInitializer extends AbstractHttpSessionApplicationInitializer{
public HttpSessionApplicationInitializer() {
super(AppConfig.class);
}
#Override
public void onStartup(ServletContext container) {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(WebSecurity.class, Datasource.class);
container.addListener(new ContextLoaderListener(rootContext));
AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
dispatcherContext.register(WebMvc.class);
ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
container.addFilter("securityFilter", new DelegatingFilterProxy("springSecurityFilterChain"))
.addMappingForUrlPatterns(null, false, "/*");
}
}
I have a generated scheme at the database like here https://github.com/spring-projects/spring-session/blob/master/spring-session-jdbc/src/main/resources/org/springframework/session/jdbc/schema-sqlserver.sql
but when you start the application, the session is not stored in the table, I do not know if I missed anything in the configuration, everything I found that should be set up this one but it does not work, any ideas and help would welcome me
The main problem is that you have override the onStartup method of the AbstractHttpSessionApplicationInitializer with that you break the whole purpose of the AbstractHttpSessionApplicationInitializer which is to register the filter needed for Spring Session to do its work.
I would suggest using multiple WebApplicationInitializer so that you can take advantage of the defaults instead of reinventing the wheel. In your case that would mean 3 WebApplicationInitializer.
Bootstrap your application and servlet
Configure security
Configure Spring Session
To configure your application I suggest extending the AbstractAnnotationConfigDispatcherServletInitializer which basically does all you now do manually.
#Order(1)
public MainApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class[] { AppConfig.class, WebSecurity.class};
}
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebMvc.class };
}
}
Now that you have loaded all of your configuration you need to enable Spring Security for that create a class that extends AbstractSecurityWebApplicationInitializer.
#Order(2)
public SecurityInitializer extends AbstractSecurityWebApplicationInitializer() {}
This class is all you need as it will detect the configuration from the root context which is already loaded before hand.
Finally you want the Spring Session stuff and for that create a class extending AbstractHttpSessionApplicationInitializer.
#Order(3)
public SpringSessionInitializer extends AbstractHttpSessionApplicationInitializer {}
Again all you need as it will detect the filter in the earlier created configuration. You might want to place an #Order on each to make sure they get executed in the right order.

Troubles running spring mvc java based configuration in idea local tomcat

I created to simple spring mvc configuration using java based configuration:
Config file:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.kitchen")
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/kitchen/**").addResourceLocations("/kitchen/");
registry.addResourceHandler("/images/**").addResourceLocations("file:E:/Work/images/");
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
Initializer:
public class WebMvcAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[0];
}
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebMvcConfiguration.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
#Override
protected Filter[] getServletFilters() {
return new Filter[]{new CORSFilter()};
}
}
Controller:
#Controller
public class IndexController {
#RequestMapping(value = "/")
public String getIndexPage() {
return "kitchen/index.html";
}
}
All files are located in same package. But when I try to deploy in tomcat, nothing is deployed. I am not good in configurations so I would like to ask maybe I forgot something more? I do not want to use web.xml, just plain java configuration.
Also there could be problem creating modules and artifacts in in Idea IDE I moved not a lot of time ogo to it from eclipse. so here is everithing a little bit different. Here are my configurations of project modules and artifacts, can you please tell me what could be problems in my situation?
Screens:

Convert existing Spring application to Spring-Boot

I have configured and working Spring-based REST application, but now I'd like to convert it to Spring-Boot.
My application uses Spring-Data-JPA on top of JPA datasource with Hibernate provider:
#Configuration
#EnableJpaRepositories("foo.bar.web.repository")
#EnableTransactionManagement
public class RepositoryConfig {
// properties ommited
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(className);
dataSource.setUrl(url);
dataSource.setUsername(userName);
dataSource.setPassword(password);
return dataSource;
}
#Bean
public EntityManagerFactory entityManagerFactory() {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
factory.setPackagesToScan("foo.bar.web.domain");
factory.setDataSource(dataSource());
factory.setJpaPropertyMap(new HashMap<String, Object>() {{
put("hibernate.dialect", dialect);
put("hibernate.hbm2ddl.auto", hbm2ddl);
}});
factory.afterPropertiesSet();
return factory.getObject();
}
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory());
return transactionManager;
}
}
My REST endpoints implemented using SpringMVC with following configuration:
#Configuration
#EnableWebMvc
#ComponentScan("foo.bar.web.controller")
public class MvcConfig extends WebMvcConfigurerAdapter {
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Bean
public MultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
}
Web initializer:
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{
ApplicationConfig.class,
RepositoryConfig.class
};
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{MvcConfig.class};
}
#Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
The problem is that I don't want to use Spring-Boot auto configuration because I'd like to reuse my existing configuration classes with minimal changes, but I cannot find correct way to do this. I tried to implement Spring-boot application class annotated with #SpringBootApplication, but I'm not 100% sure that my config classes is used, because in this case I get java.lang.ClassCastException: org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean$$EnhancerBySpringCGLIB$$ba21071f cannot be cast to javax.persistence.EntityManagerFactory.
Also I tried throw away #EnableAutoConfiguration annotation from application class and add TomcatEmbeddedServletContainerFactory bean to my context manually, but in this case the embedded tomcat is not configured properly.
It would be great if somebody can give me a hint how to solve my problem. I believe that all I need to do is somehow replace my WebInitilizer with Spring-Boot config.
After spending a day in a research, I finally found a solition of my problem.
First of all I had to modify my entityManagerFactory() and transactionManager() beans:
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
factory.setPackagesToScan("foo.bar.web.domain");
factory.setDataSource(dataSource());
factory.setJpaPropertyMap(new HashMap<String, Object>() {{
put("hibernate.dialect", dialect);
put("hibernate.hbm2ddl.auto", hbm2ddl);
}});
factory.afterPropertiesSet();
return factory;
}
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
Also I totally removed my WebInitializerclass and removed #EnableWebMvc annotation from MvcConfig. In Spring-Boot it's not possible to have class extended from WebMvcConfigurerAdapter in classpath because if Spring-Boot find it, all automatic configuration related to SpringMVC will be skipped. Here is the final version of my MvcConfig class:
#Configuration
#ComponentScan("foo.bar.web.controller")
public class MvcConfig {
#Bean
public MultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
}
I used the version of Spring-Boot application class which shown in doc:
#SpringBootApplication(exclude = MultipartAutoConfiguration.class)
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
Note, that in my case I had to exclude MultipartAutoConfiguration from auto configuration because I've already have this feature configured in MvcConfig. Bun it is also possible to leave it autoconfigured, but in this case I had to tune allowed file size in application.properties config file or add a MultipartConfigElement bean to my classpath.

Categories