I did try going through the following links
How to wire in a collaborator into a Jersey resource?
and
Access external objects in Jersey Resource class
But still i am unable to find a working sample which shows how to inject into a Resource class.
I am not using Spring or a web container.
My Resource is
package resource;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
#Path("/something")
public class Resource
{
#MyResource
Integer foo = null;
private static String response = "SampleData from Resource";
public Resource()
{
System.out.println("...constructor called :" + foo);
}
#Path("/that")
#GET
#Produces("text/plain")
public String sendResponse()
{
return response + "\n";
}
}
My Provider is
package resource;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
#Provider
public class MyResourceProvider implements InjectableProvider<MyResource, Integer>
{
#Override
public ComponentScope getScope()
{
return ComponentScope.PerRequest;
}
#Override
public Injectable getInjectable(final ComponentContext arg0, final MyResource arg1, final Integer arg2)
{
return new Injectable<Object>()
{
#Override
public Object getValue()
{
return new Integer(99);
}
};
}
}
My EndpointPublisher is
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.core.MediaType;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.container.grizzly.GrizzlyWebContainerFactory;
class EndpointPublisher
{
public static void main(final String[] args)
{
final String address = "http://localhost:8080/";
final Map<String, String> config = new HashMap<String, String>();
config.put("com.sun.jersey.config.property.packages", "resource");
try
{
GrizzlyWebContainerFactory.create(address, config);
System.out.println("server started ....." + address);
callGet();
}
catch (final Exception e)
{
e.printStackTrace();
}
}
public static void callGet()
{
Client client = null;
ClientResponse response = null;
client = Client.create();
final WebResource resource =
client.resource("http://localhost:8080/something");
response = resource.path("that")
.accept(MediaType.TEXT_XML_TYPE, MediaType.APPLICATION_XML_TYPE)
.type(MediaType.TEXT_XML)
.get(ClientResponse.class);
System.out.println(">>>> " + response.getResponseDate());
}
}
My annotation being
#Retention(RetentionPolicy.RUNTIME)
public #interface MyResource
{}
But when i execute my EndpointPublisher i am unable to inject foo!!
Your InjectableProvider is not implemented correctly. The second type parameter should not be the type of the field you are trying to inject - instead it should be the context - either java.lang.reflect.Type class or com.sun.jersey.api.model.Parameter class. In your case, you would use Type. So, your InjectableProvider implementation should look as follows:
package resource;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
import java.lang.reflect.Type;
#Provider
public class MyResourceProvider implements InjectableProvider<MyResource, Type> {
#Override
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}
#Override
public Injectable getInjectable(final ComponentContext arg0, final MyResource arg1, final Type arg2) {
if (Integer.class.equals(arg2)) {
return new Injectable<Integer>() {
#Override
public Integer getValue() {
return new Integer(99);
}
};
} else {
return null;
}
}
}
There is a helper class for per-request injectable providers (PerRequestTypeInjectableProvider) as well as singleton injectable providers (SingletonTypeInjectableProvider), so you can further simplify it by inheriting from that:
package resource;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.PerRequestTypeInjectableProvider;
#Provider
public class MyResourceProvider extends PerRequestTypeInjectableProvider<MyResource, Integer> {
public MyResourceProvider() {
super(Integer.class);
}
#Override
public Injectable<Integer> getInjectable(ComponentContext ic, MyResource a) {
return new Injectable<Integer>() {
#Override
public Integer getValue() {
return new Integer(99);
}
};
}
}
Note that for these helper classes the second type parameter is the type of the field.
And one more thing - the injection happens after the constructor is called, so the constructor of your resource will still print out ...constructor called :null, but if you change your resource method to return foo, you'll see the response you'll get will be 99.
This solution works well and I wanted to share what I found to enable CDI on jersey resources.
Here is the simplest bean ever :
package fr.test;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
#RequestScoped
public class Test {
private int i;
#PostConstruct
public void create() {
i = 6;
}
public int getI() {
return i;
}
}
In your resource class, we just inject this bean, as we would do in a any normal context :
package fr.test;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
#Path("/login")
public class LoginApi {
#Inject
private Test test;
#GET
#Produces("text/plain")
public String getIt() {
return "Hi there!" + test;
}
}
And here is the key. We define a Jersey "InjectionProvider" which will be responsible of beans' resolution :
package fr.test;
import javax.inject.Inject;
import java.lang.reflect.Type;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
import fr.xxxxxxxxxx.ApplicationBeans;
#Provider
public class InjectionProvider implements InjectableProvider<Inject, Type> {
public ComponentScope getScope() {
// CDI will handle scopes for us
return ComponentScope.Singleton;
}
#Override
public Injectable<?> getInjectable(ComponentContext context,
Inject injectAnno, Type t) {
if (!(t instanceof Class))
throw new RuntimeException("not injecting a class type ?");
Class<?> clazz = (Class<?>) t;
final Object instance = ApplicationBeans.get(clazz);
return new Injectable<Object>() {
public Object getValue() {
return instance;
}
};
}
}
InjectableProvider is typed with the kind of annotation we are handling, and the context type (here, normal java type)
ApplicationBeans is just a simple helper for bean resolution. Here is its content :
package fr.xxxxxxxxxx;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import fr.xxxxxxxxxxxxx.UnexpectedException;
/**
* Gives direct access to managed beans - Designed to be used from unmanaged code
*
* #author lgrignon
*
*/
#ApplicationScoped
public class ApplicationBeans
{
protected static ApplicationBeans instance;
#Inject
private BeanManager beanManager;
/**
* Gets instance
*
* #return Instance from managed environment
*/
public static ApplicationBeans instance()
{
if (instance == null)
{
BeanManager beanManager;
InitialContext ctx = null;
try
{
ctx = new InitialContext();
beanManager = (BeanManager)ctx.lookup("java:comp/BeanManager");
}catch(NamingException e)
{
try
{
beanManager = (BeanManager)ctx.lookup("java:app/BeanManager");
}catch(NamingException ne)
{
throw new UnexpectedException("Unable to obtain BeanManager.", ne);
}
}
instance = getBeanFromManager(beanManager, ApplicationBeans.class);
}
return instance;
}
/**
* Gets bean instance from context
*
* #param <T>
* Bean's type
* #param beanType
* Bean's type
* #param annotations
* Bean's annotations
* #return Bean instance or null if no
*/
public static <T> T get(final Class<T> beanType, Annotation... annotations)
{
return instance().getBean(beanType, annotations);
}
/**
* Gets bean instance from context
*
* #param <T>
* Bean's type
* #param beanType
* Bean's type
* #param annotations
* Bean's annotations
* #return Bean instance or null if no
*/
public <T> T getBean(final Class<T> beanType, Annotation... annotations)
{
return getBeanFromManager(beanManager, beanType, annotations);
}
#SuppressWarnings("unchecked")
private static <T> T getBeanFromManager(BeanManager beanManager, final Class<T> beanType, Annotation... annotations)
{
Set<Bean<?>> beans = beanManager.getBeans(beanType, annotations);
if (beans.size() > 1)
{
throw new UnexpectedException("Many bean declarations found for type %s (%s)", beanType.getSimpleName(), beansToString(beans));
}
if (beans.isEmpty())
{
throw new UnexpectedException("No bean declaration found for type %s", beanType.getSimpleName());
}
final Bean<T> bean = (Bean<T>)beans.iterator().next();
final CreationalContext<T> context = beanManager.createCreationalContext(bean);
return (T)beanManager.getReference(bean, beanType, context);
}
private static String beansToString(Collection<Bean<?>> beans)
{
String[] beansLabels = new String[beans.size()];
int i = 0;
for (final Bean<?> bean : beans)
{
beansLabels[i++] = bean.getName();
}
return Arrays.toString(beansLabels);
}
}
Hope this will help those who want to enable CDI injection in their Jersey resources.
Bye !
Related
I'm working on a Spring application and I'd like to know if there's any way I could specify in my configuration the path of an XML file, having it automatically unmarshalled into a Java object through JAXB (I may consider other libraries though) and then inject it into a bean.
A Google search yields different results but they seem more about injecting a marshaller/unmarshaller in your bean and then doing the work yourself (like this one https://www.intertech.com/Blog/jaxb-tutorial-how-to-marshal-and-unmarshal-xml/) and I'm more interested in delegating this boilerplate to Spring.
Thanks
You can implement your custom resource loader based on this article: Spicy Spring: Create your own ResourceLoader. It requires some assumptions:
Classes you want to load have all required annotation used by JAXB which allow deserialisation.
You can build JaxbContext using given list of classes.
You need to check yourself whether loaded class is what you expect.
Step 0 - create POJO
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "User")
#XmlAccessorType(XmlAccessType.FIELD)
public class User {
#XmlElement(name = "firstName")
private String firstName;
#XmlElement(name = "lastName")
private String lastName;
// getters, setters, toString
}
You need to predefine POJO model which will be loaded from XML files. Above example just present one class but it should be similar for all other POJO classes.
Step 1 - create unmarshaller
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
#Component
public class JaxbResourceUnmarshaller {
private JAXBContext context;
public JaxbResourceUnmarshaller() {
try {
context = JAXBContext.newInstance(User.class);
} catch (JAXBException e) {
throw new IllegalArgumentException(e);
}
}
public Object read(Resource resource) {
try {
Unmarshaller unmarshaller = context.createUnmarshaller();
return unmarshaller.unmarshal(resource.getInputStream());
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
}
Simple unmarshaller implementation where you need to create JAXBContext. You need to provide all root classes.
Step 2 - create class resource
import org.springframework.core.io.AbstractResource;
import java.io.IOException;
import java.io.InputStream;
public class ClassResource extends AbstractResource {
private final Object instance;
public ClassResource(Object instance) {
this.instance = instance;
}
public Object getInstance() {
return instance;
}
#Override
public String getDescription() {
return "Resource for " + instance;
}
#Override
public InputStream getInputStream() throws IOException {
return null;
}
}
I could not find any specific class which could allow to return POJO instance. Above class has simple job to transfer class from deserialiser to Spring bean. You can try to find better implementation or improve this one if needed.
Step 3 - create JAXB resource loader
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
public class JaxbResourceLoader implements ResourceLoader {
private static final String DB_URL_PREFIX = "jaxb:";
private final ApplicationContext applicationContext;
private final ResourceLoader delegate;
public JaxbResourceLoader(ApplicationContext applicationContext, ResourceLoader delegate) {
this.applicationContext = applicationContext;
this.delegate = delegate;
}
#Override
public Resource getResource(String location) {
if (location.startsWith(DB_URL_PREFIX)) {
JaxbResourceUnmarshaller unmarshaller = this.applicationContext.getBean(JaxbResourceUnmarshaller.class);
String resourceName = location.replaceFirst(DB_URL_PREFIX, "");
Resource resource = applicationContext.getResource("classpath:" + resourceName);
Object instance = unmarshaller.read(resource);
return new ClassResource(instance);
}
return this.delegate.getResource(location);
}
#Override
public ClassLoader getClassLoader() {
return this.delegate.getClassLoader();
}
}
In case resource definition starts from jaxb: let's try to handle it. In other case postpone to default implementation. Only classpath resources are supported.
Step 4 - register JAXB resource loader
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.Ordered;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;
#Component
public class ResourceLoaderBeanPostProcessor implements BeanPostProcessor, BeanFactoryPostProcessor, Ordered,
ResourceLoaderAware, ApplicationContextAware {
private ResourceLoader resourceLoader;
private ApplicationContext applicationContext;
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.resourceLoader);
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
this.resourceLoader = new JaxbResourceLoader(this.applicationContext, this.resourceLoader);
beanFactory.registerResolvableDependency(ResourceLoader.class, this.resourceLoader);
}
#Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
#Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
}
This is just a copy of register class from article with only some changes. Probably could be much improved with latest Spring version.
Step 5 - simple usage
Assume you have pojos/user.xml file in resource folder which looks like below:
<User>
<firstName>Rick</firstName>
<lastName>Bartez</lastName>
</User>
You can inject it into Spring context like below:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
#Configuration
public class JaxbAwareConfiguration {
#Bean
public AppOwner appOwner(ResourceLoader resourceLoader) {
ClassResource resource = (ClassResource) resourceLoader.getResource("jaxb:pojos/user.xml");
User user = (User) resource.getInstance();
return new AppOwner(user);
}
}
A little bit unpleasant is casting resource to ClassResource and instance to User class but it is a downside of this solution.
Is it possible to replace, augment or intercept java bean cascade validation using the JSR 303/380 specification for #Valid. Basically, I want to perform some additional processing for every cascaded validation using #Valid annotation.
Make sure you want it.
import org.springframework.beans.BeanUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
public class JSR303CollectionListValidator implements Validator {
private final Validator validator;
public JSR303CollectionListValidator(LocalValidatorFactoryBean localValidatorFactoryBean) {
this.validator = localValidatorFactoryBean;
}
#Override
public boolean supports(Class clazz) {
return clazz.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
ValidationUtils.invokeValidator(validator, target, errors);
if(!target.getClass().isPrimitive()){
PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(target.getClass());
for(PropertyDescriptor descriptor : propertyDescriptors){
if(List.class.isAssignableFrom(descriptor.getPropertyType())){
Method method = descriptor.getReadMethod();
try {
Object object = method.invoke(target);
if(!Objects.isNull(object)){
List typeOfObject = (List) object;
for(Object resource : typeOfObject){
validate(resource, errors);
}
}
} catch (IllegalAccessException | InvocationTargetException e) {
FieldError error = new FieldError(target.getClass().getSimpleName(), descriptor.getName()
, "bean validation fail");
errors.getFieldErrors().add(error);
}
}
}
}
}
}
register bean
#ControllerAdvice
public class WebDataBindHandler {
#Inject
private LocalValidatorFactoryBean localValidatorFactoryBean;
#InitBinder
void initBinder(WebDataBinder binder) {
binder.addValidators(new JSR303CollectionListValidator(localValidatorFactoryBean));
}
}
I am migrating from Jersey 1.19 to Jersey 2.25. I am not finding enough documentation to replace InjectableProvider
Can some one help me.
import java.lang.reflect.Type;
import java.util.List;
import java.util.Locale;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;
import org.glassfish.hk2.api.InjectionResolver;
import org.springframework.stereotype.Component;
import com.sun.jersey.api.core.HttpContext;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.server.impl.inject.AbstractHttpContextInjectable;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
#Provider
#Component
public class LocaleProvider extends AbstractHttpContextInjectable<Locale>
implements InjectionResolver<Context, Type> {
private final Locale swedish = new Locale("sv", "", "");
#Override
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}
#Override
public Injectable<Locale> getInjectable(ComponentContext ic, Context a, Type c) {
if (c.equals(Locale.class)) {
return this;
}
return null;
}
#Override
public Locale getValue(HttpContext c) {
final List<Locale> locales = c.getRequest().getAcceptableLanguages();
if (locales.isEmpty()) {
return Locale.US;
}
for (Locale locale : locales) {
if (locale.getLanguage().equals(swedish.getLanguage())) {
return swedish;
} else if (locale.getLanguage().equals(Locale.US.getLanguage())) {
return Locale.US;
}
}
// return english if no other supported language is found
return Locale.US;
}
}
Use an HK2 Factory. A helper abstract class is AbstractContainerRequestValueFactory, which give you easy access to the ContainerRequest. You can get the acceptable languages from that
public class LocaleFactory
extends AbstractContainerRequestValueFactory<Local> {
#Override
public Locale provide() {
ContainerRequest cr = getContainerRequest();
}
}
Then you need to register it. If you are using a ResourceConfig you can do
public AppConfig extends ResourceConfig {
public AppConfig() {
register(new AbstractBinder() {
#Override
public void configure() {
bindFactory(LocalFactory.class)
.to(Locale.class).in(RequestScoped.class);
}
});
}
}
See Also:
Dependency injection with Jersey 2.0
AbstractContainerRequestValueFactory class has been removed, since Jersey 2.26 version. Alternatively, we can use org.glassfish.hk2.api.Factory to inject custom instance values which can be retrieved using #Context
Dependency
add jersey-hk2 dependency dependency in the classpath org.glassfish.jersey.inject:jersey-hk2
Factory class
Define the factory to construct the value to inject.
import org.glassfish.hk2.api.Factory;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;
import java.util.List;
import java.util.Locale;
#Provider
public class LocaleFactory implements Factory<Locale> {
private final Locale swedish = new Locale("sv", "", "");
#Context
private ContainerRequestContext ctx;
#Override
public Locale provide() {
final List<Locale> locales = ctx.getAcceptableLanguages();
if (locales.isEmpty()) {
return Locale.US;
}
for (Locale locale : locales) {
if (locale.getLanguage().equals(swedish.getLanguage())) {
return swedish;
} else if (locale.getLanguage().equals(Locale.US.getLanguage())) {
return Locale.US;
}
}
// return english if no other supported language is found
return Locale.US;
}
#Override
public void dispose(Locale instance) {/**Noop**/}
}
Registering the factory class
Registering LocaleFactory for the type Locale with the RequestScoped(for every request the LocaleFactory#provide method will be called)
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.process.internal.RequestScoped;
import org.glassfish.jersey.server.ResourceConfig;
import javax.ws.rs.ext.Provider;
import java.util.Locale;
#Provider
class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
register(new AbstractBinder() {
#Override
protected void configure() {
bindFactory(LocaleFactory.class)
.to(Locale.class)
.in(RequestScoped.class);
}
});
}
}
On this moment I'm dealing with a tricky problem. For my API I was hoping to use Play and Akka actor. The problem I'm having is that every object I try to inject in my Actor remains null. One solution is to inject this object in the controller and than pass it on to my actor but this is not what I want to do. I want my object only on the place where I need it.
package actors;
import actors.Messages.GetAanleverAfspraakById;
import akka.actor.UntypedActor;
import model.domain.AanleverAfspraakDO;
import play.db.jpa.JPAApi;
import javax.inject.Inject;
import javax.persistence.Query;
import java.util.Collection;
/**
* Created by harms.h on 22-03-2016.
*/
public class AfspraakActor extends UntypedActor {
#Inject
private JPAApi api;
#Override
public void onReceive(Object message) throws Exception {
if(message instanceof GetAanleverAfspraakById){
final AanleverAfspraakDO aanleverAfspraakDO = this.getAanleverAfspraakDO(((GetAanleverAfspraakById) message).getId());
getSender().tell(aanleverAfspraakDO, getSelf());
}
else{
unhandled(message);
}
}
private AanleverAfspraakDO getAanleverAfspraakDO(int id){
final AanleverAfspraakDO aanleverAfspraakDO = api.withTransaction(() -> {
final Query query = api.em().createNamedQuery("findbyid").setParameter("id", id);
final Collection<AanleverAfspraakDO> resultSet = query.getResultList();
final AanleverAfspraakDO result = resultSet.iterator().next();
return result;
});
return aanleverAfspraakDO;
}
}
What am I doing wrong here?
For now I used the following code after a long search on the internet
this.api = Play.current().injector().instanceOf(JPAApi.class);
I'm not sure if this is a clean solution what do you guys think?
Play 2.5: Create the following class:
import javax.inject.Inject;
import akka.actor.Actor;
import akka.actor.IndirectActorProducer;
import akka.actor.UntypedActor;
import play.api.Play;
public class GenericDependencyInjector implements IndirectActorProducer {
final Class<? extends UntypedActor> actorClass;
#Inject
public GenericDependencyInjector(Class<? extends UntypedActor> actorClass) {
this.actorClass = actorClass;
}
#Override
public Class<? extends Actor> actorClass() {
return actorClass;
}
#Override
public Actor produce() {
return Play.current().injector().instanceOf(actorClass);
}
}
Then when you create the actor, pass the GenericDependencyInjector along with your Service Interface:
final Props props = Props.create(GenericDependencyInjector.class, JPAApi.class);
context().actorOf(props);
Now you should be able inject your services into the actor with ease.
I have this situation:
I have one interface Service which aggregates all service interfaces. So for example if I have two interfaces ILoginService1 and ILoginService2 the Service interface looks like this
Service extends ILoginService1,ILoginService2.
I need this interface to be accessible in a given context like this:
service.login();
This is my solution (something similar to http://artofsoftwarereuse.com/tag/dynamic-proxy/):
I create one annotation ServiceFacade, which I put on Service interface, then I have BeanPostProcessor in which I create DynamicProxy for the Service interface.
But the problem is that Service interface isn't pick up from spring component scan, even in the case I put #Component on it, but other components are put in Spring container.
How can I fix my solution so far or I'm missing something or is there other solutions?
Here is source code:
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
<context:component-scan base-package="org.finki.auction.ui.application"/>
<context:component-scan base-package="org.finki.auction.services"/>
</beans>
Annotation:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface ServiceFacade{}
Invocation Handler for Dynamic Proxy:
/**
*
*/
package org.finki.auction.services;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
/**
*
*/
#Component("serviceLayer")
public class ServiceLayer implements InvocationHandler, ApplicationContextAware
{
private static ApplicationContext applicationContext = null;
private static Map<String, String> serviceMap = new HashMap<>();
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
Object result;
try
{
String searchKey = method.getName();
String beanName = serviceMap.get(searchKey);
Object methodObject = applicationContext.getBean(beanName);
result = method.invoke(methodObject, args);
} catch (InvocationTargetException e)
{
throw e.getTargetException();
} catch (Exception e)
{
throw new RuntimeException("unexpected invocation exception: " + e.getMessage());
}
return result;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
ServiceLayer.applicationContext = applicationContext;
Map<String, Object> beans = applicationContext.getBeansWithAnnotation(Service.class);
for (Map.Entry<String, Object> entryBean : beans.entrySet())
{
String beanName = entryBean.getKey();
Object beanObject = entryBean.getValue();
Method[] beanMethods = beanObject.getClass().getDeclaredMethods();
for (Method bMethod : beanMethods)
{
serviceMap.put(bMethod.getName(), beanName);
}
}
}
}
BeanPostProcessor class:
/**
*
*/
package org.finki.auction.services.annotation;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import org.finki.auction.services.Service;
import org.finki.auction.services.ServiceLayer;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
*
*/
#Component("serviceFacadeProcessor")
public class ServiceFacadeProcessor implements BeanPostProcessor, ApplicationContextAware
{
private static ApplicationContext applicationContext = null;
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
{
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
{
Class<?> clz = bean.getClass();
Class<?>[] tmpInterfaces = clz.getInterfaces();
System.out.println("ServiceFacadeProcessor : " + bean);
if (tmpInterfaces != null && tmpInterfaces.length == 1
&& tmpInterfaces[0].isAnnotationPresent(ServiceFacade.class))
{
System.out.println("Find serviceFacade >>>>");
Class<?>[] interfaces = Arrays.copyOf(tmpInterfaces, tmpInterfaces.length + 1);
interfaces[tmpInterfaces.length] = Service.class;
ClassLoader cl = bean.getClass().getClassLoader();
ServiceLayer serviceLayerBean = applicationContext.getBean("serviceLayer", ServiceLayer.class);
Object t = Proxy.newProxyInstance(cl, interfaces, serviceLayerBean);
System.out.println("Find serviceFacade <<<<");
return t;
}
return bean;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
ServiceFacadeProcessor.applicationContext = applicationContext;
}
}
So, my problem is not the configuration, my problem is how to attach Service interface to spring container in order to be caught by BeanPostProcessor and create dynamic proxy for it. It's is my solution so far maybe I'm missing something, but if someone have better way doing it, just let me now.
Thanks in advance
Solution:
/**
*
*/
package org.finki.auction.services.annotation;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import org.finki.auction.services.Service;
import org.finki.auction.services.ServiceLayer;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* #author
*
*/
#Component
public class ServiceFactoryBean implements FactoryBean<Service>, ApplicationContextAware
{
private static ApplicationContext applicationContext = null;
#Override
public Service getObject() throws Exception
{
Class<?>[] tmpInterfaces = Service.class.getInterfaces();
Class<?>[] interfaces = Arrays.copyOf(tmpInterfaces, tmpInterfaces.length + 1);
interfaces[tmpInterfaces.length] = Service.class;
ServiceLayer serviceLayerBean = applicationContext.getBean("serviceLayer", ServiceLayer.class);
ClassLoader cl = serviceLayerBean.getClass().getClassLoader();
Object t = Proxy.newProxyInstance(cl, interfaces, serviceLayerBean);
return (Service) t;
}
#Override
public Class<?> getObjectType()
{
return Service.class;
}
#Override
public boolean isSingleton()
{
return true;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
ServiceFactoryBean.applicationContext = applicationContext;
}
}
Also need to delete BeanPostProcessor and annotation.
I run into something similar and believe you can get your scenario working using Spring's Java Configuration feature.
#Configuration
public class ServiceConfiguration {
// you can wire your service1 and service2 here
#Bean
Service service() {
// create and return dynamic proxy here
}
}
This way you will end up with a bean of type 'Service' and name 'service' which will be your dynamic proxy with invocation handler etc.
I'm sure Java Configuration will not limit you to the approach outlined above (where you wire your service1 and service2 into the config) - methinks that is implementation detail.