Where Should the Spring @Service Annotation Be Kept? – Spring @Service注解应该保存在哪里?

最后修改: 2021年 1月 27日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

As software developers, we’re always looking for the best practices for using a given technology or library. Naturally, there are debates sometimes.

作为软件开发者,我们总是在寻找使用某种技术或库的最佳实践。自然,有时也会有争论。

One such debate is regarding the placement of Spring’s @Service annotation. Since Spring provides alternative ways to define beans, it’s worth paying attention to the whereabouts of stereotype annotations.

其中一个争论是关于Spring的@Service注解的位置。由于Spring提供了定义Bean的其他方法,所以值得关注定型注解的位置。

In this tutorial, we’ll look at the @Service annotation and examine whether it works best to place it on interfaces, abstract classes, or concrete classes.

在本教程中,我们将看看@Service注解,并研究将其放在接口、抽象类或具体类上是否效果最佳

2. @Service on Interfaces

2.@Service关于接口的问题

Some developers may decide to put @Service on interfaces because they want to:

一些开发者可能会决定把@Service放在接口上,因为他们想这样做。

  • Explicitly show that an interface should only be used for service-level purposes
  • Define new service implementations and have them automatically detected as Spring beans during startup

Let’s see how it looks if we annotate an interface:

让我们看看如果我们注解一个接口会是什么样子。

@Service
public interface AuthenticationService {

    boolean authenticate(String username, String password);
}

As we notice, AuthenticationService becomes more self-descriptive now. The @Service mark advises developers to use it only for the business layer services and not for the data access layer or any other layers.

正如我们所注意到的,AuthenticationService现在变得更具有自我描述性。@Service标记建议开发者仅将其用于业务层服务,而不是用于数据访问层或任何其他层。

Normally, that’s fine, but there’s a drawback. By putting Spring’s @Service on interfaces, we create an extra dependency and couple our interfaces with an outside library.

通常情况下,这很好,但也有一个缺点。通过将Spring的@Service放在接口上,我们创建了一个额外的依赖关系,并将我们的接口与外部库联系起来。

Next, to test the autodetection of our new service beans, let’s create an implementation of our AuthenticationService:

接下来,为了测试新服务Bean的自动检测,让我们创建一个AuthenticationService的实现。

public class InMemoryAuthenticationService implements AuthenticationService {

    @Override
    public boolean authenticate(String username, String password) {
        //...
    }
}

We should pay attention that our new implementation, InMemoryAuthenticationService, doesn’t have the @Service annotation on it. We left @Service only on the interface, AuthenticationService.

我们应该注意,我们的新实现,InMemoryAuthenticationService,上面没有@Service注解。我们只在接口@Service上留下了AuthenticationService

So, let’s run our Spring context with the help of a basic Spring Boot setup:

因此,让我们在Spring Boot基本设置的帮助下运行我们的Spring上下文。

@SpringBootApplication
public class AuthApplication {

    @Autowired
    private AuthenticationService authService;

    public static void main(String[] args) {
        SpringApplication.run(AuthApplication.class, args);
    }
}

When we run our app, we get the infamous NoSuchBeanDefinitionException, and the Spring context fails to start:

当我们运行我们的应用程序时,我们得到了臭名昭著的NoSuchBeanDefinitionException,并且Spring上下文未能启动。

org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No qualifying bean of type 'com.baeldung.annotations.service.interfaces.AuthenticationService' available: 
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: 
...

Therefore, placing @Service on interfaces isn’t enough for the auto-detection of Spring components.

因此,@Service放在接口上并不足以实现Spring组件的自动检测

3. @Service on Abstract Classes

3.@Service对抽象类的影响

Using the @Service annotation on abstract classes isn’t common.

在抽象类上使用@Service注解并不常见。

Let’s test it out to see if it achieves our objective of causing Spring to autodetect our implementation classes.

让我们来测试一下,看看它是否达到了我们的目的,使Spring自动检测我们的实现类。

We’ll start by defining an abstract class from scratch and putting the @Service annotation on it:

我们将从头开始定义一个抽象类,并给它加上@Service注解。

@Service
public abstract class AbstractAuthenticationService {

    public boolean authenticate(String username, String password) {
        return false;
    }
}

Next, we extend AbstractAuthenticationService to create a concrete implementation without annotating it:

接下来,我们扩展AbstractAuthenticationService,以创建一个具体的实现,而不注释它

public class LdapAuthenticationService extends AbstractAuthenticationService {

    @Override
    public boolean authenticate(String username, String password) { 
        //...
    }
}

Accordingly, we also update our AuthApplication, to inject the new service class:

因此,我们也要更新我们的AuthApplication,以注入新的服务类

@SpringBootApplication
public class AuthApplication {

    @Autowired
    private AbstractAuthenticationService authService;

    public static void main(String[] args) {
        SpringApplication.run(AuthApplication.class, args);
    }
}

