Java: How custom DI configuration works for WARs and EARs - java

I have written a Bootstrapper class that reads an XML file from the classpath and can be used as a lightweight dependency injector by other classes at runtime:
<!-- myAppConfig.xml -->
<appConfig>
<environment runningOn="LOCAL" host="localhost.example.com" system="Windows 7"/>
</appConfig>
public class Bootstrapper
{
private String runningOn = "Unknown";
private String host = "Unknown";
private String system = "Unknown";
public Bootstrapper(final String appConfigFileName)
{
setRunningOn(extractRunningOn(appConfigFileName));
setHost(extractHost(appConfigFileName));
setSystem(extractSystem(appConfigFileName));
}
public String getHost()
{
return host;
}
// Rest of class ommitted for brevity...
}
// Then in an executable JAR's main method:
public static void main(String[] args)
{
Bootstrapper bootstrapper = new Bootstrapper("myAppConfig.xml");
// Prints: "This app is running on the localhost.example.com server node."
System.out.println("This app is running on the " +
bootstrapper.getHost() + " server node.");
}
In this sense appConfig and Bootstrapper kind of serve as ultra lightweight "DI" mechanisms.
What I'm wondering is: how do I translate this design to a WAR's web.xml and an EAR's server.xml?
Whereas in an executable JAR, the main method explicitly instantiates a Bootstrapper which can then be interrogated for its fields/properties, in a WAR/EAR everything is defined in an XML file (web.xml/server.xml) without a single "entry point". So in a WAR or EAR, if I had multiple classes that each needed to know what the local host name was, I would have to keep instantiating the same Bootstrapper over and over again, passing it the same myAppConfig.xml each time.
I'm wondering if there's a way to configure web.xml and server.xml to instantiate a Bootstrapper on startup/deployment, and to automatically inject/populate my dependent classes right then and there (or, at the very least, to give each dependent class access to the global/singleton Bootstrapper defined in the XML file).
Thanks in advance!

For a war (and an ear since it will contain a war) project you could use a ServletContextListener to instantiate your Bootstrapper.
A good example of how to use a ServletContextListener is here.
However if you are using Java EE 6 then a better way to do this would be to use an EJB 3.1 Singleton and some CDI.
import javax.ejb.Singleton
import javax.ejb.Startup
import javax.enterprise.context.ApplicationScoped
#Singleton // EJB 3.1 Singleton
#Startup // Telling the container to eagerly load on startup
#ApplicationScoped // CDI Scope
public class Bootstrapper {
private String host = "Unknown";
#PostConstruct
public void readConfiguration() {
// ... load your xml file
}
public String getHost() {
return host;
}
}
Using the above you can now inject this Bootstrapper bean within most of the EE 6 ecosystem with a simple #Inject or #EJB annotation.

Related

Spring Beans Created Multiple Times

