I'm starting to learn Spring Boot, and I'm following a tutorial on youtube. However, there is an weird thing happening on my project. I just created a Controller called GreetingController. Below is the complete code of the class
#RestController
#EnableAutoConfiguration
public class GreetingController {
private static BigInteger nextId;
private static Map<BigInteger, Greeting> greetingMap;
private static Greeting save(Greeting greeting) {
if (greetingMap == null) {
greetingMap = new HashMap<BigInteger, Greeting>();
nextId = BigInteger.ONE;
}
greeting.setId(nextId);
nextId = nextId.add(BigInteger.ONE);
greetingMap.put(greeting.getId(), greeting);
return greeting;
}
static {
Greeting g1 = new Greeting();
g1.setText("Hello World");
save(g1);
Greeting g2 = new Greeting();
g2.setText("Hola Mundo");
save(g2);
}
#RequestMapping(value = "/api/greetings", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Collection<Greeting>> getGreetings() {
Collection<Greeting> greetings = greetingMap.values();
return new ResponseEntity<Collection<Greeting>>(greetings,
HttpStatus.OK);
}
}
The controller is under the following package:
However, when I bootstrap the application with the URL http://localhost:8080/api/greetings the following error appears on my page:
But, when I put the GreetingController in the same package of Application class, as the image below:
And then get the same URL http://localhost:8080/api/greetings, I got the right response:
Can anyone explain me why?
Rename your com.example package to org.example. Spring boot scans for controllers all subpackages of package of class when you place your #SpringBootApplication annotation.
Or put #ComponentScan("org.example") on the same class. This way you tell spring boot where to search your controllers (and other beans).
If you want to support having a controller in another package, you need to include it in the component scan of your Application.
In Application.java, you could add the following:
#ComponentScan({com.example, org.example})
By default, the package that Application is in will be included in the ComponentScan, which is why it was working for you when you had the controller in the same package as Application.
Also, you don't need the #EnableAutoConfiguration annotation on your controller, FYI.
Related
I am confused a bit,based on this documentation, I should be only putting this annotation #DataJpaTest and it will configure my application context to be only for JPA repositories and that too for an in-memory database. Now the problem is in my application I have a Main class which is annotated with #SpringBootApplication and it is loading rest of the configuration for Web interceptor and many other things from there. In fact it is trying to load the bootstrap.properties file too.
In my understanding it should NOT be using this configuration.
Below is my test code and main class.
#OpenAPIDefinition(info = #Info(title = "Test API", version = "0.1.10-SNAPSHOT", description = "Test API", contact = #Contact(name = "My Team", email = "sample#mycompany.com")), servers = {
#Server(description = "Current Stage Server url", url = "https://mycompany.com/testapics"),
#Server(description = "Stable Stage Server url", url = "https://mycompany.com/testapi") })
#SpringBootApplication(scanBasePackages = { "com.mycompany.utility", "com.mycompany.sample.users",
"com.mycompany.sample.utility", "com.mycompany.another.sample.interceptor",
"com.mycompany.sample.security.vulnerableinput", "com.mycompany.sample.security.nocache.filter",
"com.mycompany.sample.security.common", "com.mycompany.sample.security.csrf",
"com.mycompany.sample.security.nocache" })
#EnableCaching
#EnableAsync
public class SampleApiApplication implements WebMvcConfigurer {
The main class has a bunch of other bean configurations in it. (Omitting those for clarity).
Below is my test class.
#DataJpaTest
public class UsersRepositoryTest {
#Autowired
private UsersRepository usersRepository;
#Test
public void test_usersRepository_findByEmplId() {
List<User> users = getUsersData();
UsersRepository.saveAll(users);
for (User user : users) {
Assert.assertTrue(user.getId() != null);
}
}
private List<User> getUsersData() {
List<User> userList = new ArrayList<>();
User user = new User();
user.setEmpId("XYZ_001");
user.setUserUUID(UUID.randomUUID().toString());
userList.add(user);
return userList;
}
}
According to the Spring Boot Reference:
If you use a test annotation to test a more specific slice of your application, you should avoid adding configuration settings that are specific to a particular area on the main method’s application class.
The underlying component scan configuration of #SpringBootApplication defines exclude filters that are used to make sure slicing works as expected. If you are using an explicit #ComponentScan directive on your #SpringBootApplication-annotated class, be aware that those filters will be disabled. If you are using slicing, you should define them again.
So in your case consider to move annotations like #EnableCaching, #EnableAsync to a separate class.
Okay, so I am pretty new to testing and Spring Boot in general, so please correct me if I am doing something completely wrong here in the first place.
As a project my team and I are making a Web Application using Spring Boot where we are making calls to the Microsoft Graph API in some of our services. See this service for cancelling an event in a user's calendar:
import com.microsoft.graph.authentication.IAuthenticationProvider;
import com.microsoft.graph.models.extensions.IGraphServiceClient;
import com.microsoft.graph.requests.extensions.GraphServiceClient;
import org.springframework.stereotype.Service;
#Service
public class CancelEventService {
public void cancelEvent(final String token, final String id) {
IAuthenticationProvider authenticationProvider = iHttpRequest -> iHttpRequest.addHeader("Authorization", "Bearer " + token);
IGraphServiceClient graphServiceClient = GraphServiceClient.builder().authenticationProvider(authenticationProvider).buildClient();
graphServiceClient.me().events(id)
.buildRequest()
.delete();
}
}
This is working great, but I have been struggling for a couple of days with how to write unit tests for this. The way I see it, I want to either make mocks of the GraphServiceClient, or use a tool like WireMock to make the request go to a mockserver and return some values I configure.
I've tried doing both, but I can't make mocks of GraphServiceClient because it is not a Bean in my project, so I can't figure out how I should proceed to make an implementation I can autowire in to my Service.
When it comes to WireMock I am not even sure I understand if it is capable of doing what I want to, and if it is, I sure haven't been able to configure it correctly (note that we are using JUnit 5 for this project). A simple example where you make a GET-request to Google.com and return "Hello" via WireMock would suffice to get me going here.
Any concrete examples of what I should do, or even just a nod in the right direction would be well appreciated.
Well, I cannot assure you that it will work but it will give you a better landscape of the situation:
1) So first, we need to make a slight change on your service.
Need to extract IGraphServiceClient from the method so we can mock it later, see:
#Service
public class CancelEventService {
private IGraphServiceClient graphServiceClient;
#Autowired
public CancelEventService(IGraphServiceClient graphServiceClient){
this.graphServiceClient = graphServiceClient;
}
public void cancelEvent(final String token, final String id) {
IAuthenticationProvider authenticationProvider = iHttpRequest -> iHttpRequest.addHeader("Authorization", "Bearer " + token);
graphServiceClient = GraphServiceClient.builder().authenticationProvider(authenticationProvider).buildClient();
graphServiceClient.me().events(id)
.buildRequest()
.delete();
}
}
2) The test would look like this:
(Notice that all we are using here is included in spring boot test module, so you shouldn't need to add anything to the project dependencies)
#RunWith(SpringRunner.class)
public class CancelEventServiceTest {
private IGraphServiceClient graphServiceClientMock;
private CancelEventService serviceToBeTested;
#Before
public void setUp(){
graphServiceClientMock = Mockito.mock(IGraphServiceClient.class, RETURNS_DEEP_STUBS);
serviceToBeTested = new CancelEventService(graphServiceClientMock);
}
#Test
public void test_1() {
serviceToBeTested.cancelEvent("token", "id");
verify(graphServiceClientMock, times(1)).me().events("id").buildRequest()
.delete();
}
}
Hope it helps!
As a follow up on this, we found a solution to the problem by creating a class
GraphServiceClientImpl with a method that returns an instantiated GraphServiceClient.
#Component
public class GraphServiceClientImpl {
public IGraphServiceClient instantiateGraphServiceClient(final String token) {
IAuthenticationProvider authenticationProvider = iHttpRequest -> iHttpRequest.addHeader("Authorization", "Bearer " + token);
return GraphServiceClient.builder().authenticationProvider(authenticationProvider).buildClient();
}
}
#Service
public class CancelEventService{
private GraphServiceClientImpl graphServiceClientImpl;
public CancelEventService(final GraphServiceClientImpl graphServiceClientImpl) {
this.graphServiceClientImpl = graphServiceClientImpl;
}
public void cancelEvent(final String token, final String id) {
IGraphServiceClient graphServiceClient = graphServiceClientImpl.instantiateGraphServiceClient(token);
graphServiceClient
.me()
.events(id)
.buildRequest()
.delete();
}
}
Then, our test:
#ExtendWith(MockitoExtension.class)
class CancelEventServiceTest {
#Mock
private GraphServiceClientImpl graphServiceClientImpl;
#InjectMocks
private CancelEventService cancelEventService;
#BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
void cancelEvent_Successful() {
//given
IGraphServiceClient serviceClientMock = mock(IGraphServiceClient.class, RETURNS_DEEP_STUBS);
given(graphServiceClientImpl.instantiateGraphServiceClient(anyString())).willReturn(serviceClientMock);
//when
cancelEventService.cancelBooking("Token", "1");
//then
verify(serviceClientMock, times(1)).me();
}
}
Probably not the optimal solution, but it works. Any other takes on this would be welcome!
I'm trying to implement an OGC API - Features service using Spring Boot. The specification includes links to self, parent collections and such, so I figured Spring HATEOAS could help with that. Unfortunately, I'm having trouble with the bbox-crs parameter.
An example demonstration:
#RestController
public class DemoController {
#GetMapping(path = "/link", produces = "application/geo+json")
public Link getLink(#RequestParam(required = false, name = "bbox-crs") String bboxCrs) {
return linkTo(methodOn(DemoController.class).getLink(bboxCrs))
.withSelfRel()
.expand()
.withType("application/geo+json");
}
}
Test:
#SpringBootTest
#AutoConfigureMockMvc
class DemoApplicationTests {
#Autowired
private MockMvc mockMvc;
#Test
public void getLinkWithParam() throws Exception {
String expectedJson = new ObjectMapper().writeValueAsString(
new Link("http://localhost/link?bbox-crs=hello").withType("application/geo+json"));
mockMvc.perform(get("/link?bbox-crs=hello"))
.andExpect(content().contentType("application/geo+json"))
.andExpect(content().json(expectedJson));
}
#Test
public void getLinkNoParam() throws Exception {
String expectedJson = new ObjectMapper().writeValueAsString(
new Link("http://localhost/link").withType("application/geo+json"));
mockMvc.perform(get("/link"))
.andExpect(content().contentType("application/geo+json"))
.andExpect(content().json(expectedJson));
}
}
The first test succeeds, but the second test fails with the error Illegal character in path at index 23: http://localhost/link{?bbox-crs}.
According to issue #799, this is working as intended, as the URI Template spec does not allow for hyphens in variable names. I'm trying to build an URI, though, not an URI Template. Is there some way to
achieve what I'm after? Maybe by configuring Spring or by creating the link in a different way?
pom.xml version info:
springfox-swagger2: 2.5.0
swagger-core: 1.5.10
springfox-swagger-ui: 2.6.1
springboot: 1.5.3
I has a project with swagger2 and springboot.
The project code without #Aspect works very well.The code reads as follows.
public interface TestApi {
WfExecution test(Long temp);
}
#Api(value = "TestAPI")
#RequestMapping(value = "/test")
#RestController
public class TestApiImpl implements TestApi {
#Override
#RequestMapping(value = "/test")
#ApiOperation(value = "", notes = "", produces = MediaType.APPLICATION_JSON)
public WfExecution test(#ApiParam(value = "", required = true) #RequestParam(required = true, value = "temp")
Long temp) {
return new WfExecution();
}
}
the right result:
But when I add the follow code, the swagger-ui doesn't show the test-api-impl.
#Aspect
#Component
public class LoggerAop {
#Before("execution(* com.XXX.controller.impl.TestApiImpl.*(..))")
public void doBeforeAdvice(JoinPoint joinPoint){
System.out.println("XXX");
}
}
the error result:
Is there a conflict between swagger and spring AOP?
#egg
I setup the similar project and faced a same issue.
After setting the proxyTargetClass property to true in #EnableAspectJAutoProxy annotation as below, the issue got resolved.
#EnableAspectJAutoProxy(proxyTargetClass=true)
This issue occurs only when we are using the interface for controller.
To quote the use of this property EnableAspectJAutoProxy from Java doc.
Users can control the type of proxy that gets created for {#code FooService} using
the {#link #proxyTargetClass()} attribute. The following enables CGLIB-style 'subclass'
proxies as opposed to the default interface-based JDK proxy approach.
This answer is for those who is still facing the issue
#Aspect is basically a filter. If you exclude the swagges resources it will start working.
you can use
#Pointcut("within(com..*..*Controller)")
#Pointcut("within(com..*..*Service)")
so it will scan only those pacakges which is starting from com...
I am running Restful web-service as standalone application using Jersey.
Below are my service classes which serve's the requests.
LoginServiceImpl.java
#Component
public class LoginServiceImpl implements LoginService {
#Value("${login.service.defaultmessage}")
private String defaultMessage;
#Autowired
private EmLoginDAO emLoginDAO;
#Override
public String defaultCall() {
return defaultMessage;
}
#Override
public String updatePassword(List<Login> userList) {
System.out.println(emLoginDAO + "\n" + userList);
emLoginDAO.save(userList);
return "Passwords Updated...";
}
#Override
public List<Login> getPasswords() {
System.out.println("OBJECT: " + emLoginDAO);
List<Login> userList = null;
userList = emLoginDAO.findAll();
return userList;
}
}
LoginService.java
#Component
#Path("/user")
public interface LoginService {
#GET
public String defaultCall();
#POST
#Path(value = "/print")
#Consumes(MediaType.APPLICATION_XML)
#Produces(MediaType.TEXT_PLAIN)
public String updatePassword(List<Login> userList);
#GET
#Path(value = "/getpassword")
#Produces(MediaType.APPLICATION_XML)
public List<Login> getPasswords();
}
Below is my spring configuration file.
<context:component-scan base-package="com.em.login" />
<context:annotation-config />
After starting the service when I call the respective method get called.
But my defaultMessage and emLoginDAO objects are null. So it is not referring to the properties and spring configuration files.
So can any one please help me to get this correct. Or to find a way to set the properties and spring configuration file paths to Jersey.
Update
Closeable server = null;
try {
DefaultResourceConfig resourceConfig = new DefaultResourceConfig(
LoginServiceImpl.class);
resourceConfig.getContainerResponseFilters().add(
new GZIPContentEncodingFilter());
server = SimpleServerFactory.create(serviceurl,
resourceConfig);
System.in.read();
} catch (IOException e) {
} finally {
if (server != null) {
try {
server.close();
} catch (IOException ex) {
}
}
}
I think this is the culprit:
DefaultResourceConfig resourceConfig = new DefaultResourceConfig(LoginServiceImpl.class);
You are using Spring's IOC to create the objects and do the autowiring, but you are not getting the instance from the Spring container. You need to get the LoginServiceImpl instance from the Spring container, and not have Jersey create it (Jersey does not know how to autowire your #Autowired properties.
You should use the Spring integration with Jersey, seen here.
Edit to respond to your comment, you posted this code:
LoginServiceImpl loginServiceImpl = (LoginServiceImpl) SpringApplicationContext.getBean("loginServiceImpl");
DefaultResourceConfig resourceConfig = new DefaultResourceConfig( loginServiceImpl.getClass());
You are creating a loginServiceImpl via the spring container, and I'll bet if you check your autowired fields will be there.
However, the second line where you use loginServiceImpl.getClass() this is going to create a new LoginServiceImpl, which is not the same one as the loginServiceImpl you got from the context, so you still are going to have the same problem.
You could take a look at Microserver, that will do all the wiring between Jersey and Spring for you (and setup a Grizzly webserver). From the tags I notice you are using Spring boot, with Microserver: micro-boot module you can do (in a class in package com.em.login):
public static void main(String[] args){
new MicrobootApp(()->"test-app").run();
}
And it should wire up Grizzly, Jersey & Spring with Spring-boot enabled for any backend (non-Jax-rs) dependencies.
Alternatively without Spring Boot (plain old Jersey and Spring)
public static void main(String[] args){
new MicroserverApp(()->"test-app").run();
}
To do it manually, you will need to add the Jersey-Spring integration jar to your classpath and make sure both are configured in a way that interoperates (i.e. I think a registering Spring ContextListener is essential). There is an example app here.
Have you configured those two in your spring configuration files?
I mean have you annotated EmLoginDAO also as stereotype Component?
I got this working.
Referred the this part of the Jersey documentation.
Below is the code I have used to make this working.
ResourceConfig resourceConfig = new ResourceConfig(LoginServiceImpl.class);
resourceConfig.register(org.glassfish.jersey.server.filter.UriConnegFilter.class);
resourceConfig.register(org.glassfish.jersey.server.spring.SpringComponentProvider.class);
resourceConfig.property(ServerProperties.METAINF_SERVICES_LOOKUP_DISABLE, true);
resourceConfig.property("contextConfigLocation", "classpath:/spring-config.xml");
URI serviceUri = UriBuilder.fromUri(serviceHost).port(servicePort).build();
server = SimpleContainerFactory.create(serviceUri, resourceConfig);
Thank you all for helping.