I am new to annotation based controller. I have two servlet like this:
pathA-servlet for url: pathA/*
pathB-servlet for url: pathB/*
And I have a controller like:
public class MyController extends SimpleFormController {
private MyService myService;
}
And two service implementation:
public class MyService1 implements MyService {
}
public class MyService2 implements MyService {
}
And in pathA-servlet:
<bean name="/doSomeThing" class="MyController">
<property name="myService" ref="myService"/>
</bean>
<bean id="myService" class="MyService1"/>
And in pathB-servlet:
<bean name="/doSomeThing" class="MyController">
<property name="myService" ref="myService"/>
</bean>
<bean id="myService" class="MyService2"/>
Now, I am trying to do the same with annotation based controller using: #Controller, #RequestMapping. How can I do that?
Here is a sample #Controller. This is a rest endpoint, you can access it with
#Controller
#RequestMapping(method = RequestMethod.POST, value = "/my")
public class CopyOfMyController {
#Autowired
private MyService service;
#RequestMapping(method = RequestMethod.POST, value = "/hib")
public void haha(#ResponseBody RequestDTO dto) {
service.doSomething(dto);
}
}
you can hit it with
Dto dto = new Dto();
dto.setPhone("12313");
RestTemplate restTemplate = new RestTemplate();
restTemplate.postForObject(new URI("http://localhost:8080/my/hib"), dto, Dto.class);
It's very simple. In your controller, first add the #Controller annotation. This annotation simply says that this class will be a Spring controller that will be able to handle HTTP Requests based on the url mapping defined in the methods of your controller.
Also add an #Autowired annotation for the service attribute. Since there is 2 implementations of MyService, add the #Qualifier by passing the bean name because Spring would unable to choose which bean to inject otherwise.
So you can do something like this :
#Controller
public class MyController {
#Autowired
#Qualifier("bean1") // This should be bean1
private MyService myService1;
#Autowired
#Qualifier("bean2")
private MyService myService2;
#RequestMapping(value = "/doSomeThing1", method = RequestMethod.GET)
public String doSomething(){
return myService1.doSomething();
}
#RequestMapping(value = "/doSomeThing2", method = RequestMethod.GET)
public String doSomething(){
return myService2.doSomething();
}
}
Related
I have a rest controller like this :
#Slf4j
#RestController
#RequestMapping(...)
public class MyController {
private MyService service;
public MyController(MyService service){
this.service = service;
}
And the service class is a component :
#Component
public class MyService{
...
}
And when I run the program, the service field is correctly injected. But how is it injected (there is no autowired annotation neither on filed ni on constructor) ?.
I am using SpringBoot 2.0.
From Spring 4.3 release. According to the documentation (https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-spring-beans-and-dependency-injection), if a bean has single constructor, #Autowired annotation can be omitted.
If a bean has one constructor, you can omit the #Autowired, as shown in the following example:
#Service
public class DatabaseAccountService implements AccountService {
private final RiskAssessor riskAssessor;
public DatabaseAccountService(RiskAssessor riskAssessor) {
this.riskAssessor = riskAssessor;
}
// ...
}
I am trying to access application.properties values in spring boot application's service class. But every time value is null, so it throws NullPointException. I can get the right value for port in controller class(if i add #Autowired in controller) but not in service class. What changes are required to make this properties available through out the application?
Controller looks like:
#RestController
public class MyController {
MyService ss = new MyService();
#RequestMapping(value = "/myapp/abcd", method = RequestMethod.POST, consumes = {"application/json"})
public ResponseMessage sendPostMethod(#RequestBody String request) {
log.debug(" POST Request received successfully; And request body is:"+ request);
ResponseMessage response = ss.processRequest(request);
return response;
}
}
And Service class is:
#Service
public class MyService {
private ApplicationProperties p;
#Autowired
public setProps(ApplicationProperties config) {
this.p = config;
}
public ResponseMessage processRequest(String request) {
System.out.println("Property value is:"+ p.getPort());
}
}
ApplicationProperties.java looks like:
#Component
#Getter
#Setter
#ConfigurationProperties
public class ApplicationProperties {
String port;
}
Finally, application.properties file has:
port=1234
I have even tried passing ApplicationProperties instance from controller to service's default constructor, but it did not work. When I debug, value persist while application startup, but when I make a rest web service POST call, it is null.
In your controller MyController, you have to inject the service, replacing:
MyService ss = new MyService();
by:
#Autowired
MyService ss;
Also, instead of your ApplicationProperties class, you can use #Value annotation, from Spring, to load properties from application.properties file. Take a look at this code:
import org.springframework.beans.factory.annotation.Value;
// ...
#Service
public class MyService {
#Value("${port}")
private String port;
// ...
}
I had the same issue but for other reasons.
For me the solution was to add spring. before each parameter in application.properties.
So e.g. "spring.flyway.user=root".
HelloController.java
#RestController
class HelloController {
#GetMapping(value = "{id}/hello")
public ModelAndView listAPI(#PathVariable("id") String profileId) {
ModelAndView mav = new ModelAndView();
return mav;
}
}
HelloControllerTest.java
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = HelloConfigTest.class)
class HelloControllerTest {
#Inject
private WebApplicationContext webApplicationContext;
#Inject
private Foo mockFoo
#InjectMocks
HelloController helloController;
private MockMvc mockMvc;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void testHello() throws Exception {
mockMvc.perform(
get("/{id}/campaigns", "id1"))
.andExpect(status().isOk()));
}
}
// I have another test that directly calls the controller method.
// So I need #InjectMocks to get an instance of the controller
#Test
public void test2() {
when(mockFoo.getX()).thenReturn(true);
helloController.saveAPI();
}
HelloConfigTest.java
#Configuration
#ComponentScan("com.test.controller")
class HelloConfigTest {
#Bean
public mockFoo() {
return Mockito.mock(Foo.class);
}
}
The response that I get here is 404 and I expect 200.
But it works and I get 200 if I change #GetMapping to #RequestMapping(value="{id}/hello", method=RequestMethod.GET)
Am I missing anything here ?
Your configuration is extremely bare bones
#Configuration
#ComponentScan("com.test.controller")
class HelloConfigTest {
It doesn't register any Spring MVC infrastructure beans, either implicitly or explicitly.
When MockMvc, internally, creates a TestDispatcherServlet to test your #Controller class, it has to defer to some default Spring MVC infrastructure types.
Among these infrastructure types is HandlerMapping which is
to be implemented by objects that define a mapping between requests and handler objects.
The default implementation used by the TestDispatcherSerlet is DefaultAnnotationHandlerMapping (an old class) which looks for #RequestMapping specifically, it doesn't recursively do a meta-annotation lookup. Your #GetMapping annotated method is therefore not found and not registered as a handler.
If, instead, you configure your application context with #EnableWebMvc
#Configuration
#ComponentScan("com.test.controller")
#EnableWebMvc
class HelloConfigTest {
Spring will implicitly register a RequestMappingHandlerMapping, which does do this "recursive" lookup for the annotation hierarchy (called merging). Since #GetMapping is annotated with #RequestMapping, the annotated handler method will be found and registered.
As for the #InjectMocks, note that the instance referenced by the field is different from the one used to handle the request performed by the MockMvc object. The former is managed by Mockito, the latter by Spring.
How to intercept #RestController methods using MethodInterceptor in SpringBoot?
Prior to using springboot, I have a simple Interceptor that logs the execution time of a bean method. Simply intercepts all spring bean methods through a default proxy definition.
#Component
public class MethodTimer implements MethodInterceptor {
#Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
final StopWatch stopWatch = new StopWatch();
stopWatch.start(methodInvocation.getMethod().toGenericString());
try {
return methodInvocation.proceed();
}
finally {
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
}
}
The spring configuration where com.mypackages contains the beans and the MethodInterceptor implementation.
<context:component-scan base-package="com.mypackages" />
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<property name="proxyTargetClass" value="true"/>
</bean>
The configuration above is loaded via
#ContextConfiguration("/application-context.xml")
Method executions are logged, everything seems to be working, life is good.
Moving to SpringBoot
I use the same code, switched to annotations (eliminating the *.xml) configuration
#SpringBootApplication(scanBasePackages = { "com.mypackages" })
#EnableAutoConfiguration
#EnableAspectJAutoProxy(proxyTargetClass=true)
Added a constructor to the MethodTimer with System.out.println() to make sure that the Interceptor has been loaded by spring boot
public MethodTimer() {
System.out.println("MethodTimer - Constructor");
}
And yes, it was loaded and created as "MethodTimer - Constructor" is found in the console logs.
However, none of the #RestController methods are intercepted. Below is a simple Rest Controller
#RestController
#RequestMapping("/test")
public class HelloWorldService {
#RequestMapping(method = RequestMethod.GET)
public String sayHello() {
System.out.println("Hello World!");
return "Hello World!";
}
}
Even tried creating a pure spring bean via #Component and #Autowired it in the #RestController to see if that pure spring bean is intercepted, but it was not intercepted as well.
A simple Test service bean. The test() method should be intercepted.
#Component
public class TestService {
public void test() {
System.out.println("TestService.test()");
}
}
Revised RestController
#RestController
#RequestMapping("/test")
public class HelloWorldService {
#Autowired
private TestService testService;
#RequestMapping(method = RequestMethod.GET)
public String sayHello() {
testService.test();
return "Hello World!";
}
}
Notes
The MethodTimer was loaded as the constructor was called showing the System.out.println log in the console, it seems however spring boot did not automatically determine that this bean/component is implementing MethodInterceptor.
The simple Spring Bean annotated with #Component bean was not intercepted.
The Rest Controller annotated with #RestController was not intercepted.
I have tried adding #Component, #Service in the RestController, it did not work.
I have tried tried #EnableAutoConfiguration, adding spring.aop.* configurations in the application.properties, did not work.
Tried using SpringBoot version 1.5.4.RELEASE, does not work.
I know I can always try to use Aspect as shown in the spring-boot-sample-aop example: https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples/spring-boot-sample-aop
But not able to use previous spring code and configure it with springboot is just too lame. If MethodInterceptor and DefaultAdvisorAutoProxyCreator is not supported anymore in spring boot, it should have been deprecated
I have a spring application and want to create a bean at runtime per request to inject it into another class, just like #Producer for CDI.
My bean is just a simple POJO:
public class UserDetails {
private String name;
// getter / setter ...
public UserDetails(String name) {
this.name = name;
}
}
My producer class looks like this:
#Configuration
public class UserFactory {
#Bean
#Scope("request")
public UserDetails createUserDetails() {
// this method should be called on every request
String name = SecurityContextHolder.getContext()
.getAuthentication().getPrincipal(); // get some user details, just an example (I am aware of Principal)
// construct a complex user details object here
return new UserDetails(name)
}
}
And this is the class where the UserDetails instance should be injected:
#RestController
#RequestMapping(value = "/api/something")
public class MyResource {
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public List<String> getSomething(UserDetails userDetails) {
// the userdetails should be injected here per request, some annotations missing?
// do something
}
}
The problem is that Spring complains at runtime about no default constructor (of course).
Failed to instantiate [UserDetails]: No default constructor found
But this is intended and I want to call my own factory to let it handle the Instantiation.
How can I achieve this? Why is UserFactory never called?
Basically you aren't using your scoped proxy. You cannot inject a scoped proxy into a method, you have to inject it into your controller.
public List<String> getSomething(UserDetails userDetails) { ... }
This will lead to spring trying to create a new instance of UserDetails through reflection, it will not inject your scoped bean. Hence it complains about the fact you need a default no-args constructor.
Instead what you should do is wire the dependency into your controller instead of the controller method.
#RestController
#RequestMapping(value = "/api/something")
public class MyResource {
#Autowired
private UserDetails userDetails;
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public List<String> getSomething() {
// the userdetails should be injected here per request, some annotations missing?
// do something
}
}
The idea is that the UserDetails is a scoped proxy and when used will either use the already present object or create a new one based on the #Bean method.
Additonally, the #Scope annotation in the UserFactory has to be modified as follows in order to work:
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
You're trying to inject a request scoped bean on a singleton bean.
You need to use a proxy for the UserDetails
#Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
You need a no arguments constructor in class UserDetails.
public class UserDetails {
private String name;
// getter / setter ...
public UserDetails() {
}
public UserDetails(String name) {
this.name = name;
}
}