We should notice that we don’t try to inject the abstract class directly here, which is not possible. Instead, we intend to acquire an instance of the concrete class LdapAuthenticationService, depending only on the abstract type. This is a good practice, as the Liskov Substitution Principle also suggests.

我们应该注意到,我们在这里并没有尝试直接注入抽象类,这是不可能的。相反,我们打算获取一个具体类LdapAuthenticationService的实例,只取决于抽象类型。这是一个很好的做法,正如Liskov置换原则也建议的那样。

So, we run our AuthApplication, again:

因此,我们再次运行我们的AuthApplication,。

org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No qualifying bean of type 'com.baeldung.annotations.service.abstracts.AbstractAuthenticationService' available: 
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: 
...

As we can see, the Spring context doesn’t start. It ends up with the same NoSuchBeanDefinitionException exception.

我们可以看到,Spring上下文并没有启动。它以同样的NoSuchBeanDefinitionException异常结束。

Certainly, using @Service annotation on abstract classes doesn’t have any effect in Spring.

当然,在抽象类上使用@Service注解在Spring中没有任何影响

4. @Service on Concrete Classes

4.@Service对具体类的影响

Contrary to what we’ve seen above, it’s quite a common practice to annotate the implementation classes instead of abstract classes or interfaces.

与我们上面看到的相反,注解实现类而不是抽象类或接口是很常见的做法。

In this way, our goal is mostly to tell Spring this class is going to be a @Component and mark it with a special stereotype, which is @Service in our case.

这样,我们的目标主要是告诉Spring这个类要成为@Component,并用一个特殊的定型来标记它,在我们的例子中就是@Service

Therefore, Spring will autodetect those classes from the classpath and automatically define them as managed beans.

因此,Spring会从classpath中自动检测到这些类,并自动将它们定义为托管Bean。

So, let’s put @Service on our concrete service classes this time around. We’ll have one class that implements our interface and a second that extends the abstract class that we defined previously:

所以,这次让我们把@Service放在我们的具体服务类上。我们将有一个实现了我们的接口的类,另一个扩展了我们之前定义的抽象类。

@Service
public class InMemoryAuthenticationService implements AuthenticationService {

    @Override
    public boolean authenticate(String username, String password) {
        //...
    }
}

@Service
public class LdapAuthenticationService extends AbstractAuthenticationService {

    @Override
    public boolean authenticate(String username, String password) {
        //...
    }
}

We should take notice here that our AbstractAuthenticationService doesn’t implement the AuthenticationService here. Hence, we can test them independently.

我们应该注意到,我们的AbstractAuthenticationService并没有在这里实现AuthenticationService。因此,我们可以独立测试它们。

Finally, we add both of our service classes into the AuthApplication and give it a try:

最后,我们将我们的两个服务类添加到AuthApplication中,并试一试。

@SpringBootApplication
public class AuthApplication {

    @Autowired
    private AuthenticationService inMemoryAuthService;

    @Autowired
    private AbstractAuthenticationService ldapAuthService;

    public static void main(String[] args) {
        SpringApplication.run(AuthApplication.class, args);
    }
}

Our final test gives us a successful result, and the Spring context boots up with no exceptions. Both of the services are automatically registered as beans.

我们的最终测试给了我们一个成功的结果,Spring上下文启动时没有出现任何异常。两个服务都被自动注册为Bean。

5. The Result

5.结果

Eventually, we saw the only working way is putting @Service on our implementation classes to make them auto-detectable. Spring’s component scanning doesn’t pick up the classes unless they are annotated separately, even they’re derived from another @Service annotated interface or abstract class.

最终,我们看到唯一可行的方法是将@Service放在我们的实现类上,使其可以被自动检测到。Spring的组件扫描不会发现这些类,除非它们被单独注释,即使它们是从另一个@Service注释的接口或抽象类派生出来的。

Plus, Spring’s documentation also states that using @Service on implementation classes allows them to be autodetected by the component scan.

另外,Spring的文档也指出,在实现类上使用@Service可以使它们被组件扫描自动检测到。

6. Conclusion

6.结语

In this article, we examined different places of using Spring’s @Service annotation and learned where to keep @Service to define service-level Spring beans so that they’ll be autodetected during component scanning.

在这篇文章中,我们研究了使用Spring的@Service注解的不同地方,并了解了在哪里保留@Service来定义服务级Spring Bean,以便在组件扫描时自动检测到它们。

Specifically, we saw that placing the @Service annotation on interfaces or abstract classes has no effect and that only concrete classes will be picked up by component scanning when they’re annotated with @Service.

具体来说,我们看到将@Service注解放在接口或抽象类上是没有效果的,只有具体类在被注解为@Service时才会被组件扫描到。

As always, all the code samples and more are available over on GitHub.

一如既往,所有的代码样本和更多的内容都可以在GitHub上找到