Understanding Spring Boot Annotations: @Bean vs @Component vs ...

Understanding Spring Boot Annotations: @Bean vs @Component vs ...

A clear, concise introduction to understanding the annotations, and the differences between the different annotations in Spring Boot

Annotations make up a huge part of Spring Boot. In fact, that's how Spring Boot came up — providing annotations as configurations, instead of manually defining the configurations in an XML file.

However, when starting in Spring Boot, these are the concepts that always baffle us, and often, we just use whatever "seems right" or copied from another class without understanding.

This article should shed some light on how these annotations come together in Spring Boot.

Bean — The Foundation

Coffee Beans

The core annotations in Spring Boot, like @Service, @Component, @Configuration, @Repository, and of course, @Bean. While each has a different meaning, their main purpose is to define a bean.

So what is a bean?

Beans as Singleton

Each bean will exist as a singleton in your application. In a formal definition,

Singleton ... ensures that a class has only one instance, while providing a global access point to this instance.

Source: Refactoring Guru

That is, in an application, there will only be a single instance of the class. For example, using the @Component annotation to define a bean

@Component
public class CustomerFactory {
  ...
}

As a result, in your Spring application, there will be only one, globalCustomerFactory instance.

To access these beans, we will leverage a second characteristic of Spring Boot applications — Dependency Injections.

Dependency Injection — Bringing Beans Together

Dependency Injection

What if one bean needs to access another bean's functionality? For instance, my CustomerService class would need to access the database via CustomerRepository(I'll explain these later, I promise), and this is where Spring Boot uses Dependency Injection

Dependency injection means giving an object its instance variables.

Source: James Shore's Blog

Since Spring Boot instantiates all the beans when starting up, Spring Boot will also pass the beans to where needed. Typically, it can be done by declaring them in the constructor or using the annotation@Autowired.

Personally, I prefer using Lombok's @RequiredArgConstructor, which will automatically create a constructor with all of the class's final fields.

@Service
@RequiredArgsConstructor
public CustomerService {
  private final CustomerRepository customerRepository;

  // The constructor created by @RequiredArgsConstructor,
  // doesn't need to be created manually anymore.
  /* 
  * public CustomerService(CustomerRepository customerRepository) {
  *   this.customerRepository = customerRepository;
  * }
  */
}

When instantiating this CustomerService, Spring Boot will look at the constructor, and pass the CustomerRepository required.

