Intercept all responses in Play 2.1 - java

Is there a way to intercept all HTTP responses in using Play Framework 2.1?
This is what I have in my Global.java file to intercept all requests, but I'm also looking to intercept responses:
import java.lang.reflect.Method;
import play.GlobalSettings;
import play.mvc.*;
import play.mvc.Http.*;
import views.html.*;
public class Global extends GlobalSettings {
private static BasicAuthHandler AUTH;
#SuppressWarnings("rawtypes")
#Override
public Action onRequest(Request request, Method actionMethod) {
if ( ... ) {
return new Action.Simple() {
#Override
public Result call(Context ctx) throws Throwable {
return unauthorized();
}
};
}
return super.onRequest(request, actionMethod);
}
}
I've read the documentation on manipulating the response but it only describes how to do it for each result individually.

TransactionalAction is an example of request/response interceptor. It extends Action and provides Transactional annotation which targets controller type or method.
Example of controller method annotated with action:
#Transactional
public static Result ok(){
return ok();
}
More details.
An example of action logging responses (mind, actions which do not provide annotations like Transactional, extend Action.Simple):
public class LogAction extends Action.Simple {
#Override
public F.Promise<Result> call(Http.Context ctx) throws Throwable {
F.Promise<Result> call = delegate.call(ctx);
return call.map(r -> {
String responseBody = new String(JavaResultExtractor.getBody(r, 0L));
Logger.info(responseBody);
return r;
});
}
}
Usage, method definition:
#With(LogAction.class)
public static Result ok(){
return ok();
}
Usage, class definition - all methods intercepted:
#With(LogAction.class)
public class BaseController extends Controller {
....
}
You can go one step forward, if you dont like #With annotation. Define custom annotation yourself:
#With({ LogAction.class })
#Target({ ElementType.TYPE, ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface Log {
}
and use it this way:
#Log
public static Result ok(){
return ok();
}
If your custom annotation accepts parameters, change LogAction definition this way:
public class LogAction extends Action<Log> {
// use configuration object to access your custom annotation configuration
}

Related

Jersey 2.26+: configure() not called after converting HK2's Factory to Supplier

For simplifying our web services I'd like to introduce a custom MyObj class using the Jersey framework in version 2.34 and want to inject the created instances via the #Context annotation.
I have two questions:
Assuming a web service method #GET test(#Context MyObj obj), how can I control when instances of MyObj are created in respect to the execution of existing servlet request filters?
To create instances of MyObj, I already have a working example based on HK2's Factory's (see below). Since I observed that my factory class gets instantiated twice, and Jersey 2.26+ recomments to use the newer approach based on Supplier's, I tried to convert my example. Unfortunately, configure() won't be called in the provided Binder implements Supplier class, and thus, no objects get created. How can I get this working? (Btw., In both cases, Binder and BinderHK are registered via jersey.config.server.provider.classnames in my web.xml.)
Thank you for any help.
Working HK2 Factory example:
public class MyObjHK {}
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.process.internal.RequestScoped;
public class BinderHK
extends AbstractBinder {
#Override protected void configure() {
bindFactory(MyObjFactoryHK.class).to(MyObjHK.class).in(RequestScoped.class);
}
}
import org.glassfish.hk2.api.Factory;
public class MyObjFactoryHK
implements Factory<MyObjHK> {
#Override public MyObjHK provide() {return new MyObjHK();} // ok
#Override public void dispose(MyObjHK instance) {};
}
public class API_HK2 {
#GET
public static Response myobjhk(#Context MyObjHK obj) {
System.out.println("called hk, obj="+obj); // ok
return Response.ok().build();
}
}
Not working Supplier example:
public class MyObj {}
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.process.internal.RequestScoped;
public class Binder
extends AbstractBinder {
#Override protected void configure() { // not called ???
bindFactory(MyObjFactory.class).to(MyObj.class).in(RequestScoped.class);
}
}
import java.util.function.Supplier;
public class MyObjFactory
implements Supplier<MyObj> {
#Override public MyObj get() {return new MyObj();}
}
public class API {
#GET
public static Response myobj(#Context MyObj obj) {
System.out.println("called, obj="+obj); // null ???
return Response.ok().build();
}
}
The binder isn't something that can be registered with the jersey.config..classnames init-param. You need to either register it with a ResourceConfig or with Feature (and register the Feature with the init-param)
#Provider
public class MyFeature implements Feature {
#Override
pubic boolean configure(FeatureContext ctx) {
ctx.register(new MyBinder());
return true;
}
}

