What is right way to supply initialization for log4j2 in a Spring MVC Webapp using Java Config and no web.xml? - spring-mvc

I have a Spring MVC Web App (4.0) with log4j2. This webapp uses no web.xml and takes care of configuration through Java Config. Log4j2 configuration needs to take place in an external config file, not on the classpath.
In a previous incarnation with web.xml we had
<context-param>
<param-name>log4jConfiguration</param-name>
<param-value>file:///path/to/log4j2.xml</param-value>
</context-param>
and this worked.
In the new web.xml-less world, I tried this:
public class WebappInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer
{
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("WebappInitializer.onStartup()");
servletContext.setAttribute("log4jConfiguration", "file:///path/to/log4j2.xml");
This does not work. It seems to happen too late and in any event doesn't work. The logs show:
ERROR StatusLogger No Log4j context configuration provided. This is very unusual.
WebappInitializer.onStartup()
and no log4j logging occurs.
What is the right way to replace this context-param declaration with web.xml in a web.xml-less Spring MVC app?
UPDATE:
I can make this problem go away by adding the following web.xml:
<?xml version="1.0"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<context-param>
<param-name>log4jConfiguration</param-name>
<param-value>file:///path/to/log4j2.xml</param-value>
</context-param>
</web-app>
Surely, there must be a better way!

I believe you can reconfigure log4j2 to a new config file during runtime like this.
LoggerContext context = (LoggerContext)LogManager.getContext(false);
context.setConfigLocation(URI.create("path to file"));
context.reconfigure();
Could just add this to your onStartup.

Probably this is a little too late, but you can do the following to set the log4j2 configuration to an external path:
public class ApplicationInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext)
throws ServletException {
servletContext.setInitParameter(Log4jWebSupport.LOG4J_CONFIG_LOCATION, "file:///path/to/log4j2.xml");

I haven’t tested it but the following should be worth a try; replace your servletContext.setAttribute(…) call in the onStartup(…) method with the following Log4jConfigurer call:
Log4jConfigurer.initLogging("file:///path/to/log4j2.xml")
This is basically what happens under the hood when you use log4jConfigLocation in the <context-param> of a web.xml file (via Log4jWebConfigurer, see code).
I’m only wondering if this will also work with log4j2, although I wouldn’t expect the <context-param> way to work with it either. Did you also use log4j2 when you were still using the web.xml file?

Related

getRequestDispatcher(.).forward(req,res) throws java.io.FileNotFoundException

I have upgraded my Servlet from 2.4 to 3.0 and deployed my application on Websphere 8.5.5.8. Application Server starts properly.
When I try to access my home.jsp page in browser it throws:
Controller Main Error OG1000SRVE0190E: File not found: /servlet/com.platform7.affina.operations.servlet.ValidateLoginUser
When I try to debug, code hits my Main Controller Servlet (which is in same package) but inside Controller servlet class I am calling:
this.getServletContext().getRequestDispatcher("Servlet/com.platform7.affina.operations.servlet.ValidateLoginUser").forward(request, response);
Which throws:
FileNotFoundException for Servlet/com.platform7.affina.operations.servlet.ValidateLoginUser.
But ValidateLoginUser is in the same package and classes folder location!
Folder structure:
\NEED4SPEEDCell02\operations_1.ear\OperationsWeb.war\WEB-INF\classes\com\platform7\affina\operations\servlet
ControllerMain.class and ValidateLoginUser.class are in same servlet package.
my Web.xml file:
<servlet>
<servlet-name>servletMain</servlet-name>
<servlet-class>com.platform7.affina.operations.servlet.ControllerMain</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>servletMain</servlet-name>
<url-pattern>/controllerMain</url-pattern>
</servlet-mapping>
So when I access my URL: it hits ControllerMain.class but inside this class I am calling another servlet which is not part of web.xml but is located in same package of ControllerMain.class.
When I print realpath: this.getServletContext().getRealPath("/"));
I get:
C:\WebSphere858\AppServer\profiles\AppSrv01\installedApps\NEED4SPEEDCell02\operations_1.ear\OperationsWeb.war
I tried using getNamedDispatcher(..) too but throws: null.
Same code works fine on Websphere 7 and even works on Websphere 8.5.5.5
Due to security reasons the default setting for com.ibm.ws.webcontainer.disallowServeServletsByClassname property has been changed.
Please Note:This APAR has changed the default value of the
WebContainer custom property
com.ibm.ws.webcontainer.disallowServeServletsByClassname from false to
true so that no security threat could occur. Prior to this change, it
was up to the developer to remember to change the custom property to
true before deploying into production.
Property Name:
com.ibm.ws.webcontainer.disallowServeServletsByClassname Description:
If set to true, disallows the use of serveServletsByClassnameEnabled
at the application server level, overriding any setting of
serveServletsByClassnameEnabled at the application level. This
property affects all applications. Values: true(default)/false
You will need to add that custom property to the Web Container and set it to false for serving servlets by class name.
But as BalusC suggested, you should add your servlet to web.xml in the form:
<servlet>
<servlet-name>servletMain</servlet-name>
<servlet-class>com.platform7.affina.operations.servlet.ValidateLoginUser</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>servletMain</servlet-name>
<url-pattern>/validateLoginUser</url-pattern>
</servlet-mapping>
and change that forward to:
this.getServletContext().getRequestDispatcher("/validateLoginUser").forward(request, response);
And do the same with your other class from the same package.
You seem to be relying on the legacy InvokerServlet which is known to have major security holes. This was deprecated in Tomcat 5 and clones (WebSphere 4) and removed in Tomcat 7 and clones (WebSphere 6).
You're not supposed to use it anymore. Just map the servlet on a normal URL pattern and invoke it. Assuming that the servlet is mapped on an URL pattern of /validateLoginUser via #WebServlet("/validateLoginUser") annotation on the servlet class, or via <url-pattern>/validateLoginUser</url-pattern> in web.xml mapping on the servlet, then you can get a request dispatcher on it as below:
request.getRequestDispatcher("/validateLoginUser");
Or, just refactor shared code to a plain Java class with a method and invoke it the usual Java way. It's these days kind of weird to have shared validation logic tight coupled in a servlet.
See also:
How to invoke a servlet without mapping in web.xml?
To make above upgrade working, I did few other changes as below for future references.
Mainly, I have to change binding files for websphere.
Previously, I had two bindings ibm-web-bnd.xmi and ibm-web-ext.xmi
ibm-web-bnd.xmi
<?xml version="1.0" encoding="UTF-8"?>
<com.ibm.ejs.models.base.bindings.webappbnd:WebAppBinding xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:com.ibm.ejs.models.base.bindings.webappbnd="webappbnd.xmi" xmi:id="WebAppBinding_1226331113121" virtualHostName="default_host">
<webapp href="WEB-INF/web.xml#WebApp"/>
<resRefBindings xmi:id="ResourceRefBinding_1226331113121" jndiName="AffinaDataSource_pma">
<bindingResourceRef href="WEB-INF/web.xml#ResourceRef_AffinaDataSource_pma"/>
</resRefBindings>
</com.ibm.ejs.models.base.bindings.webappbnd:WebAppBinding>
ibm-web-ext.xmi
<?xml version="1.0" encoding="UTF-8"?>
<com.ibm.ejs.models.base.extensions.webappext:WebAppExtension
xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI"
xmlns:com.ibm.ejs.models.base.extensions.webappext="webappext.xmi"
xmi:id="WebAppExtension_1226331113121"
serveServletsByClassnameEnabled="true">
<webApp href="WEB-INF/web.xml#WebApp"/>
<jspAttributes xmi:id="JSPAttribute_1226331113121" name="reloadEnabled" value="true"/>
<jspAttributes xmi:id="JSPAttribute_1226331113122" name="reloadInterval" value="10"/>
</com.ibm.ejs.models.base.extensions.webappext:WebAppExtension>
So as per servlet3 and Websphere 8.5.5.8, I change to replace above two .xmi files with ibm-web-bnd.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-bnd xmlns="http://websphere.ibm.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://websphere.ibm.com/xml/ns/javaee http://websphere.ibm.com/xml/ns/javaee/ibm-web-bnd_1_0.xsd" version="1.0">
<virtual-host name="default_host"/>
<resource-ref name="AffinaDataSourceAlias_pma" binding-name="AffinaDataSource_pma"/>
</web-bnd>
and then while installing application on Websphere 8.5.5.8, it use to throw outofmemmory error, so to fix that I change below max memory parameter from 256m to 512m in wsadmin.bat
C:\WebSphere858\AppServer\bin\wsadmin.bat
set PERFJAVAOPTION=-Xms256m -Xmx512m -Xquickstart
Hope this helps.