(It's kinda like building a dependency graph, so if CustomerService needs CustomerRepository, it will then look into CustomerRepository, see what it needs and builds it up)

Note: A circular dependency can happen if say Service A requires Service B, and Service B needs Service A. Try to avoid it if possible, otherwise you'll need to work around it.

Learning Annotations

Everything is a bean!

Now that you understand what exactly a bean is in Spring Boot, let's see what each type of bean does.

Note: All of the following annotations will make the class a bean, however, some of them do come with additional functionalities.

@Component

@Component is the most fundamental bean annotation in Spring Boot, the class will be turned into a bean with this annotation.

@Component
@RequiredArgsConstructor // this is to automatically creates constructor
public class CustomerFactory {
  // Spring Boot will inject any bean required
  private final CustomerService customerService;
}

@Component
@RequiredArgsConstructor
public class CustomerGroupFactory {
  // and the component bean can also be freely injected in other beans
  private final CustomerFactory customerFactory;
}

@Service

Actually, it's just the same as @Component. It just makes the class a bean.

@Service is more of a semantic feature, which marks that the class is one in the Service Layer. That is, the service is where the business logic is, here is where you process the data from users, save them in the database using the repository, and return the data back to the user via the controller.

Here's a very simple demonstration of the use of Service. While simplified, most CRUD related operations, in their essence, are just like this.

@Service
@RequiredArgsConstructor // creates a constructor to inject CustomerRepository
public class CustomerService {
  private final CustomerRepository customerRepository;

  public CustomerDetails saveCustomer(SaveCustomerRequest request) {
    // First, we process the data from client, turn it into a backend object
    Customer customer = new Customer(request.name, request.email,...);

    // Then we save it into database via repository
    Customer savedCustomer = customerRepository.save(customer);

    // Lastly, we transform the data and pass it back to user
    return new CustomerDetails(savedCustomer.id, ...);
  }
}

@Configuration

Again, like @Component, it marks a class to be a bean.

So it is more of a semantic feature again. It means that the class is a configuration class that contains configuration beans.

This typically occurs when we want to override the default behavior of Spring Boot, that is, override the default implementation of Spring Boot. A common example would be to configure the security.

Of course, this configuration class can also consist of custom configuration specific to your application, for example, you might be defining a RedisTemplate to create a Redis client connected to your Redis cluster.

As @Configuration is usually used with @Bean, the example will go below.

@Bean

It's finally something different. @Bean will mark the object returned from a method a bean.

Yupp, not the class, but the return type of the method.A

Beside the examples above, you might also want to create a RestTemplate, to send REST API calls to 3rd party applications that only provide REST endpoints for integrations. Let's say this RestTemplate bean is specifically for this integration, so you'd want to set the base URL for it.

@Configuration
public class WebConfig {
  // Retrieve the URL from configuration properties file
  @Value("${some-application.url}")
  private String someApplicationBaseUrl;

  @Bean("some-application")
  public RestTemplate getRestTemplate() {
    RestTemplate template = new RestTemplate();
    template.setUriTemplateHandler(
        new DefaultUriBuilderFactory("https://some-application.com")
    );
    return template;
  }
}

So now, we have a RestTemplate bean in our application! And as you might have multipleRestTemplate beans around since you might want to integrate with multiple 3rd parties, that's why we have annotated the bean with the name "some-application".

If we wish to inject this particular RestTemplate bean with name "some-application", we have to specify it via the @Qualifier annotation.

@Service
@RequiredArgsConstructor
public class CustomerService {
  private final CustomerRepository customerRepository;
  private final RestTemplate someApplicationRestTemplate;

  public CustomerService(CustomerRepository customerRepository,
        @Qualifier("some-application") RestTemplate restTemplate) {
    this.customerRepository = customerRepository;
    someApplicationRestTemplate = restTemplate;
  }
}

Unfortunately, the @RequiredArgsConstructor won't work with @Qualifier annotation, so we have to manually define the constructor. An alternative is to use @Autowired annotation, but personally, I prefer constructor way to have more control.

Note: You can also pass the name in other annotations, but the bean names are typically used in@Beanonly.

@Controller

Back to the class bean again! @Controller will make the class a bean and semantically indicate that the class is at the controller layer.

The controller layer means this class exposes the API endpoints for clients to consume.

For example, you might want to allow users to create a Customer by POSTing to /customers

@Controller
@RequestMapping("/customers") // The base URL of the API from this class
@RequiredArgsController
public class CustomerController {
  private final CustomerService customerService;

  @PostMapping("") // Accepts POST requests to the base URL
  public ResponseEntity<CustomerResponse> create(
      @RequestBody customerRequest) {
    Customer customer = customerService.create(customerRequest);
    ...
  }
}

Summary

That's pretty much it, some of the most commonly used annotations in Spring Boot. They might seem a little magical, but annotations in Java essentially serve a single purpose

Annotations provide instructions / metadata to the JVM / compiler to build the codes

As a result, the annotations can add additional codes, like how Lombok's RequiredArgsConstructor can generate constructors for you using annotations. Likewise, Spring Boot's @Component makes the class a singleton bean.

Example annotation from Spring Security

Example documentation on annotations from Spring Security

Beside these bean-related annotations, Spring Boot has many other annotations, and sometimes can only be found via their documentation. It does take some form of experience for you to slowly build your understandings and knowledge towards Spring Boot.

Unfortunately, at least for me, Spring Boot's documentations isn't always easy to navigate