Add (custom) decoder to WebMVC endpoint

I have a WebMVC endpoint:
#RequestMapping(path = "/execution/{id}", method = RequestMethod.POST)
public ResponseEntity<...> execute(#PathVariable String id) {
...
}
Here, the provided id should be decoded first. Is it possible to define an annotation which does this "in the background"; that is, prior to calling the endpoint? Something in the lines of:
#RequestMapping(path = "/execution/{id}", method = RequestMethod.POST)
public ResponseEntity<...> execute(#PathVariable #DecodedIdentifier String id) {
...
}
Note the #DecodedIdentifier annotation. I know it does not exists, but it hopefully explains my intent. I know this is possible with Jersey's JAX-RS implementation, but what about Spring's WebMVC?
Here, I am using base64 decoding, but I wondering if I could inject a custom decoder as well.
Although you can use annotations, I recommend you to use a custom Converter for this purpose.
Following your example, you can do something like this.
First, you need to define a custom class suitable to be converted. For instance:
public class DecodedIdentifier {
private final String id;
public DecodedIdentifier(String id) {
this.id = id;
}
public String getId() {
return this.id;
}
}
Then, define a Converter for your custom class. It can perform the Base64 decoding:
public class DecodedIdentifierConverter implements Converter<String, DecodedIdentifier> {
#Override
public DecodedIdentifier convert(String source) {
return new DecodedIdentifier(Base64.getDecoder().decode(source));
}
}
In order to tell Spring about this converter you have several options.
If you are running Spring Boot, all you have to do is annotate the class as a #Component and the auto configuration logic will take care of Converter registration.
#Component
public class DecodedIdentifierConverter implements Converter<String, DecodedIdentifier> {
#Override
public DecodedIdentifier convert(String source) {
return new DecodedIdentifier(Base64.getDecoder().decode(source));
}
}
Be sure to configure your component scan so Spring can detect the #Component annotation in the class.
If you are using Spring MVC without Spring Boot, you need to register the Converter 'manually':
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new DecodedIdentifierConverter());
}
}
After Converter registration, you can use it in your Controller:
#RequestMapping(path = "/execution/{id}", method = RequestMethod.POST)
public ResponseEntity<...> execute(#PathVariable DecodedIdentifier id) {
...
}
There are also other options you can follow. Please, consider read this article, it will provide you further information about the problem.
As a side note, the above mentioned article indicates that you can directly define a valueOf method in the class which will store the result of the conversion service, DecodedIdentifier in your example, and it will allow you to get rid of the Converter class: to be honest, I have never tried that approach, and I do not know under which conditions it could work. Having said that, if it works, it can simplify your code. Please, if you consider it appropriate, try it.
UPDATE
Thanks to #Aman comment I carefully reviewed the Spring documentation. After that, I found that, although I think that the conversion approach aforementioned is better suited for the use case - you are actually performing a conversion - another possible solution could be the use of a custom Formatter.
I already knew that Spring uses this mechanism to perform multiple conversion but I were not aware that it is possible to register a custom formatter based on an annotation, the original idea proposed in the answer. Thinking about annotations like DateTimeFormat, it makes perfect sense. In fact, this approach were previously described here, in Stackoverflow (see the accepted answer in this question).
In your case (basically a transcription of the answer above mentioned for your case):
First, define your DecodedIdentifier annotation:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
public #interface DecodedIdentifier {
}
In fact, you can think of enriching the annotation by including, for example, the encoding in which the information should be processed.
Then, create the corresponding AnnotationFormatterFactory:
import java.text.ParseException;
import java.util.Base64;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
import org.springframework.context.support.EmbeddedValueResolutionSupport;
import org.springframework.format.AnnotationFormatterFactory;
import org.springframework.format.Formatter;
import org.springframework.format.Parser;
import org.springframework.format.Printer;
import org.springframework.stereotype.Component;
#Component
public class DecodedIdentifierFormatterFactory extends EmbeddedValueResolutionSupport
implements AnnotationFormatterFactory<DecodedIdentifier> {
#Override
public Set<Class<?>> getFieldTypes() {
return Collections.singleton(String.class);
}
#Override
public Printer<?> getPrinter(DecodedIdentifier annotation, Class<?> fieldType) {
return this.getFormatter(annotation);
}
#Override
public Parser<?> getParser(DecodedIdentifier annotation, Class<?> fieldType) {
return this.getFormatter(annotation);
}
private Formatter getFormatter(DecodedIdentifier annotation) {
return new Formatter<String>() {
#Override
public String parse(String text, Locale locale) throws ParseException {
// If the annotation could provide some information about the
// encoding to be used, this logic will be highly reusable
return new String(Base64.getDecoder().decode(text));
}
#Override
public String print(String object, Locale locale) {
return object;
}
};
}
}
Register the factory in your Spring MVC configuration:
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatterForFieldAnnotation(new DecodedIdentifierFormatterFactory());
}
}
Finally, use the annotation in your Controllers, exactly as you indicated in your question:
#RequestMapping(path = "/execution/{id}", method = RequestMethod.POST)
public ResponseEntity<...> execute(#PathVariable #DecodedIdentifier String id) {
...
}
You can achieve this implementing a HandlerMethodArgumentResolver:
public class DecodedIdentifierArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(DecodedIdentifier.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String value = webRequest.getParameterValues(parameter.getParameterName())[0];
return Base64.getDecoder().decode(value);
}
}
The problem with a custom HandlerMethodArgumentResolver and #PathVariable or #RequestParam is that it will never get executed, as #PathVariable and #RequestParam have their own resolvers each, which get executed prior to any custom resolvers. What if I want to obfuscate Long id param with Hashids? Then, the parameter has to be passed as a hashed String, get decoded to original Long id value. How do I provide conversion type change?

