【问题标题】:Where should @Service annotation be kept? Interface or Implementation?@Service 注释应该保存在哪里?接口还是实现?
【发布时间】:2013-04-27 10:28:06
【问题描述】:

我正在使用 Spring 开发应用程序。我需要使用 @Service 注释。我有ServiceIServiceImpl 这样ServiceImpl implements ServiceI。我很困惑我应该在哪里保留 @Service 注释。

我应该用@Service注释接口还是实现?这两种方法有什么区别?

【问题讨论】:

标签: spring service


【解决方案1】:

我从不将@Component(或@Service,...)放在界面上,因为这会使界面无用。让我解释一下原因。

声明 1:如果您有一个接口,那么您想将该接口用于注入点类型。

权利要求 2: 接口的目的是定义一个可以由多个实现实现的协定。在另一边,你有你的注入点 (@Autowired)。只有一个接口和一个实现它的类,(恕我直言)是无用的,并且违反了YAGNI

事实:当你输入:

  • @Component(或@Service,...)在接口处,
  • 有多个实现它的类,
  • 至少有两个类成为 Spring Bean,并且
  • 有一个使用接口进行基于类型注入的注入点,

然后你会得到和NoUniqueBeanDefinitionException (或者你有一个非常特殊的配置设置,包括环境、配置文件或限定符......)

结论:如果您在界面上使用@Component(或@Service,...),那么您必须至少违反这两个规则之一。因此,我认为将@Component 放在接口级别是没有用的(除了一些罕见的情况)。


Spring-Data-JPA Repository 接口完全不同

【讨论】:

  • 你写的东西很有趣……那么正确的方法是什么?是不是根本没有对接口进行注解,而是给实现注解了 Service ? Spring 是否仍然能够使用接口类型自动装配?你的答案是什么>stackoverflow.com/questions/12899372/…
  • 我有一个问题,为什么只有一个类实现的情况下,我们需要为服务层创建一个接口?我见过很多项目,他们有controller layer, **service layer (servicInterface, serviceInterfaceImpl), and repository layer.
  • @Yubaraj:公平点,但你的问题是关于一个完全不同的话题(对于我的回答,我假设:有一个接口,问题不在于有接口,而是关于在哪里放置注释)。对于您的问题:几乎没有理由为永远不会有两个实现的业务服务类提供接口。 (顺便说一句:只要您不为其他人构建 api,您可以随时重构代码并在以后需要时引入接口)
  • @Yubaraj ,接口允许在需要时为 bean 创建基于接口的轻量级 jdk 代理。当没有接口时,spring 必须使用 cglib 进行子类化或修改 bean 以制作代理。 @Transactional 是使用 bean 代理的示例之一。 AOP 是另一种。
  • 即使有时你不会有多个实现类,我仍然更喜欢在接口中声明我的方法。其他开发人员会发现只需查看声明和文档即可更轻松地检查哪些服务可用,而无需担心实现。
【解决方案2】:

@Service@Repository@Component 等基本注解都有相同的用途:

使用基于注释的配置和类路径时的自动检测 扫描。

根据我的经验,我总是在接口或抽象类上使用@Service 注释,并使用@Component@Repository 等注释来实现它们。 @Component 注释我在那些服务于基本目的的类上使用,简单的 Spring bean,仅此而已。我在DAO 层中使用的@Repository 注释,例如如果我必须与数据库通信,进行一些事务等。

因此,我建议根据功能使用@Service 和其他层来注释您的界面。

【讨论】:

  • 你能说出注解接口和注解实现的区别吗?
  • 来自Spring docs“这个注解作为@Component 的一个特化,允许通过类路径扫描自动检测实现类,”暗示它的目的是用于实现类。
  • @TheKojuEffect,这篇文章详细解释了注释接口与实现之间的区别 - stackoverflow.com/questions/3120143/…
  • @user3257644 请注意,该帖子中的答案给出的建议是关于“@Transactional”注释的,而不是一般的注释。
  • 接口上的@service注解没有作用,就像其他的原型注解一样。所有的原型注释都应该放在抽象类或具体类上。
【解决方案3】:

我只在实现类上使用了@Component@Service@Controller@Repository注解,而不是在接口上。但是带有接口的@Autowired 注释仍然对我有用。如果你的接口只有一个实现,Spring 组件扫描会自动找到它,只需 @Autowired 注释。如果您有多个实现,则需要使用 @Qualifier 注释和 @Autowired 在注入点注入正确的实现。

