我正在使用 Spring 开发应用程序。我需要使用@Service
注释。我有ServiceI
和ServiceImpl
这样的ServiceImpl implements ServiceI
。我很困惑我应该在哪里保留@Service
注释。
我应该用 注释接口还是实现@Service
?这两种方法有什么区别?
我从不将@Component
(or @Service
, ...) 放在界面上,因为这会使界面无用。让我解释一下为什么。
权利要求 1:如果您有一个接口,那么您想将该接口用于注入点类型。
权利要求 2:接口的目的是定义可以由多个实现实现的契约。在另一边,您有您的注入点 ( @Autowired
)。只有一个接口和一个实现它的类(恕我直言)是无用的,并且违反了YAGNI。
事实:当你放:
@Component
(或@Service
,...)在界面上,然后你会得到和NoUniqueBeanDefinitionException
(或者你有一个非常特殊的配置设置,带有环境、配置文件或限定符......)
结论:如果您在界面上使用@Component
(or @Service
, ...),那么您必须至少违反两个 clain 之一。因此,我认为放在@Component
接口级别是没有用的(除了一些罕见的场景)。
Spring-Data-JPA Repository 接口完全不同
基本上像@Service,@Repository,@Component等注释它们都具有相同的目的:
使用基于注释的配置和类路径扫描时的自动检测。
根据我的经验,我总是@Service
在接口或抽象类上使用注释,@Component
并@Repository
为它们的实现使用注释。@Component
我在那些服务于基本目的的类上使用注释,简单的 Spring bean,仅此而已。@Repository
我在DAO
图层中使用的注释,例如,如果我必须与数据库通信,有一些事务等。
因此,我建议@Service
根据功能使用 the 和其他层注释您的界面。
我只在实现类而不是接口上使用了 、 和@Component
注释@Service
。但是带有接口的注释仍然对我有用。如果你的接口只有一个实现,Spring 组件扫描会自动通过注释找到它。如果您有多个实现,则需要使用注释以及在注入点注入正确的实现。@Controller
@Repository
@Autowired
@Autowired
@Qualifier
@Autowired
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
注释。我们@Service
只留下了界面,AuthenticationService
.
因此,让我们在基本的 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 这个类将是 a@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。
有关完整的解释,您可以查看Spring @Service Annotation Be Kept 在哪里?亚武兹·塔什。
在 @Service 上添加注释的优点是它暗示它是一项服务。我不知道默认情况下是否有任何实现类会继承这个注解。
缺点是您通过使用 spring 特定注释将您的接口与特定框架(即 Spring)耦合。由于接口应该与实现分离,我不建议使用任何特定于框架的注释或接口的对象部分。
我会穿上@Service
你的课,但把接口的名称作为参数放在注释中,例如
interface ServiceOne {}
@Service("ServiceOne")
class ServiceOneImpl implements ServiceOne{}
通过这样做,您可以获得所有好处,并且仍然可以注入接口但获得类
@Autowired
private ServiceOne serviceOne;
因此,您的界面与 Spring 框架无关,您可以随时更改类,而不必更新所有注入点。
因此,如果我想更改实现类,我可以注释新类并从第一个类中删除,但这就是需要更改的全部内容。如果您注入该类,那么当您想要更改 impl 类时,您可能需要做很多工作。
spring 的一个好处是可以轻松切换服务(或其他)实现。为此,您需要在接口上进行注释并像这样声明变量:
@Autowired
private MyInterface myVariable;
并不是 :
@Autowired
private MyClassImplementationWhichImplementsMyInterface myVariable;
与第一种情况一样,您可以从唯一的那一刻起激活要注入的实现(只有一个类实现了接口)。在第二种情况下,您需要重构所有代码(新的类实现有另一个名称)。因此,注释需要尽可能地出现在界面上。此外,JDK 代理非常适合这一点:它们是在应用程序启动时创建和实例化的,因为运行时类型是预先知道的,这与 CGlib 代理相反。
简而言之:
@Service是服务层的 Stereotype 注解。
@Repository 是持久层的Stereotype 注解。
@Component是一个通用的原型注解,用于告诉 Spring 在应用程序上下文中创建对象的实例。可以为实例定义任何名称,默认为驼峰式命名的类名。
有 5 个注释可用于制作 spring bean。在下面列出答案。
你真的需要接口吗?如果您要为每个服务接口提供一个实现,请避免使用它,仅使用类。当然,如果您没有 RMI 或需要接口代理时。
@Repository - 用于注入你的 dao 层类。
@Service - 用于注入您的服务层类。在服务层中,您可能还需要使用 @Transactional 注释进行数据库事务管理。
@Controller - 用于您的前端层控制器,例如作为 Spring bean 注入的 JSF 托管 bean。
@RestController - 用于 spring 休息控制器,这将帮助您避免每次都将 @ResponseBody 和 @RequestBody 注释放在您的休息方法中。
@Component - 当您需要注入不是控制器、服务或 dao 类的 spring bean 时,在任何其他情况下使用它