Choose Controller Bean instance by RequestMapping - java

I want Spring to create 2 instances of FooController. Requests to /foo should be handled by one of the instances and requests to /bar should be handled by the other instance. I want something like the below, but of course #RequestMapping doesn't work that way and also Spring gives me the ambiguous mapping error on FooController as well.
#RestController
public class FooController {
String name;
public FooController(String name) { this.name = name; }
}
#Configuration
public class FooControllerConfig {
#Bean
#RequestMapping("/foo")
public FooController getFooFooController(){
return new FooController("foo");
}
#Bean
#RequestMapping("/bar")
public FooController getBarFooController(){
return new FooController("bar");
}
}

I'm really confused by why you need this requirement? Can you please explain why this is required? Is it that each mapping requires a different name?
First you do not map Beans to a RequestMapping. While I am not even sure the spring application would start it would potentially create a new Bean with an identical name every time you access one of these mappings which would probably throw an error.
You could potentially overcome the duplicate names with your own annotation processing but that is way more work then this looks like it is worth.
Just looking at what you have there is there any reason why the following will not meet your requirements?
#RestController
public class FooController {
private static final fooName = "fooName";
private static final barName = "barName";
#RequestMapping("/foo")
public String getFoo(){
return fooName;
}
#RequestMapping("/bar")
public String getBar(){
return barName;
}
}

