This small library with no dependencies provides an easy fluent API to load xml beans from different sources via JAXB, giving some of them higher priority than to the others.
BeanLoader is useful if you are facing one of the following cases:
- You need to load a bean from a range of locations and stop, when you load the bean successfully. For example, when you need to load a bean from a file, but in case the file is missing you've got a classpath resource to use instead. Or another file etc.
- You need to reload the bean multiple times from the same resource.
Of course, you can just write a
fetchAndGetMyBean()
and call it over and over again, but why? - You want to use a
java.nio.file.WatchService
to load the bean every time it is changed externally. - You want to load multiple xml files of the same bean class from a directory by pattern, but you don't wanna fetch the list of files and iterate yourself.
<dependency>
<groupId>ru.yandex.qatools.beanloader</groupId>
<artifactId>beanloader</artifactId>
<version>2.0</version>
</dependency>
This code
Bean bean = (Bean) JAXB.unmarshal(this.getClass().getClassLoader().getResource("bean.xml"), Bean.class);
is equivalent to:
import static ru.qatools.beanloader.BeanLoader.load;
import static ru.qatools.beanloader.BeanLoaderStrategies.resource;
Bean bean = load(Bean.class).from(resource("bean.xml")).getBean();
And this
Bean bean = (Bean) JAXB.unmarshal(new File("/etc/bean.xml"), Bean.class);
to this:
Bean bean = load(Bean.class).from(file("/etc/bean.xml")).getBean();
This is the main function of BeanLoader. As it is said above, the goal is to simplify the choise of the source to load the xml bean from.
import static ru.qatools.beanloader.BeanLoaderStrategies.*;
import static ru.qatools.beanloader.BeanLoader.*;
BeanLoader<Bean> beanLoader = load(Bean.class)
.from(resource("bean.xml"))
.from(url("http://example.com?get-my-bean-dawg"))
.from(file("~/beans/bean.xml"))
.from(fileWithWatcher("/etc/beans/", "bean.xml"));
// load bean iterating over the given strategies
// until one of the returns a non-null bean
Bean bean = beanLoader.getBean();
makeSomeStuff(bean);
// reload the bean, if reloads are specified for any strategy
// returns the same object if no reloads are specified
bean = beanLoader.getBean();
makeAnotherStuff(bean);
Lets say you want to reload fresh data for your bean every time some your method is called. Of course you could just insert the load-n-unmarshal code right into your method but it may not be convenient for multiple reasons. With BeanLoader you can organize your code as follows:
import static ru.qatools.beanloader.BeanLoader.load;
import static ru.qatools.beanloader.BeanLoaderStrategies.*;
public class MyClass {
private final BeanLoader<Bean> beanLoader;
public MyClass(String filename) {
this.beanLoader = load(Bean.class).from(file(filename, true));
}
public void doSomeStuff() {
// do some other stuff
Bean bean = beanLoader.getBean();
// do some stuff with the bean
}
}
The second boolean parameter here indicates that a file will be reloaded
on every call to beanLoader.getBean()
method.
Now suppose realoading a file every time doesn't suit your needs
and you want to use a java.nio.file.WatchService
to change the loaded bean
only when it gets changed externally. Instead of implementing your own thread
with while(true)
loop you can just do it in a couple of lines:
import static ru.qatools.beanloader.BeanLoader.load;
import static ru.qatools.beanloader.BeanLoaderStrategies.*;
public class MyClass {
private final BeanLoader<Bean> beanLoader;
public MyClass(String directory, String filename) {
this.beanLoader = load(Bean.class).from(fileWithWatcher(directory, filename));
}
public void doSomeStuff() {
Bean bean = beanLoader.getBean();
// do some stuff with the bean
}
}
And that's it! The bean will be reloaded only when it is changed and you'll get
the fresh version of your bean on every call to beanLoader.getBean()
guaranteed.
Although remember that not every platform supports watching files.
Sometimes you need do something with the bean immediately when it changes.
For example, log it's contents or fire some message. This can be achieved
with the help of BeanChangeListener
interface. See the following code:
import static ru.qatools.beanloader.BeanLoader.load;
import static ru.qatools.beanloader.BeanLoaderStrategies.*;
public class MyClass implements BeanChangeListener<Bean> {
private final BeanLoader<Bean> beanLoader;
public MyClass(String directory, String filename) {
this.beanLoader = load(Bean.class).from(fileWithWatcher(directory, filename, this));
}
public void doSomeStuff() {
Bean bean = beanLoader.getBean();
// do some stuff with the bean
}
@Override
public void beanChanged(Path path, Bean newBean) {
System.out.println("Wow, new bean is here! Take a look: " + stringify(newBean));
}
}
Notice that if you lose a link to the beanLoader instance — the watcher thread may get stopped somewhere in the future when the garbage collection happens. That's a subject of discussion though maybe one can think of some better behaviour for when to stop the thread.
Imagine you do not need any beanLoader, all you want to do — is to be notified on every bean change.
Due to the reasons described above there is some one more class to fill the functionality gap.
Of course you can just take the example above, delete the doSomeStuff()
method and
yeah, it will work. Although you'll need to preserve the field which will be marked as unused by
any IDE. That may cause problems when another developer will delete the field by mistake and
then get severely surprised when the file watching thread stops. There is a special parameter
for this case though: you can pass true
as a forth parameter to fileWithWatcher()
method
and this way you'll prevent the watcher thread from stopping when the beanLoader
instance
get garbage collected. Anyway, using this parameter is equivalent to the code snippet below:
import static ru.qatools.beanloader.BeanWatcher.watchFor;
public class MyClass implements BeanChangeListener<Bean> {
public MyClass(String directory, String filename) throws IOException {
watchFor(Bean.class, directory, filename, this);
}
@Override
public void beanChanged(Path path, Bean newBean) {
System.out.println("Wow, new bean is here! Take a look: " + stringify(newBean));
}
}
Note that when you call the watchFor
method your listener will be invoked immediately
for the current version of the file. Because no one needs to be notified of changed without
reading the initial content first.
Also note that if the file content at some point will not match the bean class provided -
then the listener will be invoked with null
as the second argument. The same behavior
is expected when the watched file is deleted.
The same way you can also watch over multiple files, specifying them all by pattern:
import static ru.qatools.beanloader.BeanWatcher.watchFor;
public class MyClass implements BeanChangeListener<Bean> {
public MyClass(String directory) throws IOException {
watchFor(Bean.class, directory, "*-config.xml", this);
}
@Override
public void beanChanged(Path path, Bean newBean) {
System.out.println("Wow, new bean is here! Take a look: " + stringify(newBean));
}
}
Pattern should match the rules described in the java.nio.file.FileSystem.getPathMatcher()
method javadoc for the glob
syntax. And yeah, again: the listener will be immediately
invoked for all the the files that match that glob.
...or if you don't wanna watch for changes but just load all the beans in a directory once — you can just go:
BeanLoader.loadAll(Bean.class, directory, "*-config.xml", new BeanChangeListener<Bean>() {
@Override
public void beanChanged(Path path, Bean newBean) {
System.out.println("Bean " + path + " loaded: " + stringify(newBean));
}
});