Continuation of topic
Jersey 2 + HK2 - @ApplicationScoped not working.
I already know, how to bind classes in order to @Inject
them properly.
Do you have any ideas, how to automize this process? Putting every single service in bind
statements seems like very bad smell in my application.
After using Google's Guice for a number of years, I am accustomed to the availability of a Just-In-Time binder, allowing the injection of arbitrary types without requiring any upfront configuration.
I too found the idea of having to explicitly bind every service to be a bad code smell. I'm also not crazy about the need to use a special build step and the added initialization code for the populator.
So I came up with the following JustInTimeResolver
implementation:
/**
* Mimic GUICE's ability to satisfy injection points automatically,
* without needing to explicitly bind every class, and without needing
* to add an extra build step.
*/
@Service
public class JustInTimeServiceResolver implements JustInTimeInjectionResolver {
@Inject
private ServiceLocator serviceLocator;
@Override
public boolean justInTimeResolution( Injectee injectee ) {
final Type requiredType = injectee.getRequiredType();
if ( injectee.getRequiredQualifiers().isEmpty() && requiredType instanceof Class ) {
final Class<?> requiredClass = (Class<?>) requiredType;
// IMPORTANT: check the package name, so we don't accidentally preempt other framework JIT resolvers
if ( requiredClass.getName().startsWith( "com.fastmodel" )) {
final List<ActiveDescriptor<?>> descriptors = ServiceLocatorUtilities.addClasses( serviceLocator, requiredClass );
if ( !descriptors.isEmpty() ) {
return true;
}
}
}
return false;
}
}
With this in my project, I simply added the following to my binder in my Jersey application configuration:
bind( JustInTimeServiceResolver.class ).to( JustInTimeInjectionResolver.class );
and I get automatic binding creation like I did in Guice.
I would suggest first looking here: Automatic Service Population.
The basic process is to use @Service annotations on your classes and use the JSR-269 (APT) processor (Metadata Generator) at build time. Doing so will add some metadata to your jar files (normally under META-INF/hk2-locator/default).
You can then make sure these services get picked up automatically rather than having to do all those pesky binds by using a Populator which you get from the Dynamic Configuration Service which is available in every ServiceLocator.
The pseudo-code would be something like this:
public void populate(ServiceLocator locator) throws Exception {
DynamicConfigurationService dcs = locator.getService(DynamicConfigurationService.class);
Populator populator = dcs.getPopulator();
populator.populate(new ClasspathDescriptorFileFinder(getClass().getClassLoader()));
}
In the above code the ClasspathDescriptorFileFinder is used to search through the classpath to find the metadata. Other strategies could be used in environments like OSGi.
IMO this is a much better way to add services rather than doing all the binds by yourself.
I Have a sugestion that solved my problem here, i've tried proposed solution and not worked here. In my solution it's necessary annotate each class with @MyInjectable annotation.
1-Create an Annotation
@Retention(RUNTIME)
@Target(ElementType.TYPE)
public @interface MyInjectable {
}
2-Create a AbstractBinder implementation
public class MyApplicationBinder extends AbstractBinder {
@Override
protected void configure() {
bindFactory(EMFFactory.class).to(EntityManagerFactory.class).in(Singleton.class);
bindFactory(EMFactory.class).to(EntityManager.class).in(RequestScoped.class);
bind(Environment.class).to(Environment.class);
scanAndBind("com.yourpackage.here");
}
private void scanAndBind(String packageName) {
try {
Class[] classes = getClasses(packageName);
for (Class<?> klazz:
classes) {
MyInjectable annotation = klazz.getAnnotation(MyInjectable.class);
if (annotation!= null) {
bind(klazz).to(klazz);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static Class[] getClasses(String packageName)
throws ClassNotFoundException, IOException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
assert classLoader != null;
String path = packageName.replace('.', '/');
Enumeration<URL> resources = classLoader.getResources(path);
List<File> dirs = new ArrayList<>();
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
dirs.add(new File(resource.getFile()));
}
ArrayList<Class> classes = new ArrayList<Class>();
for (File directory : dirs) {
classes.addAll(findClasses(directory, packageName));
}
return classes.toArray(new Class[classes.size()]);
}
private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
List<Class> classes = new ArrayList<Class>();
if (!directory.exists()) {
return classes;
}
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
assert !file.getName().contains(".");
classes.addAll(findClasses(file, packageName + "." + file.getName()));
} else if (file.getName().endsWith(".class")) {
classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
}
}
return classes;
}
}
3-Create a ResourceConfig
public class MyApplication extends ResourceConfig {
@Inject
public MyApplication(ServiceLocator locator) {
ServiceLocatorUtilities.enableImmediateScope(locator);
....
register(new MyApplicationBinder());
}
}
4-Configure properly in web.xml
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>br.com.solutiontrue.ws</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>your.package.name.MyApplication</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.resource.validation.disable</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>