How to dynamically disable specific API in spring?

I have a flag DISABLE_FLAG and I want to use it to control multiple specific APIs in different controllers.
#RestController
public final class Controller1 {
#RequestMapping(value = "/foo1", method = RequestMethod.POST)
public String foo1()
}
#RestController
public final class Controller2 {
#RequestMapping(value = "/foo2", method = RequestMethod.POST)
public String foo2()
}
I can use an interceptor to handle all the urls. Is there a easy way to do that like annotation?
You could use AOP to do something like that.
Create your own annotation...
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Maybe { }
and corresponding aspect...
#Aspect
public class MaybeAspect {
#Pointcut("#annotation(com.example.Maybe)")
public void callMeMaybe() {}
#Around("callMeMaybe()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// do your logic here..
if(DISABLE_FOO) {
// do nothing ? throw exception?
// return null;
throw new IllegalStateException();
} else {
// process the request normally
return joinPoint.proceed();
}
}
}
I don't think there is direct way to disable a constructed request mapping but We can disable API in many ways with some condition.
Here is the 2 ways disabling by spring profile or JVM properties.
public class SampleController {
#Autowired
Environment env;
#RequestMapping(value = "/foo", method = RequestMethod.POST)
public String foo(HttpServletResponse response) {
// Using profile
if (env.acceptsProfiles("staging")) {
response.setStatus(404);
return "";
}
// Using JVM options
if("true".equals(System.getProperty("DISABLE_FOO"))) {
response.setStatus(404);
return "";
}
return "";
}
}
If you are thinking futuristic solution using cloud config is the best approach. https://spring.io/guides/gs/centralized-configuration/
Using Conditional components
This allows to build bean with conditions, if the condition failed on startup, the entire component will never be built. Group all your optional request mapping to new controller and add conditional annotation
#Conditional(ConditionalController.class)
public class SampleController {
#Autowired
Environment env;
#RequestMapping(value = "/foo", method = RequestMethod.POST)
public String foo(HttpServletResponse response) {
return "";
}
public static class ConditionalController implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().acceptsProfiles("staging"); // Or whatever condition
}
}
}
You can solve this with annotations by utilizing spring profiles. You define two profiles one for enabled flag and another profile for the disabled flag. Your example would look like this:
#Profile("DISABLED_FLAG")
#RestController
public final class Controller1 {
#RequestMapping(value = "/foo1", method = RequestMethod.POST)
public String foo1()
}
#Profile("ENABLED_FLAG")
#RestController
public final class Controller2 {
#RequestMapping(value = "/foo2", method = RequestMethod.POST)
public String foo2()
}
Here is the link to the spring framework documentation for this feature: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Profile.html
I did it as follows :
#Retention(RUNTIME)
#Target(ElementType.METHOD)
public #interface DisableApiControl {
}
This class is my customization statement. After could use AOP :
for AbstractBaseServiceImpl :
public abstract class AbstractBaseServiceImpl {
private static boolean disableCheck = false;
public void setDisableChecker(boolean checkParameter) {
disableCheck = checkParameter;
}
public boolean getDisableChecker() {
return disableCheck;
}
}
NOTE : The above class has been prepared to provide a dynamic structure.
#Aspect
#Component
public class DisableApiControlAspect extends AbstractBaseServiceImpl {
#Autowired
private HttpServletResponse httpServletResponse;
#Pointcut(" #annotation(disableMe)")
protected void disabledMethods(DisableApiControl disableMe) {
// comment line
}
#Around("disabledMethods(disableMe)")
public Object dontRun(ProceedingJoinPoint joinPoint, DisableApiControl disableMe) throws Throwable {
if (getDisableChecker()) {
httpServletResponse.sendError(HttpStatus.NOT_FOUND.value(), "Not found");
return null;
} else {
return joinPoint.proceed();
}
}
}
checker parameter added global at this point. The rest will be easier when the value is given as true / false when needed.
#GetMapping("/map")
#DisableApiControl
public List<?> stateMachineFindMap() {
return new ArrayList<>;
}

