I have managed to successfully implement a custom injection annotation with target PARAMETER. I do not understand how I make my the annotation support target METHOD as well though?
Here is my sample code:
Hello annotation:
#Retention(RUNTIME)
#Target({METHOD, PARAMETER})
public #interface Hello {
}
Hello annotation resolver:
#Singleton
public class HelloResolver {
public static class HelloInjectionResolver extends ParamInjectionResolver<Hello> {
public HelloInjectionResolver() {
super(HelloValueFactoryProvider.class);
}
}
#Singleton
public static class HelloValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
public HelloValueFactoryProvider(final MultivaluedParameterExtractorProvider extractorProvider,
final ServiceLocator injector) {
super(extractorProvider, injector, UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(final Parameter parameter) {
if (!String.class.equals(parameter.getRawType())) return null;
if (parameter.getAnnotation(Hello.class) == null) return null;
return new AbstractContainerRequestValueFactory<String>() {
#Override
public String provide() {
final DateTime now = DateTime.now();
if (22 < now.getHourOfDay() || now.getHourOfDay() < 6) {
throw new WebApplicationException(FORBIDDEN);
} else {
return format("Hello, it is %s o'clock so I am awake! :)", forPattern("HH:mm").print(now));
}
}
};
}
}
public static class Binder extends AbstractBinder {
#Override
protected void configure() {
bind(HelloValueFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class);
bind(HelloInjectionResolver.class).to(
new TypeLiteral<InjectionResolver<Hello>>() {
}
).in(Singleton.class);
}
}
}
Hello resources:
#Path("hello")
public class HelloResource {
#GET
#Path("method")
#Produces(APPLICATION_JSON)
#Hello
public String method() {
return "Hello!";
}
#GET
#Path("param")
#Produces(APPLICATION_JSON)
public String param(#Hello final String hello) {
return hello;
}
}
When I hit
http://localhost:8080/hello/method
I get a Hello! back no matter if the hour is within the forbidden interval.
I am not sure this will work, but you could try this:
public static class HelloInjectionResolver extends ParamInjectionResolver<Hello> {
public HelloInjectionResolver() {
super(HelloValueFactoryProvider.class);
}
public boolean isMethodParameterIndicator() {
return true;
}
}
Warning: I have not tried this myself but in theory that should allow your resolver to work as a parameter in the method.
Related
If I have a class, where all methods should evaluate only if a boolean attribute is true, what is the best way to define it in terms of best practices?
Is there a way to replace the recurring isEnabled check in all the methods by a single check? The current form works, I'm looking for a way to improve it to not have isEnabled checked in all the methods.
#Singleton
public class SomeClass implements SomeInterface {
EventPublisher eventPubilsher;
boolean isEnabled;
#Inject
public SomeClass(final Decider decider, EventPublisher, eventPublisher) {
this.isEnabled = decider.isSomethingEnabled();
this.eventPublisher = eventPublisher;
}
#Override
public void method1() {
if(isEnabled){
eventPublisher.publishSomething1();
}
}
#Override
public void method2() {
if(isEnabled){
eventPublisher.publishSomething2();
}
}
#Override
public void method3() {
if(isEnabled){
eventPublisher.publishSomething3();
}
}
}
You can have implementation for enabled stuff, and one for disabled stuff. The disabled implementation won't do anything:
public class DisabledSomeClass implements SomeInterface {
#Override
public void method1() {
}
#Override
public void method2() {
}
#Override
public void method3() {
}
}
Enabled one will publish stuff:
public class EnabledSomeClass implements SomeInterface {
EventPublisher eventPublisher;
public EnabledSomeClass(EventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
#Override
public void method1() {
eventPublisher.publishSomething1();
}
#Override
public void method2() {
eventPublisher.publishSomething2();
}
#Override
public void method3() {
eventPublisher.publishSomething3();
}
}
Then extract the logic, if functionality is enabled in new factory class - SomeInterfaceFactory (probably not the best name, but you get the idea), it will return the correct implementation, depending on whether something is enabled.
public class SomeInterfaceFactory {
public static SomeInterface getInstance(Decider decider, EventPublisher eventPublisher) {
if (decider.isSomethingEnabled()) {
return new EnabledSomeClass(eventPublisher);
} else {
return new DisabledSomeClass();
}
}
}
This should work
#Singleton
public class SomeClass implements SomeInterface {
EventPublisher eventPubilsher;
boolean isEnabled;
#Inject
public SomeClass(final Decider decider, EventPublisher, eventPublisher) {
this.isEnabled = decider.isSomethingEnabled();
this.eventPublisher = eventPublisher;
}
#Override
public void method1() {
publish(ep->ep.publishSomething1());
}
#Override
public void method2() {
publish(ep->ep.publishSomething2());
}
#Override
public void method3() {
publish(ep->ep.publishSomething3());
}
private void publish(Consumer<EventPublisher> callback){
if(isEnabled) callback.accept(eventPublished);
}
}
A Runnable interface can receive a lambda without args and run a lambda function with run() method. So, you can create a method inside your Decider class and receive a Runnable as a parameter, like:
class Decider{
private boolean enabled;
//...
public void execute(Runnable runnable){
if(enabled) runnable.run();
}
}
And receive your Decider and do:
//...
EventPublisher eventPubilsher;
Decider decider;
#Inject
public SomeClass(final Decider decider, EventPublisher, eventPublisher) {
this.decider = decider;
this.eventPublisher = eventPublisher;
}
#Override
public void method1() {
Decider.execute(() -> eventPublisher.publishSomething1());
}
//...
You're locking the evaluation of the condition to the constructor. If you want any flexibility, you should pull it out.
public class SomeClass implements SomeInterface {
boolean isEnabled;
public SomeClass(EventPublisher, eventPublisher) {
//
}
public void addDecide(boolean value){
isEnabled = value;
}
boolean getEnabled(){
return isEnabled;
}
#Override
public void method3() {
if(getEnabled()){
eventPublisher.publishSomething3();
}
}
}
You can even go crazy and add a supplier:
public void addDecide(Supplier<Boolean> supplier){
supplier = supplier;
}
boolean getEnabled(){
return supplier.get();
}
Here is what I'm trying to do:
#SpringBootApplication public class App {
public static final String NAME;
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
#Autowired public App(ApplicationArguments arguments) {
NAME = arguments.getSourceArgs()[0]; // ERROR (1)
}
#GetMapping("/" + NAME) public void test() { // ERROR (2)
return NAME;
}
}
The code doesn't work as written because (1) NAME cannot be assigned, and (2) annotation value for #GetMapping must be a constant expression.
I just want #GetMapping to use a value based on a command line argument. How can this be done?
in the first request. spring mvc will init the resource. so your put your dynamic url in haddlermapping.
we need three classes. MyController MyDispatcherServlet DispatcherServletCustomConfiguration
MyController.java
#Component(value="MyController")
public class MyController {
#Autowired
ClaimService claimService;
public ResponseEntity<HttpStatus> insertClaim() {
return new ResponseEntity<>(HttpStatus.OK);
}
}
MyDispatcherServlet.java. after initStrategies.put, your dynamic url into handdlerMapping.
public class MyDispatcherServlet extends DispatcherServlet {
private String url;
public MyDispatcherServlet(String url) {
super();
this.url = url;
}
#Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
List<HandlerMapping> handlerMappings = getHandlerMappings();
for (HandlerMapping handlerMapping : handlerMappings) {
if (handlerMapping instanceof RequestMappingHandlerMapping) {
RequestMappingHandlerMapping requestMappingHandlerMapping = ((RequestMappingHandlerMapping) handlerMapping);
RequestMappingInfo.Builder n = RequestMappingInfo
.paths(url)
.methods(RequestMethod.GET);
try {
Method method = MyController.class.getDeclaredMethod("insertClaim");
requestMappingHandlerMapping.registerMapping(n.build(), "MyController", method);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
}
}
DispatcherServletCustomConfiguration.java
#Configuration
public class DispatcherServletCustomConfiguration {
#Value("${myUrl}")
private String url;
#Bean
public DispatcherServlet dispatcherServlet() {
return new MyDispatcherServlet(url);
}
}
run with the command java -jar stackoverflow-1.0-SNAPSHOT.jar --myUrl=abcd
tl;dr;
I am looking for a way to set a "condition" attribute on a Spring Cacheable annotation from another class. Is there such a way?
Using Spring Cache where it should cache ONLY whenever a certain method has been invoked. This method is in ClassA, the method (data) to cache is in ClassB. What I want to do is something like this:
public ClassA implements myInterface {
...
private Boolean inProcess = false;
public void cacheWhenThisMethodCalled() {
try {
inProcess = true;
// do work here, somewhere along the line
// the method in ClassB gets called
} finally {
inProcess = false;
}
}
ClassB:
public ClassB {
...
#Cacheable(cacheNames={"aCache"}, condition="#classA.inProcess")
public ValueClass findValueClass(UUID id)
However, I can't find the right condition for the SPeL to work. I have tried many combinations, none successfully. ClassA is a SpringBean, but the #Bean annotation returns the Interface, not the class. Can this be made to work? Or is there a better way?
Use a ThreadLocal - you would need to do that anyway for thread safety - otherwise a different thread can change the field.
This works fine...
#SpringBootApplication
#EnableCaching
public class So47580936Application {
public static void main(String[] args) {
SpringApplication.run(So47580936Application.class, args);
}
#Bean
public ApplicationRunner runner(Bar bar) {
return args -> {
bar.cacheFromHere();
bar.dontCacheFromHere();
};
}
#Component
public static class Foo {
#Cacheable(cacheNames = "foos", condition = "T(com.example.So47580936Application$Bar).cacheit()")
public String foo() {
System.out.println("here");
return "foo";
}
}
#Component
public static class Bar {
private static final ThreadLocal<Boolean> cacheit = new ThreadLocal<>();
#Autowired
private Foo foo;
public static boolean cacheit() {
return cacheit.get() == null ? false : cacheit.get();
}
public void cacheFromHere() {
try {
this.cacheit.set(true);
System.out.println("Cache:" + this.foo.foo());
System.out.println("Cache:" + this.foo.foo());
}
finally {
this.cacheit.remove();
}
}
public void dontCacheFromHere() {
System.out.println("Don't:" + this.foo.foo());
System.out.println("Don't:" + this.foo.foo());
}
}
}
result:
here
Cache:foo
Cache:foo
here
Don't:foo
here
Don't:foo
EDIT
Or, you can just make the ThreadLocal a #Bean ...
#SpringBootApplication
#EnableCaching
public class So47580936Application {
public static void main(String[] args) {
SpringApplication.run(So47580936Application.class, args);
}
#Bean
public ApplicationRunner runner(Bar bar) {
return args -> {
bar.cacheFromHere();
bar.dontCacheFromHere();
};
}
#Bean
public ThreadLocal<Boolean> cacheit() {
return new ThreadLocal<>();
}
#Component
public static class Foo {
#Cacheable(cacheNames = "foos", condition = "#cacheit.get() ?: false")
public String foo() {
System.out.println("here");
return "foo";
}
}
#Component
public static class Bar {
#Autowired
private ThreadLocal<Boolean> cacheit;
#Autowired
private Foo foo;
public void cacheFromHere() {
try {
this.cacheit.set(true);
System.out.println("Cache:" + this.foo.foo());
System.out.println("Cache:" + this.foo.foo());
}
finally {
this.cacheit.remove();
}
}
public void dontCacheFromHere() {
System.out.println("Don't:" + this.foo.foo());
System.out.println("Don't:" + this.foo.foo());
}
}
}
Hi i am trying to study named injection in Dagger2
Here are my Java classes but none of them seems to be working.
What i want is that based on #Named annotation i wish to get different objects.
public interface Server {
public void start();
public void stop();
public String request(String request);
}
public abstract class AbstractServer implements Server {
private boolean started;
#Override
public void start() {
started = true;
}
#Override
public void stop() {
if (!started) {
throw new IllegalStateException("Server was not started");
}
}
}
public class AudioServer extends AbstractServer{
#Override
public String request(String request) {
return "Response from Audio server: " + request;
}
}
public class VideoServer extends AbstractServer {
#Override
public String request(String request) {
return "Response from Video server: " + request;
}
}
#Module
public class ServerModule {
public ServerModule() {
}
#Provides
#Named("audio")
#Singleton
AudioServer provideAudioServer() {
return new AudioServer();
}
#Provides
#Named("video")
#Singleton
VideoServer provideVideoServer() {
return new VideoServer();
}
}
Please not ServerComponent.java is not compiling
#Singleton
#Component(modules = {ServerModule.class})
public interface ServerComponent {
AudioServer provideAudioServer();
VideoServer provideVideoServer();
void inject(TestInject inject);
}
public class TestInject {
private static final Logger logger = Logger.getLogger(TestInject.class.getSimpleName());
#Inject
#Named("audio")
Server audioServer;
public TestInject() {
// ServerComponent component = DaggerServerComponent.builder()
// .build();
// component.inject(this);
}
public void test() {
String serverResponse = null;
if (audioServer != null) {
serverResponse = audioServer.request("game.mp3");
logger.warning(serverResponse);
} else {
serverResponse = "Failure";
logger.info(serverResponse);
}
}
public static void main(String[] args) {
TestInject inject = new TestInject();
inject.test();
}
}
EDITED Please see answer in TestInject.java and ServerComponent.java
public interface Server {
public void start();
public void stop();
public String request(String request);
}
public abstract class AbstractServer implements Server {
private boolean started;
#Override
public void start() {
started = true;
}
#Override
public void stop() {
if (!started) {
throw new IllegalStateException("Server was not started");
}
}
}
public class AudioServer extends AbstractServer{
#Override
public String request(String request) {
return "Response from Audio server: " + request;
}
}
public class VideoServer extends AbstractServer {
#Override
public String request(String request) {
return "Response from Video server: " + request;
}
}
#Module
public class ServerModule {
public ServerModule() {
}
#Provides
#Named("audio")
#Singleton
AudioServer provideAudioServer() {
return new AudioServer();
}
#Provides
#Named("video")
#Singleton
VideoServer provideVideoServer() {
return new VideoServer();
}
}
Please not ServerComponent.java is not compiling
#Singleton
#Component(modules = {ServerModule.class})
public interface ServerComponent {
#Named("audio")
Server provideAudioServer();
#Named("video")
Server provideVideoServer();
void inject(TestInject inject);
}
public class TestInject {
private static final Logger logger = Logger.getLogger(TestInject.class.getSimpleName());
#Inject
#Named("audio")
Server audioServer;
#Inject
#Named("video")
Server videoServer;
public TestInject() {
ServerComponent component = DaggerServerComponent.builder()
.build();
component.inject(this);
}
public void testAudioServer() {
String serverResponse = null;
if (audioServer != null) {
serverResponse = audioServer.request("game.mp3");
logger.warning(serverResponse);
} else {
serverResponse = "audio server Failure";
logger.info(serverResponse);
}
}
public void testVideoServer() {
String serverResponse = null;
if (videoServer != null) {
serverResponse = videoServer.request("movie.mp4");
logger.warning(serverResponse);
} else {
serverResponse = "Video server Failure";
logger.info(serverResponse);
}
}
public static void main(String[] args) {
TestInject inject = new TestInject();
inject.testAudioServer();
inject.testVideoServer();
}
}
Your main problem seems to be related to the fact that you expect in the class TestInject a Server named audio while your provider returns AudioServer so dagger cannot satisfy your dependency.
Indeed don't forget that the annotation #Named is used to distinguish 2 objects of the same type, in other words you can annotate with #Named("audio") different providers as long as they don't return the same type. The object produced will then be identified by its type and its name.
So for example here is one way to fix you problem:
The class TestInject:
public class TestInject {
...
public TestInject() {
// Needed to inject your dependencies
ServerComponent component = DaggerServerComponent.builder()
.build();
component.inject(this);
}
...
}
The class ServerComponent
#Singleton
#Component(modules = ServerModule.class)
public interface ServerComponent {
void inject(TestInject inject);
}
The class ServerModule
#Module
public class ServerModule {
#Provides
#Named("audio")
#Singleton
public Server provideAudioServer() {
return new AudioServer();
}
#Provides
#Named("video")
#Singleton
public Server provideVideoServer() {
return new VideoServer();
}
}
Even with your question update, your module should be what I propose abose otherwise it won't compile for the same reason described previously.
How should I do the ValueFactoryProvider binding in order to have two custom injection annotations coexist in Jersey 2? Below I have included an example of my current approach and as you can see the Hello annotation injection "hides" the SmallTalk annotation injection.
Hello annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.PARAMETER})
public #interface Hello {
}
SmallTalk annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.PARAMETER})
public #interface SmallTalk {
}
Hello annotation resolver:
#Singleton
public class HelloResolver {
public static class HelloInjectionResolver extends ParamInjectionResolver<Hello> {
public HelloInjectionResolver() {
super(HelloValueFactoryProvider.class);
}
}
#Singleton
public static class HelloValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
public HelloValueFactoryProvider(final MultivaluedParameterExtractorProvider extractorProvider,
final ServiceLocator injector) {
super(extractorProvider, injector, UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(final Parameter parameter) {
final Class<?> classType = parameter.getRawType();
if (classType == null || (!classType.equals(String.class))) return null;
return new AbstractContainerRequestValueFactory<String>() {
#Override
public String provide() {
return "Hello!";
}
};
}
}
public static class Binder extends AbstractBinder {
#Override
protected void configure() {
bind(HelloValueFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class);
bind(HelloInjectionResolver.class).to(
new TypeLiteral<InjectionResolver<Hello>>() {
}
).in(Singleton.class);
}
}
}
SmallTalk annotation resolver:
#Singleton
public class SmallTalkResolver {
public static class SmallTalkInjectionResolver extends ParamInjectionResolver<SmallTalk> {
public SmallTalkInjectionResolver() {
super(SmallTalkValueFactoryProvider.class);
}
}
#Singleton
public static class SmallTalkValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
public SmallTalkValueFactoryProvider(final MultivaluedParameterExtractorProvider extractorProvider,
final ServiceLocator injector) {
super(extractorProvider, injector, UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(final Parameter parameter) {
final Class<?> classType = parameter.getRawType();
if (classType == null || (!classType.equals(String.class))) return null;
return new AbstractContainerRequestValueFactory<String>() {
#Override
public String provide() {
return "Nice weather.";
}
};
}
}
public static class Binder extends AbstractBinder {
#Override
protected void configure() {
bind(SmallTalkValueFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class);
bind(SmallTalkInjectionResolver.class).to(
new TypeLiteral<InjectionResolver<SmallTalk>>() {
}
).in(Singleton.class);
}
}
}
Resource configuration:
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(new HelloResolver.Binder());
register(new SmallTalkResolver.Binder());
registerClasses(HelloResource.class);
}
}
Resource using both injection annotations:
#Path("/")
public class HelloResource {
#GET
#Path("hello")
#Produces("application/json")
public String hello(#Hello final String hello, #SmallTalk final String smallTalk) {
return hello + " " + smallTalk;
}
}
Result when requesting the resource - should have been "Hello! Nice weather.":
Found a solution! I added
if (parameter.getAnnotation(Hello.class) == null) return null;
and
if (parameter.getAnnotation(SmallTalk.class) == null) return null;
to the createValueFactory method of the two value factory providers.