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.
Related
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?
I have a problem with the annotated method in interface
public interface CsvImportService {
#Import
void importFile(Long userId, String organizationId, MultipartFile file, String charset) throws Exception;
}
Implementation of interface
#Service
public class CsvImportServiceImpl implements CsvImportService {
#Override
public void importFile(Long userId, String organizationId, MultipartFile file, String charset) {
...
}
}
I tried to handle it by Spring AOP
#Slf4j
#Aspect
#Component
public class ImportAspect {
#AfterReturning(pointcut = "#annotation(com.backend.annotations.Import)")
public void handleImport(JoinPoint joinPoint) throws Throwable {
LOGGER.info("handle");
}
}
But found out that annotation doesn't go to the impl, so I realized that I want to add the annotation to the realization method and started to write BPP
#Component
public class ImportAnnotationBeanPostProcessor implements BeanPostProcessor {
Map<String, Class<?>> map = new HashMap<>();
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Class<?> beanClass = bean.getClass();
if (CsvImportService.class.isAssignableFrom(beanClass)) {
map.put(beanName, beanClass);
}
return bean;
}
#SneakyThrows
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> beanClass = map.get(beanName);
if (beanClass != null) {
for (Method declaredMethod : CsvImportService.class.getDeclaredMethods()) {
if (declaredMethod.isAnnotationPresent(Import.class)) {
Method importMethod = beanClass.getDeclaredMethod(declaredMethod.getName(), declaredMethod.getParameterTypes());
//TODO Add annotation to method
}
}
}
return bean;
}
}
And didn't find a reflection method that adds an annotation to the method.
how am I supposed to do that?
when I use a aspect on an annotation, I cannot use AnnotationUtils.getAnnotation,
//here, I cannot not find PulsarListener
PulsarListener annotation = AnnotationUtils.getAnnotation(method, PulsarListener.class);
and when i remove #Aspect ,then it's ok.
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface PulsarListener {
String[] topics() default {};
}
#Aspect
#Slf4j
#Component
#Order(0)
public class MDCAspect {
#Around("#annotation(PulsarListener)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
try {
String requestUUID = MDC.get("requestUUID");
if (StringUtils.isEmpty(requestUUID)) {
String uid = ObjectId.get().toHexString();
MDC.put("requestUUID", uid);
}
return joinPoint.proceed();
} finally {
MDC.clear();
}
}
}
#Component
#Slf4j
public class PulsarConsumer {
#PulsarListener(topics = "${topics}")
public void listen(Message<byte[]> receive) {
//doSomething
}
}
public class PulsarPostProcessor implements BeanPostProcessor {
#Value("${pulsar.service.url}")
private String pulsar_service_url;
#Autowired
private ApplicationContext applicationContext;
#Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
Method[] methods = bean.getClass().getDeclaredMethods();
for (Method method : methods) {
//here , i canot not found PulsarListener
//here is the problem
PulsarListener annotation = AnnotationUtils.getAnnotation(method, PulsarListener.class);
if (annotation != null) {
if (log.isDebugEnabled()) {
log.debug("bean :{},method:{}", beanName, method.getName());
}
}
}
}
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));
We have a problem with our tests that the field UriInfo is not correctly injected when the resource is wrapped in a TransactionalProxy.
We tried using the SpringResourceFactory but that did not help either.
I tried to extract the relevant classes for this usecase:
public class InMemoryClientFactory implements FactoryBean<InMemoryClientExecutor>{
#Inject
private SessionResource sessionResource;
#Override
public InMemoryClientExecutor getObject() throws Exception {
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
Registry registry = dispatcher.getRegistry();
registry.addSingletonResource(sessionResource);
final InMemoryClientExecutor inMemoryClientExecutor = new InMemoryClientExecutor(dispatcher);
}
#Override
public Class getObjectType() {
return InMemoryClientExecutor.class;
}
#Override
public boolean isSingleton() {
return true;
}
}
#Path("session")
public interface SessionResource {
#GET
#Path("{sessionId}")
#Produces({MediaType.APPLICATION_XML})
Response get(#PathParam("sessionId") String sessionId);
#DELETE
#Path("{sessionId}")
Response delete(#PathParam("sessionId") String sessionId);
}
#Service
#Transactional
public class SessionResourceImpl implements SessionResource {
#Context
private UriInfo uriInfo;
#Override
public Response get(String sessionId) {
// uriInfo will be null here
String url = uriInfo.getBaseUriBuilder().path(SessionResource.class).path(SessionResource.class, "delete").build(sessionId)
.toString());
return Response.ok(session).build();
#Override
public Response delete(String sessionId) {
System.out.println("Deleted Session "+1);
}
}
#ContextConfiguration(locations = ["classpath:/META-INF/testContext.xml"])
#Transactional
#RunWith(SpringJUnit4ClassRunner.class)
public class SessionResourceIT {
#Inject
InMemoryRestClientFactory inMemoryClientFactory;
#Inject
SessionResource resource;
#Test
public void test() {
SessionResource resource = inMemoryClientFactory.createProxy(SessionResource.class);
ClientResponse cr = client.get(sessionId);
assertNotNull(cr.getEntity(String.class));
}
}
A possible workaround is to unwrap the transactional proxy for the tests, this works as long as the test itself is annotated with #Transactional. I hope someone has a better solution than this.
public class InMemoryClientFactory implements FactoryBean<InMemoryClientExecutor>{
#Inject
private SessionResource sessionResource;
#Override
public InMemoryClientExecutor getObject() throws Exception {
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
Registry registry = dispatcher.getRegistry();
registry.addSingletonResource(unwrapProxy(sessionResource));
final InMemoryClientExecutor inMemoryClientExecutor = new InMemoryClientExecutor(dispatcher);
}
#Override
public Class getObjectType() {
return InMemoryClientExecutor.class;
}
#Override
public boolean isSingleton() {
return true;
}
private static Object unwrapProxy(Object bean) throws Exception {
Object result = bean;
/*
* If the given object is a proxy, set the return value as the object
* being proxied, otherwise return the given object.
*/
if (AopUtils.isAopProxy(bean) && bean instanceof Advised) {
Advised advised = (Advised) bean;
result = advised.getTargetSource().getTarget();
}
return result;
}
}