How to override #RequestMapping in another controller?

I need to extend an existing controller and add some functionality to it. But as a project requirement I can't touch in the original controller, the problem is that this controller have an #RequestMapping annotation on it. So my question is how can I make requests to /someUrl go to my new controller instead of the old one.
here is a example just to clarify what I'm talking about:
Original controller:
#Controller
public class HelloWorldController {
#RequestMapping("/helloWorld")
public String helloWorld(Model model) {
model.addAttribute("message", "Hello World!");
return "helloWorld";
}
}
new Controller:
#Controller
public class MyHelloWorldController {
#RequestMapping("/helloWorld")
public String helloWorld(Model model) {
model.addAttribute("message", "Hello World from my new controller");
// a lot of new logic
return "helloWorld";
}
}
how can I override the original mapping without editing HelloWorldController?
Url mapping as annotation can not be overridden. You will get an error if two or more Controllers are configured with the same request url and request method.
What you can do is to extend the request mapping:
#Controller
public class MyHelloWorldController {
#RequestMapping("/helloWorld", params = { "type=42" })
public String helloWorld(Model model) {
model.addAttribute("message", "Hello World from my new controller");
return "helloWorld";
}
}
Example: Now if you call yourhost/helloWorld?type=42 MyHelloWorldController will response the request
By the way.
Controller should not be a dynamic content provider. You need a #Service instance. So you can implement Controller once and use multiple Service implementation. This is the main idea of Spring MVC and DI
#Controller
public class HelloWorldController {
#Autowired
private MessageService _messageService; // -> new MessageServiceImpl1() or new MessageServiceImpl2() ...
#RequestMapping("/helloWorld")
public String helloWorld(Model model) {
model.addAttribute("message", messageService.getMessage());
return "helloWorld";
}
}
Here is another workaround, that may or may not be dangerous.
Create the below class "MyRequestMappingHandler", then wire it up in your MvcConfig
#Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new MyRequestMappingHandler();
}
RequestMappingHandlerMapping: * THIS IS NOT PRODUCTION CODE - UP TO YOU *
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class MyRequestMappingHandler extends RequestMappingHandlerMapping {
#Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo mappingForMethod = super.getMappingForMethod(method, handlerType);
// Check if this class extends a super. and that super is annotated with #Controller.
Class superClass = handlerType.getSuperclass();
if (superClass.isAnnotationPresent(Controller.class)) {
// We have a super class controller.
if (handlerType.isAnnotationPresent(Primary.class)) {
// We have a #Primary on the child.
return mappingForMethod;
}
} else {
// We do not have a super class, therefore we need to look for other implementations of this class.
Map<String, Object> controllerBeans = getApplicationContext().getBeansWithAnnotation(Controller.class);
List<Map.Entry<String, Object>> classesExtendingHandler = controllerBeans.entrySet().stream().filter(e ->
AopUtils.getTargetClass(e.getValue()).getSuperclass().getName().equalsIgnoreCase(handlerType
.getName()) &&
!AopUtils.getTargetClass(e.getValue()).getName().equalsIgnoreCase(handlerType.getName()))
.collect(Collectors.toList());
if (classesExtendingHandler == null || classesExtendingHandler.isEmpty()) {
// No classes extend this handler, therefore it is the only one.
return mappingForMethod;
} else {
// Classes extend this handler,
// If this handler is marked with #Primary and no others are then return info;
List<Map.Entry<String, Object>> classesWithPrimary = classesExtendingHandler
.stream()
.filter(e -> e.getValue().getClass().isAnnotationPresent(Primary.class) &&
!AopUtils.getTargetClass(e.getValue().getClass()).getName().equalsIgnoreCase
(handlerType.getName()))
.collect(Collectors.toList());
if (classesWithPrimary == null || classesWithPrimary.isEmpty()) {
// No classes are marked with primary.
return null;
} else {
// One or more classes are marked with #Primary,
if (classesWithPrimary.size() == 1 && AopUtils.getTargetClass(classesWithPrimary.get(0).getValue
()).getClass().getName().equalsIgnoreCase(handlerType.getName())) {
// We have only one and it is this one, return it.
return mappingForMethod;
} else if (classesWithPrimary.size() == 1 && !AopUtils.getTargetClass(classesWithPrimary.get(0)
.getValue()).getClass().getName().equalsIgnoreCase(handlerType.getName())) {
// Nothing.
} else {
// nothing.
}
}
}
}
// If it does, and it is marked with #Primary, then return info.
// else If it does not extend a super with #Controller and there are no children, then return info;
return null;
}
}
What this allows you to do is, extend a #Controller class, and mark it with #Primary, and override a method on that class, your new class will now be loaded up when spring starts up instead of blowing up with "multiple beans / request mappings etc"
Example of "super" Controller :
#Controller
public class Foobar {
#RequestMapping(method = "GET")
private String index() {
return "view";
}
}
Example of implementation :
#Primary
#Controller
public class MyFoobar extends Foobar {
#Override
private String index() {
return "myView";
}
}
Each mapping must be unique.. There is no way to overrule an existing #RequestMapping.
BUT You can always do some workarounds:
Use a param in the request like this will create a new #RequestMapping that will differ from the existing one.
#RequestMapping("/helloWorld/{someDataId}", method = RequestMethod.GET)
public String helloWorld(#PathVariable("someDataId") final long id, Model model) {
/* your code here */
}
Or creating another #Controller extending the existing one:
public class YourController extends BaseController {
#Override
#RequestMapping("/helloWorld")
public void renderDashboard(Model model){
// Call to default functionallity (if you want...)
super.renderDashboard(patientId, map);
}
}
You can dynamically (on application startup) deregister the existing handler methods from the RequestMappingHandlerMapping, and register your (new) handler method instead.
This could be done as follows:
class ApplicationConfig {
#Bean
NewController newController() {
return new NewController();
}
#Autowired
public void registerOverriddenControllerEndpoint(final RequestMappingHandlerMapping handlerMapping,
final NewController controller) throws NoSuchMethodException {
final RequestMappingInfo mapping = RequestMappingInfo.paths("path/to/be/overridden")
.methods(RequestMethod.GET) // or any other request method
.build();
handlerMapping.unregisterMapping(mapping);
Class[] argTypes = new Class[]{/* The parameter types needed for the 'methodThatHandlesTheEndpoint' method */};
handlerMapping.registerMapping(mapping, controller, NewController.class.getMethod("methodThatHandlesTheEndpoint", argTypes));
}
}
This means, that I have now two methods with the same mapping:
class ExistingController {
// This will be now ignored
#GetMapping("path/to/be/overridden")
public ResponseEntity<Void> methodThatHandlesTheEndpoint() {
}
}
and
class NewController {
// This will be now the main handler
#GetMapping("path/to/be/overridden")
public ResponseEntity<Void> methodThatHandlesTheEndpoint() {
}
}

