Spring 4 WebApplicationInitializer log4j2 multiple initialization issues - java

I am building an application using 100% code configuration approach for a spring 4 web app. Following is my web config class.
public class WebAppInitializer extends Log4jServletContainerInitializer implements WebApplicationInitializer {
public void onStartup(ServletContext container) throws ServletException {
super.onStartup(null, container);
// Create the 'root' Spring application context
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(MyAppContext.class);
// Manage the lifecycle of the root application context
container.addListener(new ContextLoaderListener(rootContext));
// Create the dispatcher servlet's Spring application context
AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext();
webContext.register(MyServletContext.class);
// Register and map the dispatcher servlet
ServletRegistration.Dynamic dynamic = container.addServlet("dispatcher", new DispatcherServlet(webContext));
dynamic.setLoadOnStartup(1);
dynamic.addMapping("/api/*");
}
}
Problem -
a. My spring beans are getting initialized twice
b. Whenever I add logj2.xml in my resources (using maven), then my bean creation fails.
I am new to this, kindly help me.
Log4J - 2.5, Tomcat - 8.0.32
Thanks!

I managed to fix it. It was not the problem with WebApplInitializer but with Spring Java Configurations files. I was maintaining separate configs for ApplicationContext and ServletContext. In ApplicationContext, using
#ComponentScan(value = "com.application.module",
excludeFilters = {#ComponentScan.Filter(value = {Configuration.class})})
did the trick.
In servlet context, I used -
#ComponentScan(basePackageClasses = AppContext.class)

Related

Multiple ContextLoader* definitions exception when deploying web app as part of an EAR

Update 1: There might have been some residual files in my JBoss (not sure how though) cause it's working now.
I'm going for the 100% code-based approach to configuration for my Spring MVC web app. It works fine when I deploy it to Jetty. However, when I deploy it as part of an EAR, it complains about multiple ContextLoader* definitions in web.xml - which I don't even have...
My code:
public class WebInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) {
// Create the 'root' Spring application context
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(WebConfig.class);
// Manage the lifecycle of the root application context
container.addListener(new ContextLoaderListener(rootContext));
// Register and map the dispatcher servlet
ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(rootContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"com.acme.web.controller"}) //picks up TestController below.
public class WebConfig extends WebMvcConfigurerAdapter {
//It's actually empty
}
public class TestController {
#RequestMapping(value = "/testThisApp")
public String testThisApp(){
return "testThisApp";
}
}
Exception:
[org.apache.catalina.core.ContainerBase.[jboss.web].[default-host].[/app-web]] (MSC service thread 1-7) Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener: java.lang.IllegalStateException: Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:264) [spring-web-3.1.0.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:111) [spring-web-3.1.0.RELEASE.jar:4.1.7.RELEASE]
at org.apache.catalina.core.StandardContext.contextListenerStart(StandardContext.java:3392) [jbossweb-7.0.13.Final.jar:]
at org.apache.catalina.core.StandardContext.start(StandardContext.java:3850) [jbossweb-7.0.13.Final.jar:]
at org.jboss.as.web.deployment.WebDeploymentService.start(WebDeploymentService.java:90) [jboss-as-web-7.1.1.Final.jar:7.1.1.Final]

What's the intended use of `servlet-context.xml`, `root-context.xml` and `web.xml`?

