Custom Spring Bean Parameters - java

I'm using the Spring Akka example posted on activator to create Spring managed bean actors. This is the code I'm currently using including a demo class:
#Component
class Test extends UntypedActor {
#Autowired
protected ObjectMapper objectMapper;
protected final Account account;
protected final Order order;
public Test(Account account, Order order) {
this.account = account;
this.order = order;
}
#Override
public void onReceive(Object message) throws Exception {
if (message instanceof SomeCommand) {
// Do something using the order and the account;
} else if (message instanceof FooCommand) {
// More stuff
}
}
}
#Component
public class SpringExtension extends AbstractExtensionId<SpringExtensionImpl> implements ExtensionIdProvider {
#Autowired
private ApplicationContext applicationContext;
#Override
public SpringExtensionImpl createExtension(ExtendedActorSystem system) {
return applicationContext.getBean(SpringExtensionImpl.class);
}
#Override
public ExtensionId<? extends Extension> lookup() {
return applicationContext.getBean(SpringExtension.class);
}
}
#Component
public class SpringExtensionImpl implements Extension {
#Autowired
private ApplicationContext applicationContext;
public Props props(String actorBeanName) {
return Props.create(SpringActorProducer.class, applicationContext, actorBeanName);
}
}
public class SpringActorProducer implements IndirectActorProducer {
private final ApplicationContext applicationContext;
private final String actorBeanName;
public SpringActorProducer(ApplicationContext applicationContext, String actorBeanName) {
this.applicationContext = applicationContext;
this.actorBeanName = actorBeanName;
}
#Override
public Actor produce() {
return (Actor) applicationContext.getBean(actorBeanName);
}
#Override
public Class<? extends Actor> actorClass() {
return (Class<? extends Actor>) applicationContext.getType(actorBeanName);
}
}
Now my question is, how do instantiate an actor with custom constructor arguments. I have thought about using a factory or setter methods but I don't think this is an option since the underlying Actor class is not accessible I believe. Any input on this matter is greatly appreciated. If something is now clear, please post a comment.
PS. If you believe my there is an error in my code or there is a better way of going about it, please do tell me! I have little experience with Spring and Akka combined so any advice is appreciated.

You could pass the additional arguments as varargs (Object...) to SpringExtensionImpl and SpringActorProducer. So your code would look like this:
#Component
public class SpringExtensionImpl implements Extension {
#Autowired
private ApplicationContext applicationContext;
public Props props(String actorBeanName, Object... args) {
return (args != null && args.length > 0) ?
Props.create(SpringActorProducer.class,
applicationContext,
actorBeanName, args) :
Props.create(SpringActorProducer.class,
applicationContext,
actorBeanName);
}
}
public class SpringActorProducer implements IndirectActorProducer {
private final ApplicationContext applicationContext;
private final String actorBeanName;
private final Object[] args;
public SpringActorProducer(ApplicationContext applicationContext, String actorBeanName) {
this.applicationContext = applicationContext;
this.actorBeanName = actorBeanName;
this.args = null;
}
public SpringActorProducer(ApplicationContext applicationContext, String actorBeanName, Object... args) {
this.applicationContext = applicationContext;
this.actorBeanName = actorBeanName;
this.args = args;
}
#Override
public Actor produce() {
return args == null ?
(Actor) applicationContext.getBean(actorBeanName):
(Actor) applicationContext.getBean(actorBeanName, args);
}
#Override
public Class<? extends Actor> actorClass() {
return (Class<? extends Actor>) applicationContext.getType(actorBeanName);
}
}
You can then create your Test actor like this:
SpringExtensionImpl springExtensionImpl;
actorSystem.actorOf(springExtensionImpl.create(Test.class, account, order));

Related

Create context with Java reflection and invoke method

Exception class
public BusinessException(ErrorCodeEnum errorCodeEnum) {
super(errorCodeEnum.getMessage());
this.errorCodeEnum = errorCodeEnum;
this.errorMessage = errorCodeEnum.getMessage();
}
ErrorCodeEnum class
public String getMessage() {
MessageUtil messageUtil = SpringContextUtil.getBean(MessageUtil.class);
return messageUtil.get(this.message);
}
SpringContextUtil class
public static Object getBean(String name) {
return appContext.getBean(name);
}
public static <T> T getBean(Class<T> clazz) {
return appContext.getBean(clazz);
}
public static synchronized void setContext(ApplicationContext applicationContext) {
appContext = applicationContext;
}
#Override
#Autowired
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
setContext(applicationContext);
}
To test an exception of BusinessException class
I need to invoke getBean method.
what is the correct way to invoke the method?