Spring4 MVC not recognizing jsps

I am trying to make an MVC project using gradle and Spring4.
#Bean
public UrlBasedViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/jsp/");
resolver.setSuffix(".jsp");
return resolver;
}
...
#RequestMapping("/home")
public String welcome() {
return "index";
}
But when I run using gradle jettyRun I get...
http://localhost:8080/personal-war/home
HTTP ERROR 404
Problem accessing /personal-war/WEB-INF/jsp/index.jsp. Reason:
NOT_FOUND
Update Web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>Spring MVC Application</display-name>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.proj.spring.config</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
I added this line
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
but then only html renders the server side stuff isn't working
First, you need to know that Servlet containers (I'll assume Jetty too) have a Servlet for rendering JSPs. This Servlet is typically extension mapped to *.jsp.
The Servlet Specification gives the order of url-pattern matching
The container will try to find an exact match of the path of the request to the path of the servlet. A successful match selects the
servlet.
The container will recursively try to match the longest path-prefix. This is done by stepping down the path tree a directory
at a time, using the ’/’ character as a path separator. The longest
match determines the servlet selected.
If the last segment in the URL path contains an extension (e.g. .jsp), the servlet container will try to match a servlet that handles
requests for the extension. An extension is defined as the part of
the last segment after the last ’.’ character.
If neither of the previous three rules result in a servlet match, the container will > attempt to serve content appropriate for the resource requested. If a "default" servlet is defined for the application, it will be used. Many containers provide an implicit
default servlet for serving content.
In your case, when you forward to
/WEB-INF/jsp/index.jsp
The Servlet container will match that path to your DispatcherServlet mapped to
/*
This happens before the container could match the JSP servlet mapped to *.jsp.
It therefore uses your DispatcherServlet to service(..) the request. But your DispatcherServlet does not have a handler for
/WEB-INF/jsp/index.jsp
The simple solution would be to map your DispatcherServlet to
/
and have it be the fallback Servlet if no match is found.

my servlet is not found

i'm trying to use a servlet for the first time (and i'm a very bad web developer during my spare time).
I create a servlet and i put it into a folder with all my other classes (javabean). The package where i put my classes is called Jeans.
I made a page that calls the servlet when it is submitted (after pressing a submit button).
<form action="prova" method="post" id="FormNews" name="FormNews">
//prova is the name of the servlet
I can see that in web.xml there is my servlet
<servlet>
<description></description>
<display-name>prova</display-name>
<servlet-name>prova</servlet-name>
<servlet-class>prova</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>prova</servlet-name>
<url-pattern>/prova</url-pattern>
</servlet-mapping>
But when i trying to see my servlet on a browser i get this error
javax.servlet.ServletException: Wrapper cannot find servlet class prova or a class it depends on
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:291)
org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:859)
org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:602)
org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
java.lang.Thread.run(Unknown Source)
What should i do to use a servlet? Probably i have to set somewhere its path...
Probably TomCat can't see my servlet is in the folder my_web_application_name/jeans
Thank you guys
The package where i put my classes is called Jeans
Then your first line of servlet prova.java
package jeans; //use small case package name
public class Prova extends HttpServlet { //see the class name, this is by java naming conventions
....
.......
}
Also, web.xml
<servlet>
<description></description>
<display-name>Prova</display-name>
<servlet-name>Prova</servlet-name>
<servlet-class>jeans.Prova</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Prova</servlet-name>
<url-pattern>/Prova</url-pattern>
</servlet-mapping>
Useful link:
java-naming-conventions
servlets info
Your class is not available in the class path. Ensure its available. If you put in a package, ensure you compile it using package declaration. Once you manually copy the class file , restart the application server. In Web.xml declare the class with package name.
You probably need a tutorial on how to write a servlet.
http://www.mkyong.com/servlet/a-simple-servlet-example-write-deploy-run/
You have to put fully qualified class name into <servlet-class>
Example :
<servlet-class>examples.servlets.HelloWorld</servlet-class>
Thanks everybody.
My error was on the "servlet-class" tag.
I wrote
<servlet-class>Prova</servlet-class>
Instead of
<servlet-class>jeans.Prova</servlet-class>
where jeans was the folder where i put my class.

ServletContextListener not being invoked

I creating a Java EE 7 project using Eclipse Maven plugin. My problem is when I run the application the class that implements SerlvetContextListener does not get invoked. What is causing this problem?
#WebListener
public class ApplicationContextListener implements ServletContextListener{
#Override
public void contextInitialized(ServletContextEvent sce)
{
Request request = new HttpRequest(sce);
new Thread (request).start();
HibernateUtil.getSessionFactory();
}
#Override
public void contextDestroyed(ServletContextEvent sce)
{
}
}
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<listener>com.kyrogaming.AppServletContextListener</listener>
<!-- Jersey Mapping -->
<servlet>
<servlet-name>jersey-servlet</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.kyrogaming.webservices</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>jersey-servlet</servlet-name>
<url-pattern>/service/*</url-pattern>
</servlet-mapping>
<!-- end Jersey Mapping -->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
To summarize JNL's and Ted Goddard's answers:
For a ServletContextListener (or other listeners, such as a ServletContextAttributeListener or a ServletRequestAttributeListener) to be loaded by the servlet container, you need to tell the container about it. As described in the API docs, there are three ways to do this:
Declare it in the deployment descriptor (web.xml):
com.kyrogaming.AppServletContextListener
or annotate its class with #WebListener (see "Note about annotations" below)
or register it programatically, via the methods in ServletContext, such as addListener().
Note about annotations
Method 1) and 3) will always work. For method 2) (annotations) to work, the servlet container must be configured to scan the classes in the classpath, to find the annotated listener classes.
The webapp's own classes (under WEB-INF/classes) and libraries (JARs under WEB-INF/lib) will not be scanned if the web.xml contains the attribute metadata-complete="true" (the attribute defaults to false). See the Java Servlet Specification Version 3.0, chapter 8.1, "Annotations and pluggability".
In a web application, classes using annotations will have their annotations processed only if they are located in the WEB-INF/classes directory, or if they are packaged in a jar file located in WEB-INF/lib within the application. The web application deployment descriptor contains a new “metadata-complete” attribute on the web-app element. The “metadata-complete” attribute defines whether the web descriptor is complete, or whether the class files of the jar file should be examined for annotations and web fragments at deployment time. If “metadata-complete” is set to "true", the deployment tool MUST ignore any servlet annotations present in the class files of the application and web fragments. If the metadata-complete attribute is not specified or is set to "false", the deployment tool must examine the class files of the application for annotations, and scan for web fragments.
So, to allow the container to find annotated classes in JARs, make sure the web.xml sets metadata-complete="false", or does not set it at all.
Note that setting this may delay the application startup; see for example What to do with annotations after setting metadata-complete="true" (which resolved slow Tomcat 7 start-up)? .
Unfortunately, that still does not explain why the ServletContextListener in the question is not loaded. Note that the web.xml in the question does not metadata-complete, meaning it defaults to false, thus classpath scanning is enabled. There is probably some other problem; this checklist hopefully helps in finding it.
Using metadata-complete="false" in web.xml fixed this issue for me.
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1"
metadata-complete="false">
In web.xml you also need to specify the <listener-class>.
<listener>
<listener-class>
com.kyrogaming.AppServletContextListener
</listener-class>
</listener>
For the record, I'm adding yet another possible (and rather vicious) cause of ServletContextListener not being invoked.
This can happen when you have a java.lang.LinkageError, that is when you forgot to add <scope>provided</scope> to your javax.servlet-api dependency.
In such a case the listener instance is created but only the static part is executed, not the contextInitialized and contextDestroyed methods.
You shall discover only when you invoke some servlet, as the linkage error is not raised during listener instantiation.
There is one other extremely rare scenario which can cause this. (which took me 4 hours to uncover)
If you're using Tomcat10 then you can't use javax.servlet library in your maven/gradle.
Tomcat9 still has javax.servlet, but Tomcat10 migrated to jakarta.servlet
Tomcat10 expects to have Listener class that uses jakarta.servlet.ServletContextListener
So use this maven dependency: (scope is provided, because Tomcat10 already has such library)
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>5.0.0</version>
<scope>provided</scope>
</dependency>
The running container might need to explicitly allow scanning for annotations:
Ex for jetty:
cd [JETTY_BASE]
java -jar [JETTY_HOME]/start.jar --add-module=annotations
In a Spring-Boot 1.3+ scenario, you need to have the package for the class annotated with #WebListener (and #WebFilter, #WebServlet) fall under the #ServletComponentScan package scope.
Per Baeldung.

#WebServlet annotation with Tomcat 7

In my application, I had a servlet which was defined like this in the web.xml:
<servlet>
<display-name>Notification Servlet</display-name>
<servlet-name>NotificationServlet</servlet-name>
<servlet-class>com.XXX.servlet.NotificationServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>NotificationServlet</servlet-name>
<url-pattern>/notification/*</url-pattern>
</servlet-mapping>
After moving to use Tomcat 7, I would like to use the #WebServlet annotation that will do the job.
Here is the way I did it:
#WebServlet( name="NotificationServlet", displayName="Notification Servlet", urlPatterns = {"/notification"}, loadOnStartup=1)
public class NotificationServlet extends HttpServlet {
And it does not work.
Could someone please tell me what I did wrong?
Provided that you're sure that you're using Tomcat 7 or newer, the webapp's web.xml has to be declared conform Servlet 3.0 spec in order to get Tomcat to scan and process the annotations. Otherwise Tomcat will still run in a fallback modus matching the Servlet version in web.xml. The support for servlet API annotations was only added in Servlet 3.0 (Tomcat 7).
So, the root declaration of your web.xml must look like below (make sure you remove any DOCTYPE from web.xml too, otherwise it will still be interpreted as Servlet 2.3!).
<web-app
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
Further, there's a minor difference in the URL pattern. The URL pattern /notifications will let the servlet only listen on requests on exactly that path. It does not kick in on requests with an extra path like /notifications/list or something. The URL pattern /notifications/* will let the servlet listen on requests with extra path info as well.
The minimum #WebServlet annotation should thus look like this
#WebServlet("/notifications/*")
The rest of attributes are optional and thus not mandatory to get the servlet to function equally.
See also:
Our servlets wiki page
One may also want to check for having two classes with an annotations with the same name:
#WebServlet(name = "Foo", urlPatterns = {"/foo"})
public class Foo extends HttpServlet {
//...
}
And:
#WebServlet(name = "Foo", urlPatterns = {"/bar"})
public class Bar extends HttpServlet {
//...
}
In this cases, one of the servlets will not work. If you don't use the name, leave it out, like #BalusC suggests. I got the strange behavior that one of the servlets only worked right after changing and compiling it, but not after compilation without changes.
Additionally, in order to use these annotations and compile your code you must import the corresponding dependency in your pom.xml, but as provided cause your "Servlet 3.0" compliant server already has this.
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>

Resources