Output classpath content from within Spring context configuration - java

Does Spring provide any way to output the actual content of the classpath environment variable when it is loading a resource in a context configuration file?
<!-- Import the special context -->
<import resource="classpath:mySpecialApplicationContext.xml"/>
I set the Log4J logging level to ALL for Springframework classes but this value does not appear to be logged by the framework. I am trying to figure out if Spring is loading this from a dependency, and I want to see the classpath setting during application runtime. The application is built by Maven with many dependencies.
If there are two or more mySpecialApplicationContext.xml's in the classpath, which one does Spring use?
Thank you.

You can see the relevant (I think) source code at http://goo.gl/9dK2c
In short:
No, the classpath is not logged
The details of what would be loaded when there is more than one matching resource in the classpath are ClassLoader dependent - the DefaultResourceLoader uses the thread's classloader, but typically, the first one found would be used.

Related

Possible to ignore configuration classes on the classpath?

I have a Spring Boot application that works as expected when ran with embedded tomcat, but I noticed that if I try to run it from an existing tomcat instance that I'm using with a previous project then it fails with a NoClassDefFoundError for a class that I don't use anywhere in my application.
I noticed in the /lib directory I had a single jar that contained a few Spring annotated classes, so as a test I cleaned out the /lib directory which resolved the issue. My assumption is that Spring is seeing some of the configurations/beans/imports on the classpath due to them existing in the /lib directory and either trying to autoconfigure something on its own, or is actually trying to instantiate some of these classes.
So then my question is - assuming I can't always fully control the contents of everything on the classpath, how can I prevent errors like this from occurring?
EDIT
For a little more detail - the class not being found is DefaultCookieSerializer which is part of the spring-session-implementation dependency. It is pulled into one of the classes in the jar located in /lib, but it is not any part of my application.
Check for features provided by #EnableAutoConfiguration. You can explicitly configure set of auto-configuration classes for your application. This tutorial can be a good starting point.
You can remove the #SpringBootApplication annotation from the main class and replace it with an #ComponentScan annotation and an #Import annotation that explicitly lists only the configuration classes you want to load. For example, in a Spring boot MVC app that uses metrics, web client, rest template, Jackson, etc, I was able to replace the #SpringBootApplication annotation with below code and get it working exactly as it was before, with all functional tests passing:
#Import({ MetricsAutoConfiguration.class,
InfluxMetricsExportAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class,
DispatcherServletAutoConfiguration.class,
WebMvcAutoConfiguration.class,
JacksonAutoConfiguration.class,
WebClientAutoConfiguration.class,
RestTemplateAutoConfiguration.class,
RefreshAutoConfiguration.class,
ValidationAutoConfiguration.class
})
#ComponentScan
The likely culprit of mentioned exception are incompatible jars on the classpath.
As we don't know with what library you have the issue we cant tell you the exact reason, but the situation looks like that:
One of Spring-Boot autoconfiguration classes is being triggered by the presence of class on the classpath
Trigerred configuration tries to create some bean of class that is not present in the jar you have (but it is in the specific version mentioned in the Spring BOM)
Version incompatibilities may also cause MethodNotFound exceptions.
That's one of the reasons why it is good practice not to run Spring Boot applications inside the container (make jar not war), but as a runnable jar with an embedded container.
Even before Spring Boot it was preferred to take account of libraries being present on runtime classpath and mark them as provided inside your project. Having different versions of the library on a classpath may cause weird ClassCastExceptions where on both ends names match, but the rest doesn't.
You could resolve specific cases by disabling autoconfiguration that causes your issue. You can do that either by adding exclude to your #SpringBootApplication or using a property file.
Edit:
If you don't use very broad package scan (or use package name from outside of your project in package scan) in your Spring Boot application it is unlikely that Spring Boot simply imports configuration from the classpath.
As I have mentioned before it is rather some autoconfiguration that is being triggered by existence of a class in the classpath.
Theoretical solution:
You could use maven shade plugin to relocate all packages into your own package space: see docs.
The problems is you'd have face:
Defining very broad relocation pattern that would exclude JEE classes that need to be used so that container would know how to run your application.
Relocation most likely won't affect package names used as strings in the Spring Boot annotations (like annotations #PackageScan or #ConditionalOnClass). As far as I know it is not implemented yet. You'd have to implement that by yourself - maybe as some kind of shade plugin resource processor.
When relocating classes you'd have to replace package names in all relevant configuration located in the jars. Possibly also merge some of those.
You'd also have to take into account how libraries that you use, or spring uses use package names or files.
This is definitely not a trivial tasks with many traps ahead. But if done right, then it would possibly allow you to disregard what is on the containers classpath. Spring Boot would also look for classes in relocated packages, and you wouldn't have those in ordinary jars.

