Understanding Spring Boot's @Controller and @Component Annotations
Written on
Chapter 1: Overview of Spring Annotations
Spring provides a variety of annotations designed for specific tasks, with several stereotype annotations sometimes appearing to conflict with the overarching @Component annotation.
The @Component annotation serves as a generic stereotype for any Spring-managed bean. It includes an optional String parameter that designates the logical name for the component, allowing it to be recognized as a Spring bean during auto-detection processes, such as those initiated by @ComponentScan.
The @Component annotation, found within the org.springframework.stereotype package, is further refined into specialized annotations that articulate the intended roles of various types and methods within the overall architecture:
- @Repository: This annotation indicates that the annotated class encapsulates CRUD operations as Data Access Object (DAO) classes, potentially throwing Spring exceptions like DataAccessException and PersistenceExceptionTranslationPostProcessor.
- @Service: This annotation signifies that the class provides operational support for business logic without encapsulating methods. Typically, it utilizes repository classes to carry out CRUD operations.
- @Controller: Primarily used in the presentation layer, this annotation marks classes intended to act as web controllers, often in conjunction with other annotated handler methods. With the Spring MVC framework, the @RestController annotation serves as a specialized variant, incorporating both @Controller and @RequestMapping while assuming @ResponseBody semantics by default.
In this discussion, we will evaluate whether @Component and @Controller annotations can be used interchangeably and analyze the potential impacts on class functionality and overall architecture. Given the plethora of annotations in Spring, we will focus on a practical API example.
Requirements:
- An existing Spring Boot project or a cloned version from here.
- Insomnia for API testing.
- IntelliJ IDE.
- Docker desktop installed on your machine.
Chapter 2: Replacing @Controller with @Component
This project centers around a GraphQL book API utilizing the latest Spring Boot GraphQL starter. For additional details, refer to my article on this topic. According to the Spring for GraphQL specification, all query resolver classes are designated as @Controller. To assess the implications, we will replace the @Controller annotation in the BookQuery class with @Component, with the understanding that controllers are, by nature, components.
I also included logging within the allBook method to trace its invocation.
Testing the Modified BookQuery Class
After modifying the BookQuery class, we will build the project and launch the Docker containers. The Maven build should complete without errors or warnings, as @ComponentScan will identify all annotated component classes.
mvn clean install
docker-compose up -d
Upon executing the query in Insomnia, we discover that it did not function as expected. Let’s examine the container logs for any errors.
The logs reveal no errors, and the log entry "starting book" is absent, indicating that the allBook method was not invoked. Consequently, the BookQuery class is registered as a basic bean within the Spring context, and the endpoints are not exposed. To make the classic GraphQL endpoint available, we will add the @RequestMapping at the class level, along with the @PostMapping annotation, since all GraphQL methods are POST requests.
class BookQuery:
@RequestMapping("/graphql")
@PostMapping
With these adjustments, we are effectively defining a controller without using the @Controller annotation. We will now proceed to build Maven again and restart the book-api container to run with the latest JAR.
mvn clean install
docker restart book-api
After the container is operational, we will test the allBook query once more.
#### Verifying the allBook Query
Great news! The query now works. However, there's an issue: allBook is returning an ID, even though it was not specified in the GraphQL request. It appears that the @SchemaMapping annotation is no longer functioning as intended.
The @RequestMapping annotation has made the /graphql endpoint available, returning the method associated with the @PostMapping annotation. At this stage, it seems we can no longer classify this as a GraphQL request; it behaves like a standard REST API request.
Exploring Other Methods
What about other methods in this API? Let’s evaluate the allAuthor method within the BookQuery class.
The response remains identical to allBook, indicating that the specified endpoint is yielding the same result. We will also test the newBook mutation method.
class Mutation:
def newBook(self):
# Expected behavior for newBook
Both methods are returning the same results as allBook, which is not the anticipated outcome. This suggests that the changes made have disrupted the entire API functionality. It could be due to utilizing the /graphql endpoint provided by the Spring GraphQL starter.
To investigate further, let’s modify the endpoint to "/books" in the BookQuery class.
class BookQuery:
@RequestMapping("/books")
After rebuilding Maven and rerunning the Docker container, we will now test the /books endpoint.
#### Results of Modifying the Endpoint
The allBook query works successfully at the newly exposed /books endpoint, but we receive an error message stating "failed to fetch schema" in Insomnia, which aligns with our adjustments in the BookQuery class.
Now, let’s test the newBook mutation again, which should generally be accessible via the /graphql endpoint.
newBook graphql mutation test in Insomnia
This method operates correctly. As a result, the original API has transitioned into a peculiar blend of REST endpoint "/books" and GraphQL endpoint "/graphql".
Conclusion: Can You Replace @Controller with @Component?
Reflecting on our experiment, the answer is no; we cannot substitute a Controller for a Component merely by exchanging annotations. This approach fails because the @Component annotation does not include the necessary RequestMapping and ResponseBody annotations that enable Spring to detect and expose the endpoints, which are integral to the Controller annotation. This observation holds true for the other stereotype annotations and their respective functions within the API architecture.
References: