Are you confused on what’s the difference between @Component and @Service in Spring? Your not alone! The confusion over when to use @Service vs. @Component is felt by all who learn Spring. In this post, you’ll learn when and why you should favor the @Service over @Component annotation through some actual use cases.
Spring @Component | Stereotype Annotations
In Spring we have annotations that can represent different layers of our applications, therefore provide some type of service. The @Component annotation is the parent in a hierarchy of annotations categorized as stereotype annotations.
What are stereotype annotations? These annotations are used to create Spring beans automatically in the application context by scanning for anything of type @Component. Once Spring has your beans in the application context via its classpath @Component scanning, it can perform autowiring for you. Essentially, if your annotation is of type @Component, it gets picked up.
Our applications may be using many stereotype annotations to identify different boundaries/layers of our application. Here are some common suspects that you will comes across …
- @Component – Identifies a Java Class that is to be registered as a Spring Bean
- @Controller or @RestController – Web Layer services like mapping HTTP request to handler methods and processing response
- @Repository – Vendor neutral Exception Translation Service on DAO Classes – DataAcessException
- @Configuration – Java based configuration/code representing a factory to create Spring beans
- @Service – does nothing special, What? I’m confused!
At this point it should be clear that we could directly use @Component to wire up our Java Classes as Spring beans and inherit the magic of Dependency Injection via annotations like @Autowired.
All these stereotype annotations were created to introduce some specialized functionality (they actually do something that @Component doesn’t). However, one annotation from this group does not do anything extra! None is more confusing than the @Service annotation. So why even use it?
Why use @Service in Spring?
The commonly repeated answer given is “intent”. Meaning that it’s intent is to clearly identify and document your applications business layer’s entry point (AKA: Your Service Layer). This is considered a best practice.
Why is using @Service considered best practice? Your @Service layer classes should have your DAO @Repository Spring beans dependency injected into them thereby achieving a clean separation of concern. These service layer methods should not have any explicit data access code (like an SQL query) and should only throw business level exceptions back to the caller – not an SQLException for example.
Could you have functionally achieved the same thing with @Component? The answer is “yes”! The @Service doesn’t do anything special like the other stereotype annotations other than adding some meta info context in terms of identifying your service layer. Still not sold? Neither was I. I get the whole “intent” thing but I need more meat!
You can view the YouTube Version of this Blog here.
When to use @Service in Spring | Use Cases
Over the years, I have encountered actual use cases for when to use @Service over @Component. Have you ever heard of AOP (Aspect Oriented programming) ? Without going deep into the subject, Spring uses it as the mechanism to inject much of the magic behind its services like @Transactional for Transactions, @PreAuthorize for Spring Security etc .. This allows for the service to be decoupled from the application’s code and to advice it via dynamic runtime proxying.
We can also leverage Spring AOP to define and introduce our own services into our application. These custom services advise our business layer non-private methods by matching them together via a regular expression type syntax called a Pointcut expression.
I have found that there are times that you need to specifically identify (match) when your service level methods are invoked. This is where using the @Service annotation in a AOP pointcut expression becomes very useful.
I have used AOP with @Service to …
- Intercept an exception thrown from @Service methods by coding an @Aspect with an @AfterThrowing Advice
- Develop metrics to calculate the total time it takes to execute @Service level methods via an @Around AOP Advice
If we use @Component instead of @Service, it becomes more challenging to identify which @Component actually has the service methods – since we may have many other @Component Classes. This means that our AOP Pointcut has to be more specific which introduces some potential issues. Let me explain.
AOP Pointcut using @Component | The Problem
Here is an example (available on MVP Java’s GitHub) of a service class annotated with @Component (instead of @Service) which we will try to match via an AOP Pointcut.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package com.mvpjava.demo; import org.springframework.stereotype.Component; @Component public class DiscountGeneratorService implements DiscounterService { final DiscountRepository repo; public DiscountGeneratorService(final DiscountRepository repo) { this.repo = repo; } @Override public double calculateDiscountFor(Product product) { Double discount = repo.getRandomDiscount(); if (discount > 0.9) { throw new BusinessException(); } return discount; } } |
Here is the AOP Pointcut …
1 |
@Before("@within(org.springframework.stereotype.Component)") |
which will intercept all non-private methods @within a Class annotated with @Component. This AOP Advice is dynamically weaved at runtime through a dynamic proxy which intercepts any target methods @Before their execution.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package com.mvpjava.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Aspect @Component public class ServiceVsComponentAspect { private Logger logger = LoggerFactory.getLogger(getClass()); <span class="pl-c">@Before("@within(org.springframework.stereotype.Component)")</span> public void implLogging(JoinPoint joinPoint) { logger.info(" Around advice matched Class [" + joinPoint.getSignature().toLongString() + "]"); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package com.mvpjava.demo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class BootstrapCommandLineRunner implements CommandLineRunner { private static final Logger logger = LoggerFactory.getLogger(DiscountRepository.class); final DiscountGeneratorService service; public BootstrapCommandLineRunner (DiscountGeneratorService service) { this.service = service; } @Override public void run(String... args) throws Exception { Product product = new Product("Spring Book", 19.99D); logger.info("Random discount is " + service.calculateDiscountFor(product)); } } |
As expected. the pointcut matches the method in our service Class DiscountGeneratorService however we also matched (collateral damage) another Spring bean @Component with the same package named BootstrapCommandLineRunner. The following is a small extract from the console log to show what was matched.
1 2 3 |
Around advice matched Class [public transient void com.mvpjava.demo.BootstrapCommandLineRunner.run(java.lang.String[])] Around advice matched Class [public double com.mvpjava.demo.DiscountGeneratorService.calculateDiscountFor(com.mvpjava.demo.Product)] Random discount is 0.6531526592463475 |
What do you think will happen if someone adds more @Component Classes in the future?! We actually open up the possibility of matching even more @Component classes (false matches) which we do not consider at our Service layer. The issue is that our AOP Pointcut expression is too broad because @Component is too polymorphic.
Looks like we have to develop a more specific pointcut expression to precisely match the method in our service class DiscountGeneratorService. Let´s try this (there are many variations but I show only one) …
1 |
@Before("execution(double calculateDiscountFor(*)) && @within(org.springframework.stereotype.Component)") |
Well that works fine now (just matches what we want) but that looks harder to read and maintain, right? What about that wildcard “*”. What does that mean? It actually represents “matches once for a return type, package, class,method name or argument”.
What will happen if we change the return type of double to BigDecimal in the future? This will break – no matches. In the end, our poincut is getting increasingly brittle as we start going down the path of being more specific. Can we do better? Yes! read on.
AOP Pointcut using @Service| The Solution
Let’s go back to our DiscountGeneratorService Class and replace @Component with @Service.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@Service public class DiscountGeneratorService implements DiscounterService { final DiscountRepository repo; public DiscountGeneratorService(final DiscountRepository repo) { this.repo = repo; } @Override public double calculateDiscountFor(Product product) { Double discount = repo.getRandomDiscount(); if (discount > 0.9) { throw new BusinessException(); } return discount; } } |
We can clean this up by leveraging the @Service annotation in the AOP Poincut expression. This will allow us to determine what type of stereotype annotation we are working with since we only place @Service on Classes which we determine are at our applications service layer, that is our intent!
1 |
@Before("@within(org.springframework.stereotype.Service)") |
Using the above Pointcut expression with @Service allows us to precisely target the business layer entry point to our application and set it apart from the other layers. This leads to simpler and more maintainable poincut expressions that are not as brittle. Extra non-private methods added to @Service classes will get picked up and not others under @Component.
So there is a difference between @Component and @Service when we need to clearly identify them when using AOP.
Composite Annotations using @Service
From there, we can even start creating our own composite stereotype annotations. Image you created a @TransacationalService annotation which automatically leveraged Spring’s @Transactional service for all your @Service methods.
1 2 3 4 |
@Service @Transactional(timeout = 42) @Retention(RetentionPolicy.RUNTIME) public @interface TransactionalService {} |
This reads much better in the code and allows you to come up with even simpler pointcut expressions like the following ..
1 |
@Before("@within(com.mvpjava.demo.TransactionService)") |
Of course there is always the chance that Spring may add some future functionality to @Service – I’ve been reading that statement for years! If so, I hope it would be an opt-in and not shoved down our throats as this could negatively impact the current application’s behavior.
Summary | @Component vs @Service in Spring
Although Spring’s @Service doesn’t add any extra functionality over @Component out of the box, the @Service annotation becomes very useful when used with Spring AOP in targeting your business layer entry point and injecting a service of your own. You will improve the maintainability of your AOP Pointcut expressions which read as clearly as your intent.