Spring ApplicationContext.getBean returns wrong class

I've been beating my head over this and I just can't figure out what's wrong.
I have a Spring app which uses ApplicationContext.getBean() to retrieve 2 similar classes. I'm getting the wrong instance class from the bean lookup.
Here's ApplicationContext class:
public class DomainRegistryCab {
private static ApplicationContext applicationContext;
private static ApplicationContext createApplicationContext() {
return new AnnotationConfigApplicationContext( CaBridgeDomainServiceConfig.class );
}
public static CertificateProductApplicationService certificateProductAppService() {
var service = BeanFactoryAnnotationUtils.qualifiedBeanOfType(
applicationContext().getAutowireCapableBeanFactory(),
CertificateProductApplicationService.class,
CaBridgeDomainServiceConfig.CERTIFICATE_PRODUCT_APP_SERVICE);
// var service = applicationContext().getBean(
// CaBridgeDomainServiceConfig.CERTIFICATE_PRODUCT_APP_SERVICE,
// CertificateProductApplicationService.class);
// var service = applicationContext().getBean(CertificateProductApplicationService.class);
validateDataSourceIs(DataSource.ProductDataStore, service.dataSource());
return service;
}
public static CertificateProgramApplicationService certificateProgramAppService() {
var service = BeanFactoryAnnotationUtils.qualifiedBeanOfType(
applicationContext().getAutowireCapableBeanFactory(),
CertificateProgramApplicationService.class,
CaBridgeDomainServiceConfig.CERTIFICATE_PROGRAM_APP_SERVICE);
// var service = applicationContext().getBean(
// CaBridgeDomainServiceConfig.CERTIFICATE_PROGRAM_APP_SERVICE,
// CertificateProgramApplicationService.class);
// service = applicationContext().getBean(CertificateProgramApplicationService.class);
validateDataSourceIs(DataSource.ProgramDataStore, service.dataSource());
return service;
}
Here is CaBridgeDomainServiceConfig:
#Configuration
#ComponentScan(basePackageClasses = { HibernateConfigurationMarker.class })
public class CaBridgeDomainServiceConfig {
public static final String CERTIFICATE_PRODUCT_APP_SERVICE = "certificateProductAppService";
public static final String CERTIFICATE_PROGRAM_APP_SERVICE = "certificateProgramAppService";
#Bean(name= CERTIFICATE_PRODUCT_APP_SERVICE)
public CertificateProductApplicationService certificateProductAppService() {
return new CertificateProductApplicationServiceCabImpl();
}
#Bean(name= CERTIFICATE_PROGRAM_APP_SERVICE)
public CertificateProgramApplicationService certificateProgramAppService() {
return new CertificateProgramApplicationServiceCabImpl();
}
}
public interface CertificateProductApplicationService extends CertificateApplicationService {
}
public interface CertificateProductApplicationService extends CertificateApplicationService {
}
public interface CertificateApplicationService {
}
Using the above classes if I call DomainRegistryCab.certificateProductAppService() I get an instance of CertificateProgramApplicationService not CertificateProductApplicationService.
I get similar results if I use this method:
public static CertificateProductApplicationService certificateProductAppService() {
var service = applicationContext().getBean(
CaBridgeDomainServiceConfig.CERTIFICATE_PRODUCT_APP_SERVICE,
CertificateProductApplicationService.class);
validateDataSourceIs(DataSource.ProductDataStore, service.dataSource());
return service;
}
I've also tried having the #Bean methods return the implementation classes and the ApplicationContext().getBean() to request the implementation classes instead of the interfaces:
public class DomainRegistryCab {
private static ApplicationContext applicationContext;
private static ApplicationContext createApplicationContext() {
return new AnnotationConfigApplicationContext( CaBridgeDomainServiceConfig.class );
}
public static CertificateProductApplicationService certificateProductAppService() {
var service = applicationContext().getBean(CertificateProductApplicationServiceCabImpl.class);
validateDataSourceIs(DataSource.ProductDataStore, service.dataSource());
return service;
}
public static CertificateProgramApplicationService certificateProgramAppService() {
var service = applicationContext().getBean(CertificateProgramApplicationServiceCabImpl.class);
validateDataSourceIs(DataSource.ProgramDataStore, service.dataSource());
return service;
}
public static ApplicationContext applicationContext() {
if (applicationContext == null)
applicationContext = createApplicationContext();
return applicationContext;
}
}
#ComponentScan(basePackageClasses = { HibernateConfigurationMarker.class })
public class CaBridgeDomainServiceConfig {
#Bean(name= CERTIFICATE_PRODUCT_APP_SERVICE)
public CertificateProductApplicationServiceCabImpl certificateProductAppService() {
return new CertificateProductApplicationServiceCabImpl();
}
#Bean(name= CERTIFICATE_PROGRAM_APP_SERVICE)
public CertificateProgramApplicationServiceCabImpl certificateProgramAppService() {
return new CertificateProgramApplicationServiceCabImpl();
}
}
This code results in spring not finding the implementation classes at all:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'cmb.cabridge.application.cert.CertificateProductApplicationServiceCabImpl' available
I was eventually able to get things to work using the applicationContext().getBean("beanName", CertificateProductApplicationService.class). The problem was deeper in my code in the CertificateProductApplicationServiceCabImpl class which used and returned the wrong datasource.

Test Spring/SpringBoot without Application

I want to write integration tests that will have to use Spring Framework and a custom JPA provider. Straightforward answer as I thought would be to create a test class and annotate it as follows:
#RunWith(SpringRunner.class)
#SpringBootTest
public class Test { ...
Hoping for the all of the default auto-configuration required to happen on its own. But it doesn't the error is:
java.lang.IllegalStateException: Unable to find a #SpringBootConfiguration, you need to use #ContextConfiguration or #SpringBootTest(classes=...) with your test
Can I avoid creating the:
#SpringBootApplication
public class TestApp { ...
and only use src/test/java folder and provide configuration with "#SpringBootTest(classes=...)"? What kind of configuration class do I need then?
I just got the problem let me go back to begining;
First add a configuration class ApplicationContainer
public final class ApplicationContainer {
private static volatile ApplicationContainer singleton;
private ApplicationContext context;
private ApplicationContainer()
{
}
public static ApplicationContainer getInstance()
{
if(ApplicationContainer.singleton == null)
{
synchronized(ApplicationContainer.class)
{
if(ApplicationContainer.singleton == null)
{
ApplicationContainer.singleton = new ApplicationContainer();
}
}
}
return ApplicationContainer.singleton;
}
public void setApplicationContext(ApplicationContext context)
{
this.context = context;
}
public <T> T getBean(Class<T> requiredType)
{
if(this.context == null)
{
throw new IllegalStateException("ApplicationContainer is not started");
}
return this.context.getBean(requiredType);
}
public void start()
{
if(this.context == null)
{
this.context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
}
}
public void stop()
{
if(this.context == null)
{
return;
}
if(this.context instanceof AnnotationConfigApplicationContext)
{
((AnnotationConfigApplicationContext)this.context).close();
}
this.context = null;
}
#Configuration
#ComponentScan("com.domain.package")
public static class ApplicationConfig
{
}}
And change your test class's like that:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {ApplicationContainer.ApplicationConfig.class})
public class YourTestIT {
#Autowired
private ApplicationContext applicationContext;
#Before
public void up() {
ApplicationContainer.getInstance().setApplicationContext(this.applicationContext);
}
#After
public void down() {
ApplicationContainer.getInstance().stop();
}
//test cases
}
then you can #Autowired your repository classes and use directly also you should add IT extension your test class name.

Spring: how to get bean from application context which implements generic interface?

I have interface:
public interface CommandHandler<T extends Command> {
void handle(T command);
}
There are commands which implement Command marker interface
public class CreateCategoryCommand implements Command {
}
public class CreateCategoryCommand implements Command {
}
For each command I have apropriate CommandHandler implementations:
#Component
public class CreateProductCommandHandler implements CommandHandler<CreateProductCommand> {
#Override
public void handle(CreateProductCommand command) {
System.out.println("Command handled");
}
}
#Component
public class CreateCategoryCommandHandler implements CommandHandler<CreateCategoryCommand> {
#Override
public void handle(CreateCategoryCommand command) {
}
}
Question:
I have command bus
#Component
public class SimpleCommandBus implements CommandBus {
#Autowired
private ApplicationContext context;
#Override
public void send(Command command) {
// OF COURSE, THIS NOT COMPILED, BUT I NEED SOMETHING LIKE THIS
CommandHandler commandHandler = context.getBean(CommandHandler<command.getClass()>)
}
}
How to get bean from application context which implements generic interface with particular type?
Way I solved it:
#Component
public class SimpleCommandBus {
private final Logger logger;
private final Set<CommandHandler<?>> handlers;
private final Map<Class<?>, CommandHandler<?>> commandHandlersCache = new WeakHashMap<>();
public SimpleCommandBus(Logger logger, Set<CommandHandler<?>> handlers) {
this.logger = logger;
this.handlers = handlers;
}
public void send(Command command) {
CommandHandler<Command> commandHandler = getCommandHandler(command);
if (commandHandler != null)
commandHandler.handle(command);
else
logger.error("Can't handle command " + command);
}
#SuppressWarnings("unchecked")
private <C extends Command> CommandHandler<C> getCommandHandler(C command) {
Class<?> commandType = command.getClass();
if (commandHandlersCache.containsKey(commandType))
return (CommandHandler<C>) commandHandlersCache.get(commandType);
for (CommandHandler<?> haandler : handlers) {
Class<?> supportedCommandType = resolveTypeArgument(haandler.getClass(), CommandHandler.class);
if (commandType.isAssignableFrom(supportedCommandType)) {
commandHandlersCache.put(commandType, haandler);
return (CommandHandler<C>) haandler;
}
}
commandHandlersCache.put(commandType, null);
return null;
}
}

Use spring beans from Serializable objects

I need to execute task on remote machine.
This task is dummy Runnable or Callable and Serializable to be transferred to remote host, deserialized and executed there.
I need to use spring beans from that task to execute it on remote machine.
What could be the elegant way to 'serialize' bean name when task is serialized on client machine and 'deserialize' real bean while deserialization on remote machine?
Any other solutions?
private static class MyCommand implements Callable<String>, Serializable {
private static final long serialVersionUID = 8980820796677215627L;
private transient SpringBean springBean;
private String bar;
public InitDoneRemoteCommand(SpringBean springBean, String bar) {
this.springBean = springBean;
this.bar = bar;
}
#Override
public String call() {
return springBean.foo(bar);
}
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeObject(getBeanName(springBean));
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
springBean = getBean((String) in.readObject());
}
}
SpringContext .java
#Resource
public class SpringContext implements ApplicationContextAware, BeanPostProcessor, BundleContextAware, ServiceListener {
private static ApplicationContext applicationContext;
private static BundleContext bundleContext;
private static Map<Object, String> springBeanToName = synchronizedMap(new WeakHashMap<Object, String>());
private static Map<String, ServiceReference> osgiNameToServiceReference = synchronizedMap(new WeakHashMap<String, ServiceReference>());
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static BundleContext getBundleContext() {
return bundleContext;
}
#SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
ServiceReference ref = osgiNameToServiceReference.get(name);
if (ref != null)
return (T) bundleContext.getService(ref);
return (T) applicationContext.getBean(name);
}
public static String getBeanName(Object bean) {
if (isOsgiBean(bean))
return getOsgiBeanName(bean);
return springBeanToName.get(bean);
}
public static boolean isOsgiBean(Object bean) {
return bean instanceof ImportedOsgiServiceProxy || bean instanceof ServiceReferenceProxy || bean instanceof ServiceReference;
}
public static String getOsgiBeanName(Object proxy) {
if (proxy == null)
return null;
ServiceReference serviceReference = null;
if (proxy instanceof ImportedOsgiServiceProxy)
serviceReference = ((ImportedOsgiServiceProxy) proxy).getServiceReference().getTargetServiceReference();
else if (proxy instanceof ServiceReferenceProxy)
serviceReference = ((ServiceReferenceProxy) proxy).getTargetServiceReference();
else if (proxy instanceof ServiceReference)
serviceReference = ((ServiceReference) proxy);
if (serviceReference != null)
return (String) serviceReference.getProperty(OSGI_BEAN_NAME_PROPERTY);
throw new IllegalArgumentException(proxy.toString());
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
springBeanToName.put(bean, beanName);
return bean;
}
#Override
public void serviceChanged(ServiceEvent event) {
ServiceReference ref = event.getServiceReference();
String name = getOsgiBeanName(ref);
if (event.getType() == ServiceEvent.REGISTERED)
osgiNameToServiceReference.put(name, ref);
else if (event.getType() == ServiceEvent.UNREGISTERING)
osgiNameToServiceReference.remove(name);
}
#Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
SpringContext.applicationContext = context;
}
#Override
public void setBundleContext(BundleContext bundleContext) {
SpringContext.bundleContext = bundleContext;
bundleContext.addServiceListener(this);
}
}
If you have access to the ApplicationContext you can ask it to create the instance for you, which will e.g. enable autowiring:
appContext.getAutowireCapableBeanFactory().createBean(
beanClass,
AbstractBeanDefinition.AUTOWIRE_BY_TYPE,
true)
A more elegant way would be to annotate the class with #Configurable, described here.

Categories