I a new to Java Spring MVC web development. I am kind of confused by the 3 config files below. They are auto created by the STS webmvc project template.
What's the intended use of them?
Why do we need 3 config files rather than a single one?
Is there any special reason for their different locations?
root-context.xml is the Spring Root Application Context Configuration. It's optional. It's for configuring your non-web beans. You need it for Spring Security or OpenEntityManagerInView Filter though. It would be better to place it in meta-inf/spring.
servlet-context.xml is the Spring Web Application Context Configuration. It's for configuring your Spring beans in a web application. If you use root-context.xml, you should put your non-web beans in root-context.xml, and web beans in servlet-context.xml.
web.xml is for configuring your servlet container, such as Tomcat. You need this one too. It's for configuring servlet filters and the servlet. web.xml is loaded first, then optionally loads your root context, then loads your web context.
You can avoid using xml by using JavaConfig.
Create a file name "javax.servlet.ServletContainerInitializer" (without quotes) the file content will be fully qualified name of the class implementing this interface, put the file here /META-INF/services
You may implement ServletContainerInitializer and override the method like this
public class CourtServletContainerInitializer implements ServletContainerInitializer {
#Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(CourtConfiguration.class);
DispatcherServlet dispatcherServlet = new DispatcherServlet(applicationContext);
ServletRegistration.Dynamic registration = ctx.addServlet("court", dispatcherServlet);
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
After this you do not need web.xml
Do remember if you are using maven to build your application mention this in pom.xml
<properties>
<failOnMissingWebXml>false</failOnMissingWebXml>
</properties>
Before that you have to write a configuration class using #Configuration and #Bean annotations
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
#Configuration
#ComponentScan(basePackages = "com.practice.learnspringmvc.*")
public class CourtConfiguration {
#Bean
public InternalResourceViewResolver internalResourceViewResolver() {
InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
internalResourceViewResolver.setPrefix("/WEB-INF/views/");
internalResourceViewResolver.setSuffix(".jsp");
return internalResourceViewResolver;
}
}
This configuration class replaces your <bean></bean> initializers from servlet-context.xml

How to configure Spring Security with already existing WebApplicationInitializer?

I am doing a web site project with Spring/ Spring MVC I learned how to configure a spring with the java classes and annotations approach, which by far is better than the XML.
Now I want to use Spring Security with my application. However I could not understand how to configure it with already existing WebApplicationInitializer ?
The Spring documentation is not so clear.
Here is my code and what I have so far:
public class AppInitializer implements WebApplicationInitializer{
//public class AppInitializer {
private static final Class<?>[] CONFIG_CLASSES = new Class<?>[]{SiteConfigs.class, AdminConfigurations.class};
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(CONFIG_CLASSES);
DispatcherServlet servlet = new DispatcherServlet(appContext);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", servlet);
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
If I add AbstractSecurityWebApplicationInitializer to the classpath the container throws
exception.
HTTP Status 500 - No WebApplicationContext found: no ContextLoaderListener registered?
So how to config Spring Security so that springSecurityFilterChain get initialized
The exception tells you that you don't have a ContextLoaderListener which, in your case, is true. You only have a DispatcherServlet. By default Spring Security will only lookup the filters from the root application context (the one loaded by the ContextLoaderListener).
If you want to let it use a DispatcherServlets context instead you have to tell it that. You can tell it which to use by overriding the getDispatcherWebApplicationContextSuffix() method.
You aren't limited to a single WebApplicationInitializer you can have multiple in general you want to have one for your application bootstrapping and another to add security. You can then leverage the Spring convenience classes to save a couple of lines of code. See the Spring Security Reference for a sample.

Is Servlet 3.0 XML-less configuration possible with tapestry-spring?

I started writing an app using Spring MVC, then decided to go with Tapestry instead. I want to keep the XML-less configuration approach that I was using originally. I first tried this:
public class ServletConfig implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Spring //
AnnotationConfigWebApplicationContext rootContext = new
AnnotationConfigWebApplicationContext();
rootContext.register(PersistenceContext.class, ApplicationContext.class);
servletContext.addListener(new ContextLoaderListener(rootContext));
// Tapestry //
servletContext.setInitParameter("tapestry.app-package", "...");
FilterRegistration.Dynamic filter = servletContext.addFilter("app", TapestrySpringFilter.class);
filter.addMappingForUrlPatterns(null, false, "/*");
}
}
The problem here is that tapestry creates another ContextLoaderListener, using the empty constructor. Instead of taking in a WebApplicationContext, it looks at the contextClass and contextConfigLocation init parameters. So I tried this:
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Spring //
servletContext.setInitParameter("contextClass",
"org.springframework.web.context.support.AnnotationConfigWebApplicationContext");
servletContext
.setInitParameter(
"contextConfigLocation",
"...config.PersistenceContext ...config.ApplicationContext");
// Tapestry //
servletContext.setInitParameter("tapestry.app-package", "...");
FilterRegistration.Dynamic filter = servletContext.addFilter("app", TapestrySpringFilter.class);
filter.addMappingForUrlPatterns(null, false, "/*");
}
Which caused this:
java.lang.IllegalArgumentException: When using the Tapestry/Spring integration library, you must specifiy a context class that extends from org.apache.tapestry5.spring.TapestryApplicationContext. Class org.springframework.web.context.support.AnnotationConfigWebApplicationContext does not. Update the 'contextClass' servlet context init parameter.
TapestryApplicationContext inherits from org.springframework.web.context.support.XmlWebApplicationContext. So my question(s): is there a way to make annotation-based configuration work with this approach? If not, is there another approach that will allow me to use it? Or is there no way to avoid the XML?
I tried reverting to the first version of ServletConfig I put up on here, but I added
servletContext.setInitParameter("tapestry.use-external-spring-context", "true");
I'm no longer getting the error message, but the page isn't loading either. When I try to load /app/, instead of loading the index page I get this:
<html>
<head>
<title>Error</title>
</head>
<body>/app/index.jsp</body>
</html>
I'm not sure what's causing this, I can't find anything in the logs to indicate any problems. I'm thinking that there's some sort of issue with the dispatcher service. Has anybody seen this type of error before? I'm not sure if this is unrelated to my original problem or if this is a symptom of my approach not being valid. If somebody can tell me that this is a separate issue I'll take the appropriate action.
The out-of-the-box spring integration expects an XML file.
It wouldn't be too hard to extend SpringModuleDef and override locateApplicationContext to return an AnnotationConfigApplicationContext
You would then write your own TapestrySpringFilter implementation which loads your new SpringModuleDef subclass.
--edit---
I was wrong, the tapestry spring integration uses WebApplicationContextUtils.getWebApplicationContext(servletContext) to lookup the ApplicationContext. So you could initialize the servlet context with an ApplicationContext prior to loading the TapestryStringFilter and it should just work.
eg
ApplicationContext myContext = createAnnotationBasedAppContext();
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, myContext);