Does depedent project in java take log4j config of parent project?

I am having project A and project B, A has jar dependency of project B. I have defined log4j.xml in project A but I am not able to see logs of sub-project(B.jar) in file appender as well as tomcat server console. Does project B will take log4j.xml form parent project A or not then which config does it use?
There is one log4j config for your entire JVM (unless you're working in a containerized environment using class loaders and.... that's not what's described).
Missing log messages implies that the configuration from log4j either (a) isn't what you think it is (i.e. a different log4j.xml is being used) or (b) doesn't have the right settings for the missing log lines.
Adding the following to the JVM at startup may help:
-Dlog4j.debug
It may also be possible to browse the log4j settings via MBeans in jconsole.
If you want all apps (WAR files) in a Tomcat instance to have the same logging configs, the simple solution is to arrange that all WAR files have a copy of the same config file.
If you want the apps to share a common logging framework (with a single configuration), then you should consider using Context Selectors, as described in the Log4j 2 documentation.
Using Context Selectors
There are a few patterns for achieving the desired state of logging separation using ContextSelectors:
Place the logging jars in the container's classpath and set the system property log4j2.contextSelector to org.apache.logging.log4j.core.selector.BasicContextSelector. This will create a single LoggerContext using a single configuration that will be shared across all applications.
Place the logging jars in the container's classpath and use the default ClassLoaderContextSelector. Follow the instructions to initialize Log4j 2 in a web application. Each application can be configured to share the same configuration used at the container or can be individually configured. If status logging is set to debug in the configuration there will be output from when logging is initialized in the container and then again in each web application.
Follow the instructions to initialize Log4j 2 in a web application and set the system property or servlet context parameter log4j2.contextSelector to org.apache.logging.log4j.core.selector.JndiContextSelector. This will cause the container to use JNDI to locate each web application's LoggerContext. Be sure to set the isLog4jContextSelectorNamed context parameter to true and also set the log4jContextName and log4jConfiguration context parameters.
The exact method for setting system properties depends on the container. For Tomcat, edit $CATALINA_HOME/conf/catalina.properties. Consult the documentation for other web containers.
I don't think there is a direct equivalent in Log4j 1.x.

Spring-boot > implicitly auto register a 3rd party bean

This may be an impossible task, but here goes...
Is it possible to register a spring bean, by (ONLY) adding a jar to the classpath of a spring-boot application?
Scenario: I would like to create a non-intrusive plugin jar, which when imported into a spring-boot project's classpath, will automatically be picked up and provide a service (e.g. via a RestController).
Constraints
I don't want to change or reconfigure the existing spring-boot application (i.e. no additional scan paths or bean config).
I don't have any knowledge of the target spring-boot application's package structure/scan paths.
I guess I was hoping that by default Spring scan's its own package structure (i.e. org.springframework.** looking for the presence of database libs, etc) and I could piggy-back off that - I haven't had any luck (so far).
I've setup an example project in github, to further clarify/illustrate my example and attempts.
** Solution Addendum **
This bit that got it working, was to add the following file, which points to an #Configuration config file...
plugin-poc\src\main\resources\META-INF\spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.thirdpartyplugin.PluginConfiguration
I think in such cases you would try to add a spring auto configuration that is annotated with #ConditionalOnClass to be only evaluated if the given class is on the classpath. This class can register the bean and would just be evaluated if the conditional evaluates to true
Here is the relevant part of the spring boot documentation : Creating your own auto-configuration

What is the default classpath for EJBs?

Please forgive my pitiful knowledge of Java EJBs but, when an EJB is deployed to an application server as a .jar file, where do things like Hibernate and log4j first look for their configuration files (hibernate.cfg.xml and log4j.properties) in the .jar file?
(...) when an EJB is deployed to an application server as a .jar file, where do things like Hibernate and log4j first look for their configuration files (hibernate.cfg.xml and log4j.properties) in the .jar file?
This depends on the implementation of the tool and is unrelated to the fact that you are using EJBs. For Hibernate, the documentation writes:
3.7. XML configuration file
An alternative approach to
configuration is to specify a full
configuration in a file named
hibernate.cfg.xml. This file can be
used as a replacement for the
hibernate.properties file or, if both
are present, to override properties.
The XML configuration file is by
default expected to be in the root of
your CLASSPATH.
Regarding Log4J, the procedure is described below:
Default Initialization Procedure
The log4j library does not make any
assumptions about its environment. In
particular, there are no default log4j
appenders. Under certain well-defined
circumstances however, the static
inializer of the Logger class will
attempt to automatically configure
log4j. The Java language guarantees
that the static initializer of a class
is called once and only once during
the loading of a class into memory. It
is important to remember that
different classloaders may load
distinct copies of the same class.
These copies of the same class are
considered as totally unrelated by the
JVM.
The default initialization is very
useful in environments where the exact
entry point to the application depends
on the runtime environment. For
example, the same application can be
used as a stand-alone application, as
an applet, or as a servlet under the
control of a web-server.
The exact default initialization
algorithm is defined as follows:
Setting the log4j.defaultInitOverride system property to any other value then
"false" will cause log4j to skip the
default initialization procedure (this
procedure).
Set the resource string variable to the value of the
log4j.configuration system property. The preferred way to
specify the default initialization
file is through the
log4j.configuration system property. In case the system property
log4j.configuration is not defined, then set the string variable
resource to its default value
"log4j.properties".
Attempt to convert the resource variable to a URL.
If the resource variable cannot be converted to a URL, for example due to
a MalformedURLException, then search
for the resource from the classpath by
calling
org.apache.log4j.helpers.Loader.getResource(resource,
Logger.class) which returns a URL.
Note that the string
"log4j.properties" constitutes a
malformed URL. See
Loader.getResource(java.lang.String)
for the list of searched locations.
If no URL could not be found, abort default initialization. Otherwise,
configure log4j from the URL. The
PropertyConfigurator will be used to
parse the URL to configure log4j
unless the URL ends with the ".xml"
extension, in which case the
DOMConfigurator will be used. You
can optionaly specify a custom
configurator. The value of the
log4j.configuratorClass system property is taken as the fully
qualified class name of your custom
configurator. The custom configurator
you specify must implement the
Configurator interface.
To summarize, if you put both files at the root of your EJB-JAR, they should be found.
Regarding the title of your question, I suggest to read Packaging EJB 3 Applications that I'm quoting below:
Dependencies between Java EE modules
Unfortunately, no Java EE
specification provides a standard for
class loading, and each application
server implements class loaders in
whatever way seems best to the vendor.
However, Java EE defines the
visibility and sharing of classes
between different modules, and we can
depict the dependency between
different modules as shown in figure
4.
As illustrated in figure 4, the EAR
class loader loads all JARs in the lib
directory that is shared between
multiple modules. Typically a single
EJB class loader loads all EJB
packaged in all EJB-JAR modules. The
EJB class loader is often the child of
the application class loader, and
loads all EJB classes. Because the EJB
is a child to the EAR class loader,
all classes loaded at the> EAR level
will be visible to the EJBs.
(source: developer.com)
Figure 4: Illustration of class
visibility of an EAR file containing
multiple web modules, EJBs, and shared
library modules. The EAR class loader
loads the classes in the JARs packaged
as library modules, and all classes
loaded by the EAR class loader are
visible to the EJBs. The classes
loaded by EJB class loader are
typically visible to the web module in
most containers because the WAR class
loader is a child of the EJB class
loader.
I think Log4j would look in more than one place for log4j.properties file. Anyway, all configuration files in an ejb-jar go inside the META-INF directory.

Controlling the classpath in a servlet

My servlet application includes a number of library .jars, some of which contain embedded log4j.xml or log4j.properties files. I'd like to ensure that log4j finds my log4j.xml first! I've tried searching for some specification of the priorities of the various classpath elements in a servlet (e.g. does WEB-INF/classes always precede WEB-INF/lib?), or some way to configure or tweak the servlet's classloader so that a given resource directory appears early in the classpath. So far, I've drawn a blank. Any suggestions on ensuring that a servlet .war file loads the correct log4j.xml via the classloader?
Tomcat 8.5
Ditto Tomcat 8.0.
See documentation: Class Loader HOW-TO.
Tomcat 8.0
The answer is simple, taken from the Tomcat documentation page, Class Loader HOW-TO. In particular notice the use of the /WEB-INF/ directory/folder.
Therefore, from the perspective of a web application, class or resource loading looks in the following repositories, in this order:
Bootstrap classes of your JVM
/WEB-INF/classes of your web application
/WEB-INF/lib/*.jar of your web application
System class loader classes (described above)
Common class loader classes (described above)
If the web application class loader is configured with <Loader delegate="true"/> then the order becomes:
Bootstrap classes of your JVM
System class loader classes (described above)
Common class loader classes (described above)
/WEB-INF/classes of your web application
/WEB-INF/lib/*.jar of your web application
Tomcat 6
Excerpted from Tomcat 6 page, Class Loader HOW-TO.
Therefore, from the perspective of a web application, class or resource loading looks in the following repositories, in this order:
Bootstrap classes of your JVM
System class loader classes (described above)
/WEB-INF/classes of your web application
/WEB-INF/lib/*.jar of your web application
$CATALINA_HOME/lib
$CATALINA_HOME/lib/*.jar
As far as I understand the resource selection from the classpath is non-deterministic (from the point of view of the app developer). Even if the same file is loaded consistently the behaviour could change:
1. When you upgrade the version of your current container.
2. If you switch containers.
The simplest solution will be to remove embedded log4j config files from library jars. It is almost never a good idea to embed log4j config's as it leads to the problem you are seeing here...
Are they third party jars or jars you developed?
We the Spring Log4jConfigListener in our web.xml file.
You can specify as a context parameter the location of the log4j config file, i.e. you could set it as /WEB-INF/log4j.xml
Would this be an option for you? If you're not using Spring I know that you can set the Log4j location programatically which might also work.
In my experience, WEB-INF/classes typically takes precedence over jars in WEB-INF/lib, however, that also depends on the servlet container you use (I could never figure out the behavior of JRun, for instance). It would help immensely if you could tell me which container you're using.
Also, are you certain that the offending log4j configuration is in a jar in WEB-INF/lib? Typically, when I've run into classpath problems in a servlet container situation, it's because of libraries that reside outside of the web app.
The servlet specs recommend that web app classloaders load their own classes before delegating to the container's classloader (SRV.9.7.2), but since this is counter to the Java spec, not all vendors do this by default (in fact Tomcat is the only container I've used that does this by default). With that said, it's always possible to configure your container's web app classloading behavior. If you tell me which container you're using, I may be able to help you (specifically, I have done this successfully before on WebLogic, WebSphere, Glassfish and JRun)).
If you're unable to control the classpath, since Tomcat is setting it for you, are you at least able to set a system property for log4j.configuration? I believe that location pointed to by that property can be set outside of the classpath.
If not, another approach, although an ugly one, would be to explicitly run one of the configurators yourself in your application code.
You need to have log4j.properties in your CLASSPATH. The best place is under WEB-INF/classes.
You also have to make sure that you use your version of log4j.jar. So, put it in WEB-INF/lib, just to make sure you are not using one from tomcat folders, since it may cause strange classloading issues.

Categories