【讨论】:

    【解决方案4】:

    1.接口上的@Service

    @Service
    public interface AuthenticationService {
    
        boolean authenticate(String username, String password);
    }
    

    通常,这很好,但有一个缺点。通过将 Spring 的 @Service 放在接口上,我们创建了一个额外的依赖项并将我们的接口与外部库耦合。

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

    public class InMemoryAuthenticationService implements AuthenticationService {
    
        @Override
        public boolean authenticate(String username, String password) {
            //...
        }
    }
    

    我们应该注意,我们的新实现InMemoryAuthenticationService 上没有@Service 注释。我们只在界面留下了@ServiceAuthenticationService

    所以,让我们在基本的 Spring Boot 设置的帮助下运行我们的 Spring 上下文:

    @SpringBootApplication
    public class AuthApplication {
    
        @Autowired
        private AuthenticationService authService;
    
        public static void main(String[] args) {
            SpringApplication.run(AuthApplication.class, args);
        }
    }
    

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

    因此,将@Service 放在接口上不足以自动检测 Spring 组件。


    2. @Service 抽象类

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

    我们将从头开始定义一个抽象类并在其上添加@Service 注解:

    @Service
    public abstract class AbstractAuthenticationService {
    
        public boolean authenticate(String username, String password) {
            return false;
        }
    }
    

    接下来,我们扩展 AbstractAuthenticationService 以创建一个具体的实现,而不需要对其进行注释:

    public class LdapAuthenticationService extends AbstractAuthenticationService {
    
        @Override
        public boolean authenticate(String username, String password) { 
            //...
        }
    }
    

    因此,我们还更新了AuthApplication,以注入新的服务类:

    @SpringBootApplication
    public class AuthApplication {
    
        @Autowired
        private AbstractAuthenticationService authService;
    
        public static void main(String[] args) {
            SpringApplication.run(AuthApplication.class, args);
        }
    }
    

    在我们运行AuthApplication 之后,Spring 上下文没有启动。它再次以相同的 NoSuchBeanDefinitionException 异常结束。

    因此,在抽象类上使用@Service 注解在 Spring 中没有任何作用。


    3. @Service 在具体类上

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

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

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

    所以,这次让我们将@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) {
            //...
        }
    }
    

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

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

    @SpringBootApplication
    public class AuthApplication {
    
        @Autowired
        private AuthenticationService inMemoryAuthService;
    
        @Autowired
        private AbstractAuthenticationService ldapAuthService;
    
        public static void main(String[] args) {
            SpringApplication.run(AuthApplication.class, args);
        }
    }
    

    我们的最终测试给了我们一个成功的结果,并且 Spring 上下文无异常启动。这两个服务都自动注册为 bean。

    有关完整说明,您可以查看 Yavuz Taş 的 Where Should the Spring @Service Annotation Be Kept?

    【讨论】:

    • 感谢您对该主题的精彩演示,以及此处公开的其他有趣帖子中描述的理论方法:它有助于获得必要的“点击”。
    【解决方案5】:

    在@Service 上添加注释的优点是它暗示它是一个服务。我不知道是否有任何实现类会默认继承这个注解。

    缺点是您通过使用特定于 Spring 的注释将接口与特定框架(即 Spring)耦合。 由于接口应该与实现分离,我不建议使用任何特定于框架的注释或接口的对象部分。

    【讨论】:

    • 我想我们都多次听说过强耦合论点,但请记住,注解可以在没有 jar 的情况下存在,所以基本上只要您的耦合在注解上,它仍然可以解耦。
    【解决方案6】:

    我会将@Service 放在你的类上,但将接口的名称作为参数放在注释中,例如

    interface ServiceOne {}
    
    @Service("ServiceOne")
    class ServiceOneImpl implements ServiceOne{}
    

    通过这样做,您可以获得所有好处,并且仍然可以注入接口但获得类

    @Autowired 
    private ServiceOne serviceOne;
    

    因此您的界面不依赖于 Spring 框架,您可以随时更改类,而不必更新所有注入点。

    因此,如果我想更改实现类,我可以只注释新类并从第一个类中删除,但这就是需要更改的全部内容。如果您注入该类,那么当您想要更改 impl 类时可能需要做很多工作。

    【讨论】:

      【解决方案7】:

      spring 的一个好处是可以轻松切换服务(或其他)实现。 为此,您需要在接口上进行注释并像这样声明变量:

      @Autowired
      private MyInterface myVariable;
      

      而不是:

      @Autowired
      private MyClassImplementationWhichImplementsMyInterface myVariable;
      

      与第一种情况一样,您可以从唯一的那一刻起激活要注入的实现(只有一个类实现了接口)。 在第二种情况下,您需要重构所有代码(新的类实现有另一个名称)。 因此,注释需要尽可能地出现在界面上。此外,JDK 代理非常适合:它们是在应用程序启动时创建和实例化的,因为运行时类型是预先知道的,这与 CGlib 代理相反。

      【讨论】:

      • “MyClassImplementationWhichImplementsMyInterface”LOL
      • 你不需要在界面上注释第一个例子就可以工作。您可以使用@Service 注释实现并自动装配接口。 Spring 将检查任何实现此接口的对象。
      【解决方案8】:

      简单地说:

      @Serviceservice 层的 Stereotype 注解。

      @Repositorypersistence 层的 Stereotype 注释。

      @Component 是一个 generic 原型注解,用于告诉 Spring 在应用程序上下文中创建对象的实例。有可能 为实例定义任何名称,默认为驼峰式的类名。

      【讨论】:

      • 不是在寻找这些注释的含义,而是在哪里将它们放在接口或其实现上。
      【解决方案9】:

      有 5 个注解可用于制作 spring bean。在下面列出答案。

      你真的需要一个界面吗?如果您要为每个服务接口提供一个实现,请避免使用它,仅使用类。当然,如果您没有 RMI 或需要接口代理时。

      @Repository - 用于注入你的 dao 层类。

      @Service - 用于注入你的服务层类。在服务层中,您可能还需要使用 @Transactional 注释进行数据库事务管理。

      @Controller - 用于您的前端层控制器,例如 JSF 托管 bean 作为 Spring bean 注入。

      @RestController - 用于 spring 休息控制器,这将帮助您避免每次在休息方法中放置 @ResponseBody 和 @RequestBody 注释。

      @Component - 当您需要注入不是控制器、服务或 dao 类的 spring bean 时,请在任何其他情况下使用它

      【讨论】:

      • 是的,你需要在你的层的边界上有一个接口(比如数据访问层和服务层)。它们支持包含这些层实现的模块的松散耦合。没有它们,所提到的层的客户端必须知道具体的类型,当你想用 CachingDao 装饰你的 BasicDao 时,你需要更改它们......
      猜你喜欢
      • 2010-09-08
      • 2011-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-29
      • 1970-01-01
      • 2015-08-05
      相关资源
      最近更新 更多