Spring can't see beans between servlet-context and contextConfigLocation beans

I have a spring mvc project set up like so:
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-contexts/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-contexts/configuration-context.xml</param-value>
</context-param>
It appears if I make a bean in configuration-context.xml and reference a bean in servlet-context.xml it cannot find it. Are these created as two different contexts? Why does this happen / work like this in general?
Yes there are two contexts stacked on each other (parent and child context).
The beans from the DispatcherServlet (servlet-context.xml) can access the beans from the ContextLoaderListener (configuration-context.xml), but not the other way around.
So put the basic stuff in the configuration-context.xml and the web related once into servlet-context.xml.
#See also this Stack Overflow question: ContextLoaderListener or not?
Spring container can definitely see the components decided by component scan base package of context and you can get the bean from the context.
There are two types of context in spring
1. Root context (ApplicationContext)
2. Servlet context (WebApplicationContext)
Visiblity of beans defined in rootContext to servletContext - YES
Beans defined in root context is always visible in all servlet contexts by default. for example dataSource bean defined in root context can be accessed in servlet context as given below.
#Configuration
public class RootConfiguration
{
#Bean
public DataSource dataSource()
{
...
}
}
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.pvn.mvctiles")
public class ServletConfiguration implements WebMvcConfigurer
{
#Autowired
private DataSource dataSource;
...
}
Visiblity of beans defined in servletContext to rootContext - yes*
(Why * in Yes)
1. Initialization of context order is rootContext first and servletContext next.
In the root context configuration class/xml if you try to get the bean defined in servletContext you will get NULL. (because servletContext is not initialized yet, hence we can say beans not visible while initialization of rootContext)
But you can get beans defined in servletContext after initialization of servletContext(you can get beans through application context)
you can print and confirm it by
applicationContext.getBeanDefinitionNames();
2. If you want to access beans of servlet context in the filter or in the another servlet context, add "org.springframework.web.servlet" base package to your root config class/xml
#Configuration
#ComponentScan(basePackages = "org.springframework.web.servlet" )
public class RootConfiguration
after adding you can get all below beans from application context
springSecurityConfig, tilesConfigurer, themeSource, themeResolver, messageSource, localeResolver, requestMappingHandlerMapping, mvcPathMatcher, mvcUrlPathHelper, mvcContentNegotiationManager, viewControllerHandlerMapping, beanNameHandlerMapping, resourceHandlerMapping, mvcResourceUrlProvider, defaultServletHandlerMapping, requestMappingHandlerAdapter, mvcConversionService, mvcValidator, mvcUriComponentsContributor, httpRequestHandlerAdapter, simpleControllerHandlerAdapter, handlerExceptionResolver, mvcViewResolver, mvcHandlerMappingIntrospector
If you want to get your custom beans from rootContext add base package value to rootContext component scan as given below.
#Configuration
#ComponentScan(basePackages = { "com.your.configuration.package", "org.springframework.web.servlet" })
public class RootConfiguration
Above given configuration will be helpful if you want injected dependency be available in your rootContext and can be accessed in your servlet-filter. For example If you catch exception in filter and want to send error response which is same as response sent by HttpMessageConverter but it is configured in servletContext, then you may want to access that configured converter to send the same response.
Note this, below autowiring will not work in servlet-filters
#Autowired
private ApplicationContext appContext;
this will not work in servlet filter, as filters are initialized before spring container got initialized.(Depends on order of filter and DelegatingProxyFilter)
So, to get applicationContext in filter
public class YourFilter implements Filter
{
private ApplicationContext appContext;
#Override
public void init(FilterConfig filterConfig) throws ServletException
{
Filter.super.init(filterConfig);
appContext = WebApplicationContextUtils.getRequiredWebApplicationContext(filterConfig.getServletContext());
}
}
Hope it gives clear idea of how beans can be accessed between contexts.

Categories