I am just new to Spring and facing a design problem. The problem is i have a maven multiple module projects. Project Structures goes as follows.
module-backoffice - Packaging(war)
module-ws - Packaging(war)
module-dao - Packaging(jar)
module-shared - Packaging(jar)
In "module shared "all my service class and in "module-dao" all dao related codes exists.
module-shared and module-dao is in the build path of module-backoffice and module-ws.
Now Problem is when i run module-backoffice war and module-ws war Spring creates two instance of Service class beans and dao class beans.
What should i do so that one instance of service class and dao class will be created and shared among multiple war ? Please help. Thanks you.
Code in Module-Shared: Below is Service factory class
#org.springframework.stereotype.Service
public class Services {
#Autowired
private List<Service> services;
private static final Map<Class<?>, Service> serviceCache = new ConcurrentHashMap<Class<?>, Service>();
#PostConstruct
public void initServiceCache() {
services.forEach(service -> serviceCache.put(service.getClass(), service));
services = null;
}
public static Bootstrap bootstrap() {
return (Bootstrap) serviceCache.get(Bootstrap.class);
}
And the service class is :
#Component
public class Bootstrap implements Service {
public Bootstrap() {
System.out.println("Bootstrap");
}
}
When i run tomcat deploying two wars then in console two times "Bootstrap" printed. How do i restrict that.?
The purpose of web container like tomcat is to able to run applications independently so they can be started and stopped without affecting each other. In case you think there can be multiple future applications will also require the same service, you can make a separate application and expose an API for the operations.

Java Weld CDI alternative Factory

In my production code I have an factory, this factory should be mocked in my test code.
I have an interface which both factories implement:
public interface FtpTransferFactory {
FtpTransfer createFtpTransfer(String host, String machine);
}
Production code:
#Default
public class FtpTransferFactoryImpl implements FtpTransferFactory {
public FtpTransferFactoryImpl() {
}
#Override
public FtpTransfer createFtpTransfer(final String host, final String machine) {
return new FtpTransfer(); // Some real ftp transfer object
}
}
Test code:
#Alternative
public class FtpTransferFactoryTestImpl implements FtpTransferFactory {
#Override
public FtpTransfer createFtpTransfer(String host, String machine) {
return ...; // Some real ftp transfer object, with different settings (test env)
}
}
In the beans.xml located at src/test/resources:
<alternatives>
<class>engine.FtpTransferFactoryTestImpl</class>
</alternatives>
My implementing class:
#Default
public class SomeClass
/** Ftp Factory */
#Default
#Inject
private FtpTransferFactory ftpFactory;
...
}
When I execute my unit tests my implementing class still ends up with the production factory instead of the test factory. However, when I put the -element into my src/main/resources (production) it does work. But I don't want that since i'm putting testing code into production code. I've seen several tutorials doing it via this method... what I'm a doing wrong?
The classes in src/main/resources and src/test/resources are two separate bean deployment archives (BDA). A beans.xml descriptor only affects the current BDA.
So your <alternative> definition only affects your test classes but not your production classes.
If you use CDI 1.1, you can make your alternative global (i.e. activate it for all BDAs in your application) by adding a #Priority annotation.
On CDI 1.0, you could try using #Specializes instead of #Alternative to override your default bean.

Can a class loaded with #Inject use an #EJB?

