Serve with Petite
We have two goals in this chapter: to build service layer with Petite container and to merge it with the our Madvoc web application. Of course, it is important to have Petite completely decoupled from the web layer.
Plain Old Service Core
Let's forget for a moment that we are building a web application. We want to build a
business core of the application that can be started from the command
line (and tests cases). Lets encapsulate the application core in
AppCore
class, that will be responsible for application lifecycle and
configuration. During the startup, AppCore
will create and configure
the Petite container. One possible implementation may look like this:
public class AppCore { public void start() { //AppUtil.resolveDirs(); //initLogger(); initPetite(); //initDb(); // init everything else } public void stop() { // close everything } protected PetiteContainer petite; void initPetite() { petite = new PetiteContainer(); AutomagicPetiteConfigurator pcfg = new AutomagicPetiteConfigurator(); pcfg.setIncludedEntries(this.getClass().getPackage().getName() + ".*"); pcfg.configure(petite); } public PetiteContainer getPetite() { return petite; } ... }
Let's analyze AppCore
. On start()
, it resolve various
paths, to determine where the log folder is, where are the properties etc.
In this example we will not use this kind of magic, instead we will
hardcore all the paths needed to keep things simple. Next,
various frameworks are going to be initialized as well. Usually the
logger is initialized first, so we can track what is going on as soon as
possible. As this is not part of the Jodd, we will skip any further
explanation and go to the Petite initialization.
Petite container is created (method initPetite()
).
Immediately after creation, Petite container is auto-configured using
AutomagicPetiteConfigurator
. This configurator implementation
scans the class path for all classes that are annotated
with @PetiteBean
annotation. Since classpath of web application may
contain many jars, we can speed up the scanning process by narrowing the
search only to certain packages. In our example, the configurator simply
scans for all packages that belong to the package of AppCore
- we
assume that services are somewhere bellow.
This is just one way how you can register your bean.
That is all, Petite is ready for use and upon start it will find all annotated services. Even if we don't have any service at the moment, we can pretend that we have the full-blown application container/context ready. We just need to find a way how to integrate it into previously created web layer.
Integrate Madvoc and Petite
Let's integrate Madvoc and Petite application context.
Jodd already provides support for this integration by
PetiteWebApplication
, a Petite-ready web application class. As you
remember, we already made AppWebApplication
for our custom needs. So
now we need to make it Petite-aware, i.e. to extends
PetiteWebApplication
instead of WebApplication
.
By default, PetiteWebApplication
creates it's own Petite container
instance that will be used as application context. Sine we already have our
container instance in AppCore
, we need to change default behavior of
PetiteWebApplication
and use existing container from AppCore
. It is
nothing hard, just a matter of overriding providePetiteContainer()
.
Here is how our AppWebApplication
might look like, integrated with the
AppCore
:
public class AppWebApplication extends PetiteWebApplication { final AppCore appCore; public AppWebApplication() { appCore = new AppCore(); appCore.start(); } @Override protected PetiteContainer providePetiteContainer() { return appCore.getPetite(); } @Override protected void destroy(MadvocConfig madvocConfig) { appCore.stop(); super.destroy(madvocConfig); } }
And that is all: Petite-aware Madvoc web application. You will see later how this integration actually works, now we need to add some services.
Simple service
Let's add an empty service:
@PetiteBean public class FooService { }
Our business service is just a POJO, annotated with @PetiteBean
annotation. And that is all! Petite configurator will find all such
classes on the classpath and register them into the container.
Service injection
Since Madvoc is integrated with Petite, we can injected our service
in the previously created IndexAction
in this way:
@MadvocAction public class IndexAction { @PetiteInject FooService fooService; @Action public void view() { System.out.println(fooService); } }
Injection is done just by using the @PetiteInject
annotation on a
field! Of course, you can add setter/getter for the service field, but
usually we don't: we like to have our actions smaller as possible.
Note: by default, field name must match lowercased bean class name.
Hey, hurrey, it's run-time again! Run the Tomcat. You will notice in the log that Petite bean is also being registered:
INFO jodd.petite.config.AutomagicPetiteConfigurator - Petite configured in 47 ms. Total beans: 1
INFO jodd.madvoc.Madvoc - Madvoc starting...
INFO jodd.madvoc.Madvoc - Madvoc web application: jodd.example.AppWebApplication
INFO jodd.madvoc.Madvoc - Loading Madvoc parameters from: /madvoc.props
INFO jodd.madvoc.Madvoc - Configuring Madvoc using default automagic configurator
INFO jodd.madvoc.config.AutomagicMadvocConfigurator - Madvoc configured in 109 ms. Total actions: 1
INFO jodd.madvoc.Madvoc - Madvoc is up and running.
Go to /index.html
page and you will see in the console that
fooService
field is initialized, meaning that integration and injection
works perfectly!
LocalRunner
As you've noticed, our business layer is decoupled from the web layer. Good practice at this time is to build simple console-mode application where we can play with the application services without starting the Tomcat! Something like this:
public class LocalRunner { public static void main(String[] args) { AppCore appCore = new AppCore(); appCore.start(); PetiteContainer pc = appCore.getPetite(); FooService fooService = (FooService) pc.getBean("fooService"); System.out.println(fooService); appCore.stop(); } }
If you run this, you will see that we can reach services from the command line application, out of container scope. Nice!
Load parameters
Now back to the Petite container configuration. Usually, some of our Petite beans should be
configured by properties files stored on the classpath. For this
example, we will put all parameters into several properties files on
classpath root: app.props
, app-db.props
and so on. (Again, we are
using Jodd super properties, props.) Lets modify initPetite()
method:
public class AppCore { ... void initPetite() { petite = new PetiteContainer(); // automatic configuration AutomagicPetiteConfigurator pcfg = new AutomagicPetiteConfigurator(); pcfg.setIncludedEntries(this.getClass().getPackage().getName() + ".*"); pcfg.configure(petite); // load parameters Props appProps = new Props(); appProps.loadSystemProperties("sys"); appProps.loadEnvironment("env"); PropsUtil.loadFromClasspath(appProps, "/app*.prop*"); petite.defineParameters(appProps); // add appCore to Petite (and resolve parameters) petite.addBean("app", this); } ... }
Here we have many things. We first load system and environment variables
into props. The we load all /app*.prop*
files from the classpath. This
includes both Jodd props and Java properties! All this properties will
be loaded from the files and injected into the beans!
Furthermore, we added a nice trick: AppCore
instance is added to the
container. This way we can refer the AppCore
instance in the
configuration with
properties named as "app.*
".
Don't forget to configure your IDE to copy *.props
files to the
output folder!
Enable session scope beans
Even if not yet sure if we gonna needed, we can enable Petite to use
session scoped beans. The following listeners have to be registered in
web.xml
:
... <!--listeners--> <listener> <listener-class>jodd.servlet.RequestContextListener</listener-class> </listener> ...
Once when we actually add some session scoped bean, we will not be able to run application outside of servlet container (as we don't have session in the command line). To solve this problem, we need first to detect if we are running under the servlet container. There are many ways to do so, one is to detect if there is a WEB-INF string in the file path of some file on the classpath. Next step is to replace the session scope with, e.g. prototype scope:
public class AppCore { protected boolean isWebApplication; void initPetite() { petite = new PetiteContainer(); if (isWebApplication == false) { petite.registerScope(SessionScope.class, new ProtoScope()); } ... } }
Recapitulation
We learned how it is easy to create Petite container and register beans. For web applications, such one we have in this example, there is a simple way to integrated Petite container with Madvoc. Once integrated, injection of container services into web actions is just mater of declaration. Finally, we have learned how to load Petite properties and how to enable configuration for session scoped beans.