Don't try this at home. This code was performed by a bored, trained professional...
You can have multiple instances of the same controller class, each of which handles a different URL through the same or a different method in the controller. The only thing is, I don't know how to do it with just annotations. The way I just did it was to dynamically register each request mapping at initialization time. The FooController becomes a prototype bean (defined with annotations) so you can have Spring instantiate it multiple times, once for each mapping
FooController.java
#Controller
#Scope("prototype")
public class FooController {
private String name;
public FooController() {}
public FooController(String name) {
this.name = name;
}
public ResponseEntity<String> handleRequests() throws Exception {
return new ResponseEntity<>("Yo: " + name + " " + this.hashCode(), HttpStatus.OK);
}
EndpointService.java
#Service
public class EndpointService {
#Autowired
private BeanFactory beanFactory;
#Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
public void addFooController(String urlPath, String name) throws NoSuchMethodException {
RequestMappingInfo requestMappingInfo = RequestMappingInfo
.paths(urlPath)
.methods(RequestMethod.GET)
.produces(MediaType.APPLICATION_JSON_VALUE)
.build();
requestMappingHandlerMapping.registerMapping(requestMappingInfo,
beanFactory.getBean(FooController.class, name),
FooController.class.getDeclaredMethod("handleRequests"));
}
#EventListener
public void handleContextRefreshEvent(ContextRefreshedEvent ctxStartEvt) {
try {
addFooController("/blah1", "blahblah1");
addFooController("/blah2", "blahblah2");
addFooController("/blah3", "blahblah3");
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
Results:
http://localhost:8080/blah1 returns: Yo: blahblah1 1391627345
http://localhost:8080/blah3 returns: Yo: blahblah3 2078995154

Related

how to create a bean in dynamic times with different parameters

if I have a class like this:
#Service
#Scope("prototype")
public class TraderStarter {
private String address;
}
and TraderStarter should be created X times, X is dynamically determined by databases. How should I get these beans?
Only like this?
#Component("SpringContextUtil")
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
#SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
return (T) applicationContext.getBean(name);
}
}
using SpringContextUtil .getBean() and set parameters for every prototype TraderStarter?
Thanks a lot.
In a nutshell, prototype means that the new bean is created upon each request for this bean (injections into different classes / getBean call).
So if you want to create these prototype beans, someone triggers this process.
Of course one way is to use the ApplicationContext but IMO its a bad approach, since your code becomes coupled to spring.
You cannot inject prototype into the singleton, this doesn't work.
However you can use javax.inject.Provider interface that is integrated with Spring:
Here is an example:
// from your example
// you can use injection here and everything, its a regular spring bean
#Service
#Scope("prototype")
public class TraderStarter {
private String address;
public void setAddress(String address) {
this.address = address;
}
}
///////////////////////
#Component
public class MyDbManager {
private Provider<TraderStarter> traderStarterProvider;
public List<TraderStarter> dynamicallyCreateBeans() {
List<String> addresses = dbManager.findAllAddresses();// to to the db, get the data
return
addresses.stream()
.map(this::createTraderStarter) // <-- creates different beans!
.collect(Collectors.toList());
}
private TraderStarter createTraderStarter(String address) {
TraderStarter ts = provider.get();
ts.setAddress(address);
return ts;
}
}
There are other methods as well with factories (see ObjectFactory) and creating proxies (Lookup method and scoped proxy), but IMO this is the most clean approach.
In any case if you opt for other solutions, read this tutorial

Spring Autowire Request Scope

In Spring it's easy to autowire beans and have them available anywhere in the app context. Beans can be specialized to a scope such as session/request/web socket etc.
I have a rather unique scenario. I receive a message from a message broker which means the request is not received in a "Controller". Because of this, Spring is not creating #RequestScope beans (All of this logic in Spring is based on using the #Controller/#RequestMapping annotations / DispatchServlet handler).
Is there a way to create a bean within the request scope with the Spring AutowireCapableBeanFactory or some other way?
I want to do something like the below in which the SomeService.handle will be able to access the getName() method of the RequestScopeBean. Currently it throws this exception.
Exception:
BeanCreationException: Error creating bean with name '
scopedTarget.getRequestUtils': Scope 'request' is not active for the
current thread; consider defining a scoped proxy for this bean
Code
#Service
public class MyMessagingReceiver implements SomeMessageReceiver {
private final SomeService someService;
#Autowired
public MyMessagingReceiver(final SomeService someService) {
this.someService = someService;
}
public void onMessage(MessageObject messageObject) {
//possible here to use AutowireCapableBeanFactory in inject the RequestScopeBean bean?
someService.handle(messageObject);
}
}
#Service
public class SomeService {
private final RequestScopeBean requestScopeBean;
#Autowired
public SomeService(RequestScopeBean requestScopeBean) {
this.requestScopeBean = requestScopeBean;
}
public void handle(MessageObject messageObject) {
System.out.println(this.requestScopeBean.getName());
}
}
#Configuration
public class BeanDeclarations {
#Bean
#RequestScope
public RequestScopeBean requestScopeBean() {
return new RequestScopeBean();
}
}
public RequestScopeBean {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class Interceptor extends HandlerInterceptorAdapter {
private RequestScopeBean requestScopeBean;
#Autowired
public Interceptor(RequestScopeBean requestScopeBean) {
this.requestScopeBean = requestScopeBean;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String name = request.getHeader("name");
this.requestScopeBean.setName(name);
}
}

Will I have any collisions coming from the #ControllerAdvice if I use it to populate a #ModelAttribute

This is the main controller for the web entrypoint
#Controller
#RequestMapping("/webapp")
public class WebAppController {
#RequestMapping(value = "/home/{authKey}",method = RequestMethod.GET)
String index(#ModelAttribute MyMeta myMeta, Model model){
System.out.println("Token: "+myMeta.getAccessToken());
return "index";
}
#RequestMapping(value = "/config/{authKey}",method = RequestMethod.GET)
String config(#ModelAttribute MyMeta myMeta, Model model){
return "configure";
}
}
Now if you look at the interceptor you can see how I am creating the #ModelAttribute, and see the implementation
#Component
#ControllerAdvice
public class SessionInterceptor implements AsyncHandlerInterceptor {
MyMeta myMeta;
...
#ModelAttribute
public MyMeta getTest() {
return this.myMeta;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
...
// parse the key from the request
...
MetaMagicKey metaMagicKey = metaMagicKeyRepo.findKeyByMagicKey(key);
// do work here query my DB and build stuff
...
// assign the queried data built into object
this.myMeta = metaMagicKey.getId().getMyMeta();
return true;
}
My question is, I do not know the true inter-workings of Springboot so I am worried if too many people execute this I might have some object swapping, or some kind of collision? There really isn't a clean way to do this and all of the research I've done is torn between using HttpServletRequest#setAttribute() and using #ModelAttribute, I like the route I chose above as it's VERY easy to implement in my methods.
Springboot 1.4.2 - Java 8
EDIT:
What I ended up trying is this, based on several pages I've read.
I created a new component:
#Component
#RequestScope
public class HWRequest implements Serializable {
private MyMeta myMeta;
public MyMeta getMyMeta() {
return myMeta;
}
public void setMyMeta(MyMeta myMeta) {
this.myMeta = myMeta;
}
}
And then My Config class
#Configuration
public class AppConfig extends WebMvcConfigurerAdapter {
UserSessionInterceptor userSessionInterceptor;
#Autowired
public AppConfig(UserSessionInterceptor userSessionInterceptor) {
this.userSessionInterceptor = userSessionInterceptor;
}
#Bean
#RequestScope
public HWRequest hwRequest() {
return new HWRequest();
}
#Bean
public UserSessionInterceptor createUserSessionInterceptor() {
return userSessionInterceptor;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(createUserSessionInterceptor()).addPathPatterns("/user/**");
}
}
And here is the interceptor I modified
#Component
#ControllerAdvice
public class SessionInterceptor implements AsyncHandlerInterceptor {
#Resource
HWRequest hwRequest;
...
#ModelAttribute
public HWRequest getTest() {
return this.hwRequest;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
...
// parse the key from the request
...
MetaMagicKey metaMagicKey = metaMagicKeyRepo.findKeyByMagicKey(key);
// do work here query my DB and build stuff
...
// assign the queried data built into object
this.hwRequest.setMyMeta(metaMagicKey.getId().getMyMeta());
return true;
}
And of course the modified controller to fit my needs
#Controller
#RequestMapping("/user")
public class WebAppUserController {
#RequestMapping(value = "/home/{authKey}",method = RequestMethod.GET)
String index(#ModelAttribute HWRequest request, Model model){
return "index";
}
#RequestMapping(value = "/config/{authKey}",method = RequestMethod.GET)
String config(#ModelAttribute HWRequest request, Model model){
return "configure";
}
}
Based on all of the documentation I've read this should work, but maybe I am missing something as the interceptor is STILL a singleton. Maybe I am missing something?
myMeta variable represents state in singleton bean. Of course it is not thread-safe and various users will get collisions. Do not ever store any of your application state in singleton beans.
If you want to store some state per request, use Spring's request scope. That means creating separate bean just for storing state annotated with #RequestScope annotation
Reaction on EDIT:
This bean registration can be deleted as it is already registered into Spring IoC container with #Component annotation:
#Bean
#RequestScope
public HWRequest hwRequest() {
return new HWRequest();
}
Another piece that is not needed in your AppConfig is autowiring UserSessionInterceptor bean and registering it as bean again. Delete that. As that bean is being autowired it obviously already is in IoC container, so no need to register it again.
Another confusing piece is workd session in naming. As you are dealing with #RequestScope instead of #SessionScope I would advise to change naming of your class to request (e.g. RequestInterceptor). Session vs Request are very different beasts.
Otherwise it looks like it can work and should be thread safe.

Spring runtime use swap bean class

#Component
#Qualifier("SUCCESS")
public class RandomServiceSuccess implements RandomService{
public String doStuff(){
return "success";
}
}
#Component
#Qualifier("ERROR")
public class RandomServiceError implements RandomService{
public String doStuff(){
throw new Exception();
}
}
the calling code
#Controller
public class RandomConroller {
#Autowired
private RandomService service;
public String do(){
service.doStuff();
}
}
What I need to do here is to have them swapped based on a value can be retrieved from some custom http header from a http request. Thank you!
I'm totally agree with Sotirios Delimanolis that you need to inject all the implementations and choose one of them at runtime.
If you have many implementations of RandomService and don't want to clutter RandomController with selection logic, then you can make RandomService implementations responsible for selection, as follows:
public interface RandomService{
public boolean supports(String headerValue);
public String doStuff();
}
#Controller
public class RandomConroller {
#Autowired List<RandomService> services;
public String do(#RequestHeader("someHeader") String headerValue){
for (RandomService service: services) {
if (service.supports(headerValue)) {
return service.doStuff();
}
}
throw new IllegalArgumentException("No suitable implementation");
}
}
If you want to define priorities for different implementations, you may use Ordered and put the injected implementations into a TreeSet with OrderComparator.
Qualifier should be used to specify which instance of the interface you want injected in the field after specifying different IDs for each one. Following #Soritios' advice you could do something like:
#Component("SUCCESS")
public class RandomServiceSuccess implements RandomService{
public String doStuff(){
return "success";
}
}
#Component("ERROR")
public class RandomServiceError implements RandomService{
public String doStuff(){
throw new Exception();
}
}
#Component
public class MyBean{
#Autowired
#Qualifier("SUCCESS")
private RandomService successService;
#Autowired
#Qualifier("ERROR")
private RandomService successService;
....
if(...)
}
...or you could obtain just the instance you want from the application context based on your parameter:
#Controller
public class RandomConroller {
#Autowired
private ApplicationContext applicationContext;
public String do(){
String myService = decideWhatSericeToInvokeBasedOnHttpParameter();
// at this point myService should be either "ERROR" or "SUCCESS"
RandomService myService = applicationContext.getBean(myService);
service.doStuff();
}
}
You can just inject both and use the one you need.
#Inject
private RandomServiceSuccess success;
#Inject
private RandomServiceError error;
...
String value = request.getHeader("some header");
if (value == null || !value.equals("expected")) {
error.doStuff();
} else {
success.doStuff();
}

The decorator pattern and #Inject

When using Spring's based XML configuration, it's easy to decorate multiple implementations of the same interface and specify the order. For instance, a logging service wraps a transactional service which wraps the actual service.
How can I achieve the same using the javax.inject annotations?
You can use #Named together with #Inject to specify which bean to inject.
A simple example with an injected service:
public class ServiceTest {
#Inject
#Named("transactionDecorator")
private Service service;
}
And the corresponding transaction decorator class:
#org.springframework.stereotype.Service("transactionDecorator")
public class ServiceDecoratorTransactionSupport extends ServiceDecorator {
#Inject
#Named("serviceBean")
public ServiceDecoratorTransactionSupport(Service service) {
super(service);
}
}
This exposes your configuration into your code, so I would recommend doing the decorating logic in a #Configuration class and annotate for example the logging service with #Primary. With this approach your test class can look something like this:
public class ServiceTest {
#Inject
private Service service;
And the configuration class:
#Configuration
public class DecoratorConfig {
#Bean
#Primary
public ServiceDecorator serviceDecoratorSecurity() {
return new ServiceDecoratorSecuritySupport(
serviceDecoratorTransactionSupport());
}
#Bean
public ServiceDecorator serviceDecoratorTransactionSupport() {
return new ServiceDecoratorTransactionSupport(serviceBean());
}
#Bean
public Service serviceBean() {
return new ServiceImpl(serviceRepositoryEverythingOkayStub());
}
#Bean
public ServiceRepository serviceRepositoryEverythingOkayStub() {
return new ServiceRepositoryEverythingOkStub();
}
}
My second example doesn't expose any details about which implementation that will be returned, but it depends on several Spring specific classes.
You can also combine the two solutions. For example use Spring's #Primary annotation on a decorator and let Spring inject this decorator into the instance of the given type.
#Service
#Primary
public class ServiceDecoratorSecuritySupport extends ServiceDecorator {
}
This is the sort of thing you typically use AOP for, rather than writing and wrapping implementations manually (not that you can't do that).
For AOP with Guice, you'd want to create a transactional MethodInterceptor and a logging MethodInterceptor, then use bindInterceptor(Matcher, Matcher, MethodInterceptor) to set which types and methods should be intercepted. The first Matcher matches types to intercept, the second matches methods to intercept. Either can be Matchers.any(), match a specific annotation on a type or method (#Transactional, say) or whatever you want. Matching methods are then intercepted and handled automatically. Decorator pattern with a lot less boilerplate, basically.
To do it manually, one way would be:
class ServiceModule extends PrivateModule {
#Override protected void configure() {
bind(Service.class).annotatedWith(Real.class).to(RealService.class);
}
#Provides #Exposed
protected Service provideService(#Real Service service) {
return new LoggingService(new TransactionalService(service));
}
}
#Target(PARAMETER)
#Retention(RUNTIME)
#BindingAnnotation
public #interface Decorate {
Class<?> value();
}
/* see com.google.inject.name.NamedImpl for rest of
the methods DecorateImpl must implement */
public class DecorateImpl implements Decorate, Serializable {
private final Class<?> value;
private DecorateImpl(Class<?> val) {
value = val;
}
public static Decorate get(Class<?> clazz) {
return new DecorateImpl(clazz);
}
public Class<?> value() {
return value;
}
...
...
}
Here is how to use it:
public interface ApService {
String foo(String s);
}
public class ApImpl implements ApService {
private final String name;
#Inject
public ApImpl(#Named("ApImpl.name") String name) {
this.name = name;
}
#Override
public String foo(String s) {
return name + ":" + s;
}
}
First decorator:
public class ApDecorator implements ApService {
private final ApService dcrtd;
private final String name;
#Inject
public ApDecorator(#Decorate(ApDecorator.class) ApService dcrtd,
#Named("ApDecorator.name") String name) {
this.dcrtd = dcrtd;
this.name = name;
}
public String foo(String s) {
return name + ":" + s + ":"+dcrtd.foo(s);
}
}
Second decorator:
public class D2 implements ApService {
private final ApService dcrt;
#Inject
public D2(#Decorate(D2.class) ApService dcrt) {
this.dcrt = dcrt;
}
#Override
public String foo(String s) {
return "D2:" + s + ":" + dcrt.foo(s);
}
}
public class DecoratingTest {
#Test
public void test_decorating_provider() throws Exception {
Injector inj = Guice.createInjector(new DecoratingModule());
ApService mi = inj.getInstance(ApService.class);
assertTrue(mi.foo("z").matches("D2:z:D:z:I:z"));
}
}
The Module:
class DecoratingModule extends AbstractModule {
#Override
protected void configure() {
bindConstant().annotatedWith(Names.named("ApImpl.name")).to("I");
bindConstant().annotatedWith(Names.named("ApDecorator.name")).to("D");
bind(ApService.class).
annotatedWith(DecorateImpl.get(ApDecorator.class)).
to(AnImpl.class);
bind(ApService.class).
annotatedWith(DecorateImpl.get(D2.class)).
to(ApDecorator.class);
bind(ApService.class).to(D2.class);
}
}
If bindings configuration looks ugly, you can create Builder/DSL that looks nice.
The drawback is that (comparing with manual chain building) you can not chain the same module twice (i.e. D2->D2->D1->Impl) and the boilerplate in the constructor params.

Categories

Resources