TL;DR: Web Service uses an #Injected class, #Injected class uses #EJBs. #EJBs are null. Why are they null and how do I fix this?
I'm using Glassfish 3 and I have a #Stateless #WebService that is #Injecting a class with a #Dependent annotation on it. I'd like this class to be able to use other stateless ejbs as fields like this:
#EJB(name = "ejb/MySessionBean", beanName = "MySessionBean")
private MySessionLocal mySessionLocal;
But, when I try to call this web service, these #EJB fields are null (although the #Dependent class itself seems to be injected into the web service correctly). Is it possible to do what I'm trying to do?
I should add that my Web Service and my EJBs are in an EJB jar in an ear's root. The #Dependent class is inside a jar in the ear's lib/ directory.
UPDATE: I've discovered that the #EJBs work correctly (are not null) if I move the #Dependent class into the same jar as the web service. To me this suggests a classloader issue? An ear's ejb jar can #Inject a class in a "lib/*.jar", but a class in a "lib/*.jar" can't get an #EJB from a ejb jar in the ear's root.
It's still unclear to me if this is by design.
An ear's ejb jar can #Inject a class in a "lib/*.jar", but a class in a "lib/*.jar" can't get an #EJB from a ejb jar in the ear's root.
It's still unclear to me if this is by design.
I believe this is by design. A library (something in the .ear file's library directory) does not have to be processed by the machinery that fills #EJB-annotated slots. To put it another way, only a Java EE module (an EJB jar, a web application) will have its #EJB-annotated fields "filled".
CDI, by contrast, has no such restrictions (provided that the relevant META-INF/beans.xml files exist), so it can "fill" #Inject-annotated fields with beans sourced from any bean archive.
I have found a workaround, though I'd prefer not to have to use it. Its always been the case that I can get a reference to the #EJB by doing this (in my "lib/*.jar"):
//inside EjbLookup.java
public <T> T lookupEjb(String sessionBeanClassName) {
return lookup("java:comp/env/ejb/" + sessionBeanClassName);
}
public <T> T lookup(String name) {
try {
Context c = new InitialContext();
return (T) c.lookup(name);
} catch (NamingException ne) {
log.error(ne.getMessage(), ne);
throw new RuntimeException(ne);
}
}
So I can create a class like this in my "lib/*.jar":
import javax.enterprise.inject.Produces;
public class EjbProducer {
private EjbLookup ejbLookup = new EjbLookup();
#Produces
public MySessionLocal getMySessionLocal() {
return ejbLookup.lookupEjb("MySessionBean");
}
}
And now I can #Inject the EJB anywhere in my "lib/*.jar" by doing this:
#Inject
private MySessionLocal mySessionLocal;
On the other hand, if I attempt to use this code inside the EJB jar itself, I get an error about how the stateless ejb could not be created. Perhaps that has more to do with the way you're supposed to use JNDI than CDI though. I can work around this by using #EJB when I'm inside the ejb jar and using #Inject when I'm in the lib.

EJB injection not working

I have 2 EJB module projects and I want from one of the projects to call a stateless no-interface bean from another project. I want to inject the bean to be called using the EJB annotation. The problem is the injection doesn't work(I use NetBeans 7.4 if that is relevant).
The stateless no-interface EJB being called:
package standalonepackage;
import javax.ejb.Stateless;
import javax.ejb.LocalBean;
#Stateless
#LocalBean
public class StandaloneBean {
private static final String message="Greetings!";
public String returnMessage(){
return message;
}
}
The interface of the bean that calls the bean above(this ejb resides in another ejb module project)
#Local
public interface ExampleBeanLocal {
public String getMessage();
}
The implementation of the interface:
#Stateless
public class ExampleBean implements ExampleBeanLocal {
#EJB
private StandaloneBean standaloneBean;
#Override
public String getMessage() {
return String.format("Me - and the second message %s", standaloneBean.returnMessage());
}
}
I also have a main class that just calls the ExampleBean getMessage method(MainClass is located in the second ejb module project):
public class MainClass {
private static ExampleBeanLocal instance = new ExampleBean();
public static void main(String[] args) {
System.out.println(instance.getMessage());
}
}
What am I missing?
First of all if you want to access your business logic as EJB then first you will need to deploy the EJB in an application server. During the deployment process the application server will create something called the JNDI name which is like a gatepass to access your business logic.
Secondly, there are two ways you can invoke an EJB.
1. Creating ContextLookup using JNDI name
2. Using Context Dependency Injection CDI (only within the same Container)
You cannot invoke an EJB using CDI from a POJO ( since it is not contained in any container and the EJB your accessing is in a different JVM ). If you want to access an EJB from a POJO you'll need to use #Remote and use the ContextLookup way of accessing an EJB, you can find more information here
http://wiki.netbeans.org/CreatingEJB3UsingNetbeansAndGlassfish
You need application server with EJB container to run this. Have a look at JBoss, Apache TomEE or something else.
you can use this way to run your jar GLASFISH_HOME/bin/appclient -client app.jar
before compiling your maven project mvn assembly:assembly
and add your main class in your pom.xml

Is using VM arguments for servlet configuration 'clean'?

This relates to this answer:
System.getProperty("catalina.base") There can be scenario where client may use any other server
another server-independent system
property yourself, you can set as a VM
argument.
-Dconfig.location=/path/to/folder
In case of Tomcat, you can set it as
JAVA_OPTS environment variable, or
edit the catalina.bat startup file or
edit the Windows Service settings
(when it's installed as Windows
Service), etc. Other servers supports
similar constructs as well.
Is this considered 'clean'? We've been doing this for years, just want to know if this is acceptable, or there is a better way to configure runtime environment.
It feels maybe dirty, but there are apart from putting it in the classpath really no better ways if the requirement is to untouch the WAR whenever you want to change the location of external configuration files.
If untouching the WAR is not a strict requirement and rebuilding the WAR is allowed (e.g. you're using an inhouse developed application with continuous integration and serveradmins are in the same line, etc), then you could also use a <context-param> in web.xml instead.
<context-param>
<param-name>config.location<param-name>
<param-value>/path/to/file</param-value>
</context-param>
It's then in any Servlet (or better, ServletContextListener) available by ServletContext#getInitParameter():
String configLocation = servletContext.getInitParameter("config.location");
File configFile = new File(configLocation, "config.properties");
// ...
My understanding is that "more clean" would be using either <servlet-param> <init-param> in web.xml or some kind of IoC solution, like Spring.
I feel this is not the cleanest of ways to attain what you want. You can use the web.xml init params or servlet params tags.
Another way is using properties file or an XML configuration file.
I just solved a similar problem in a slightly different way. Our customer wants to configure database connection details, integration server locations and ports etc. without rebuilding the war. Using environment property to point an external file containing the information may or may not be okay, but it felt a bit dirty trick. Anyway, here's a slightly more enterprisey way.
For database connections we use JNDI lookup and below is the current solution for integration server parametrization. The parameters can come from at least three different sources now:
properties-file, which is overridable with Maven profiles and requires single line of xml in spring configuration to be accessible. This is obviously inside the war file.
web.xml context-param. This is also, of course, inside the war file.
Tomcat server can override the init parameters with context.xml which can be outside the war. This happens to be the same file where JNDI context is defined, which is nice.
Below is the implementation for configuration accessor bean. It can run in servlet context and also without one (for some unit tests it makes little sense to kickstart a full-blown web server, but we nevertheless need to satisfy spring bean injections).
I don't mean this to be a perfect solution, but it is one. Didn't find anything like this with google.
#Service
public class IntegrationConfigurationImpl implements
IntegrationConfiguration, InitializingBean,
ServletContextAware, ApplicationContextAware {
private static final String SERVER_HOST_PROPERTY = "integration.server.host";
private static final String SERVER_PORT_PROPERTY = "integration.server.port";
private static final String PROPERTY_BEAN_NAME = "integrationProperties";
private ServletContext servletContext;
private ApplicationContext applicationContext;
private static final Logger log = LoggerFactory.getLogger(IntegrationConfigurationImpl.class);
private String serverHost = "foo";
private int serverPort = 42;
#Override
public String getServerHost() {
return serverHost;
}
#Override
public int getServerPort() {
return serverPort;
}
#Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#Override
public void afterPropertiesSet() throws Exception {
// konfiguraation validointi..
if (servletContext == null) {
log.info("servlet context not set, not running as a web application. Trying to get properties from application context");
if (applicationContext.containsBean(PROPERTY_BEAN_NAME)) {
Properties p = (Properties)applicationContext.getBean(PROPERTY_BEAN_NAME);
serverHost = p.getProperty(SERVER_HOST_PROPERTY);
serverPort = Integer.valueOf(p.getProperty(SERVER_PORT_PROPERTY)).intValue();
} else {
log.info("Property bean not found :" + PROPERTY_BEAN_NAME);
}
} else {
serverHost = servletContext.getInitParameter(SERVER_HOST_PROPERTY);
serverPort = Integer.valueOf(servletContext.getInitParameter(SERVER_PORT_PROPERTY)).intValue();
}
log.info("Using integration server " + getServerHost() + ", port " + getServerPort());
}
}
The disadvantage with having system property is you need to restart the container to modify the system parameter.
Having it as init-param in web.xml, can allow you to modify by just restarting the web app.
Having in init-param is a better way.

Categories

Resources