Spring proxy to choose implementation based on annotation and runtime value

I would like to inject a proxy implementation of an interface to a component and then let spring choose the right implementation based on a runtime property (and the value of an annotation at the implementation class). So my component does not have to care about choosing the right one.
It is kind of like a scope. But i think scopes are only for handling different instances of the same implementation class. Am i wrong with this?
I would like this to run for arbitrary interfaces without creating a service locator or some other construct for every new service.
Here is an example.
Suppose I have an interface defining a service
package test;
public interface IService {
void doSomething();
}
and two implementations:
package test;
import javax.inject.Named;
#Named
#MyAnnotation("service1")
public class Service1 implements IService {
#Override
public void doSomething() {
System.out.println("this");
}
}
...
package test;
import javax.inject.Named;
#Named
#MyAnnotation("service2")
public class Service2 implements IService {
#Override
public void doSomething() {
System.out.println("that");
}
}
Now I would like to inject an IService to another component and let spring choose the correct implementation based on some queryable run time property and the value of MyAnnotation.
Is there a way to do this in a general way in spring?
EDIT:
I have a Context that holds some value. It is a thread local in this case.
package test;
public class MyValueHolder {
private static final ThreadLocal<String> value = new ThreadLocal<>();
public static void set(String newValue) {
value.set(newValue);
}
public static String get() {
return value.get();
}
public static void reset() {
value.remove();
}
}
And I have an component which uses IService
package test;
import javax.inject.Inject;
import javax.inject.Named;
#Named
public class MyComponent {
#Inject
private IService service;
public void myImportantWorkflow(){
MyValueHolder.set("service1");
service.doSomething();
MyValueHolder.set("service2");
service.doSomething();
}
}
The injected service should only be a proxy. Depending on the value set in MyValueHolder the call to doSomething should delegate to service1 or service2. So in this example it should delegate to doSomething on service1 in the first call and to service2 in the second call.
I could write such a delegator implementing the IService interface and use it for this one service. But then i have to repeat this for every other service . I hoped spring could do something like this with proxies almost by itself. Of course i have to provide some method to look beans up based on the value hold in the thread local and register it to spring. But i have no idea if that is even possible without modifying the spring framework. And if it is possible how to accomplish this.
You could use a ProxyFactoryBean to create the proxies and a TargetSource to do the lookup.
For example (not tested)
public class AnnotatedBeanTargetSource implements TargetSource, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
private Class<? extends Annotation> annotationType;
private Class<?> implementedIterface;
private Map<String, Object> beans;
#Override
public Class<?> getTargetClass() {
return this.implementedIterface;
}
#Override
public boolean isStatic() {
return false;
}
#Override
public Object getTarget() throws Exception {
if (this.beans == null) {
this.beans = lookupTargets();
}
return this.beans.get(MyValueHolder.get());
}
protected Map<String, Object> lookupTargets() {
Map<String, Object> resolvedBeans = new HashMap<String, Object>();
String[] candidates = beanFactory.getBeanNamesForAnnotation(annotationType);
for (String beanName : candidates) {
Class<?> type = beanFactory.getType(beanName);
if (this.implementedIterface.isAssignableFrom(type)) {
Annotation ann = AnnotationUtils.getAnnotation(type, annotationType);
resolvedBeans.put((String) AnnotationUtils.getValue(ann), beanFactory.getBean(beanName));
}
}
return resolvedBeans;
}
#Override
public void releaseTarget(Object target) throws Exception {
// nothing to do
}
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
public Class<? extends Annotation> getAnnotationType() {
return annotationType;
}
public void setAnnotationType(Class<? extends Annotation> annotationType) {
this.annotationType = annotationType;
}
public Class<?> getImplementedIterface() {
return implementedIterface;
}
public void setImplementedIterface(Class<?> implementedIterface) {
this.implementedIterface = implementedIterface;
}
}
This is what I would do:
#Named
public class MyComponent {
// introduce a marker interface for Injecting proxies
#InjectDynamic
IService service
...
public void useIService() {
service.doSomething();
...
service.doSomethingElse();
...
service.doFinally();
}
}
Define a BeanPostProcessor that scans for bean with fields annotated with #InjectDynamic, then creates and inject a Proxy implementing the type required by the field.
The Proxy implementation will look in the applicationContext for beans implementing Supplier<T> (Java 8 or guava versions) where <T> is the type of the field annotated with #InjectDynamic.
Then you can define
#Name
public IServiceSupplier implements Supplier<IService> {
#Override
public IService get() {
// here you implement the look-up logic for IService
}
}
In this way the look-up of active the current implementation is decoupled from the Proxy and can be change by target type.

Categories

Resources