@ Service 注释应该放在哪里? 接口还是实现?

我正在用 Spring 开发一个应用程序。我需要使用 @Service注释。我有 ServiceIServiceImpl这样的 ServiceImpl implements ServiceI。在这里,我对于应该将 @Service注释放在哪里感到困惑。

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

99690 次浏览

基本上,像 @ 服务@ Repository@ 组件等这样的注释都有同样的用途:

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

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

所以我建议根据功能使用 @Service和其他层来注释你的界面。

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

缺点是,通过使用特定于 Spring 的注释,可以将接口与一个特定的框架(例如 Spring)耦合。 由于接口应该与实现解耦,因此我不建议使用任何特定于框架的注释或接口的对象部分。

我只在实现类上使用 @Component@Service@Controller@Repository注释,而不在接口上使用。但是使用接口的 @Autowired注释仍然适合我。如果接口 Spring 组件扫描只有一个实现,那么只需使用 @Autowired注释就可以自动找到它。如果有多个实现,则需要使用 @Qualifier注释和 @Autowired来在注入点注入正确的实现。

我从来没有把 @Component(或者 @Service,...)放在接口上,因为这会使接口无用。

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

权利要求2: 接口的目的是定义一个契约,这个契约可以由多个实现实现。另一边是注射点(@Autowired)。只有一个接口和一个实现它的类(IMHO)是无用的,并且违反了 YAGNI

事实: 当你输入:

  • @Component(或 @Service,...)在一个接口上,
  • 有多个实现它的类,
  • 至少有两个班变成了春豆,
  • 有一个注入点,它使用接口进行基于类型的注入,

然后你会得到和 NoUniqueBeanDefinitionException (或者您有一个非常特殊的配置设置,包括 Environment、 Profiles 或 Qualifier...)

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


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

有5个注释可以用来制作春豆。请在下面的答案中列出。

你真的需要一个接口吗?如果要为每个服务接口实现一个实现,只需避免它,只使用 class。当然,如果你没有 RMI 或当接口代理是必需的。

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

@ Service-用于注入服务层类。在服务层,您可能还需要使用@Transactional 注释来进行 db 事务管理。

@ Controller-使用前端层控制器,例如将 JSF 托管 bean 注入为 spring bean。

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

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

简单地说:

@ Service 服务层的原型注释。

@ Repos itory 坚持不懈层的原型注释。

@ Component 是一个 一般原型注释,用于告诉 Spring 在 Application Context 中创建对象的实例。有可能 为实例定义任何名称,默认为驼峰大小写的类名。

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

@Autowired
private MyInterface myVariable;

而不是:

@Autowired
private MyClassImplementationWhichImplementsMyInterface myVariable;

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

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

interface ServiceOne {}


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

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

@Autowired
private ServiceOne serviceOne;

所以你的接口不需要绑定到 Spring 框架,你可以随时更改类,而不需要更新所有的注入点。

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

1.@接口服务

@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);
}
}

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

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


2.@抽象课程服务

在抽象类上使用 @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 上下文没有启动,它再次以相同的 异常异常结束。

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


3.@混凝土课服务

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

通过这种方式,我们的目标主要是告诉 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。

您可以查看 这个页了解其他解释。