这是每一个Java开发人员都应该知道的最重要的Spring注解。感谢优锐课老师对本文提供的一些帮助。sql
随着愈来愈多的功能被打包到单个应用程序或一组应用程序中,现代应用程序的复杂性从未中止增加。尽管这种增加带来了一些惊人的好处,例如丰富的功能和使人印象深入的多功能性,但它要求开发人员使用愈来愈多的范例和库。为了减小开发人员的工做量以及开发人员必须记住的信息量,许多Java框架都转向了注解。数据库
特别是Spring,它以注解的使用而闻名,它使开发人员仅用少数几个注解就能够建立完整的表示状态转移(REST)应用程序编程接口(APIs)。这些注解减小了执行基本功能所需的样板代码量,但也能够掩盖幕后发生的事情。例如,对字段应用依赖项注入(DI)注释如何致使在运行时注入特定的bean?或者,REST批注如何知道绑定到哪一个URL路径?编程
尽管这些问题彷佛是特定于Spring的(这引出了为何非Spring开发人员须要知道对他们的答案的问题),但它们的影响深远,使人耳目一新。根据Baeldung进行的2018年调查,有90.5%的参与者使用的是Spring。此外,根据2019年Stackoverflow开发人员调查,接受调查的全部开发人员中有16.2%使用Spring,有65.6%的人表示他们喜欢Spring。Spring的广泛存在乎味着即便使用其余框架或根本不须要任何企业框架的Java开发人员也可能会遇到Spring代码。即便是将知识仅限于Spring注解的一小部分的Spring开发人员,也会从他们的视野中受益。缓存
在本文中,咱们将深刻探讨Spring中可用的四个最相关的注解,特别注意注解背后的概念以及如何在较大的应用程序上下文中正确应用注解。尽管咱们将详细介绍这些注解及其相关注解,可是有关Spring注解的大量信息使人st目结舌,所以没法在本篇文章中找到。有兴趣的读者应查阅Spring的官方文档以获取更多详细信息。安全
从本质上讲,Spring是一个DI框架。本质上,DI框架负责以Java Bean形式将依赖项注入其余Bean中。这种范例与大多数基本应用程序相反,后者直接实例化其依赖关系。可是,在DI中,将使用间接级别建立bean,并指望DI框架为其注入依赖项。例如,一个设计良好的bean将具备一个带有依赖项参数的构造函数——并容许DI框架传入一个知足该依赖关系的对象,而不是直接在构造函数中实例化该依赖关系。这种逆转称为控制反转(IoC),而且是许多各类Spring库所基于的基础:app
1 public class Bar {} 2 // The non-DI way 3 public class Foo { 4 private final Bar bar; 5 public Foo() { 6 this.bar = new Bar(); 7 } 8 } 9 // The DI way 10 public class Foo { 11 private final Bar bar; 12 public Foo(Bar bar) { 13 this.bar = bar; 14 } 15 }
DI框架要回答的最关键的问题之一是:哪些bean能够注入其余bean中?为了回答这个问题,Spring提供了@Component注解
。 将该注释应用于类将通知Spring该类是一个组件,而且能够实例化该类的对象并将其注入到另外一个组件中。@Component
接口经过如下方式应用于类:框架
1 @Component 2 public class FooComponent {}
尽管@Component注解
足以通知Spring Bean的可注入性;Spring还提供了专门的注解,可用于建立具备更有意义的上下文信息的组件。ide
@Service
(顾名思义)表示Bean是服务。 根据官方的@Service注解文档:函数
[@Service
批注]指示带注解的类是“服务”,最初由Domain-Driven Design(Evans,2003)定义为“做为接口提供的操做,在模型中独立存在,没有封装状态”。
可能还代表某个类是“业务服务门面”(就核心J2EE模式而言)或相似的东西。测试
一般,企业应用程序中服务的概念含糊不清,可是在Spring应用程序的上下文中,服务是提供与域逻辑或外部组件交互的方法而无需保持更改服务总体行为的状态的任何类。例如,服务能够表明应用程序来从数据库获取文档或从外部REST API获取数据。
1 @Service 2 public class FooService {}
尽管没有关于服务状态的明确规则,可是服务一般不像域对象那样包含状态。例如,与将名称,地址和社会安全号码视为域对象的状态的方式相同,不会将REST客户端,缓存或链接池视为服务的状态。实际上,因为服务的所有定义,@Service
和@Component
一般能够互换使用。
@Service
是用于更多通用目的的,而@Repository注解
是@Component注解
的一种特殊化,它是为与数据源(例如数据库和数据访问对象(DAOs))进行交互的组件而设计的。
1 @Repository 2 public class FooRepository {}
根据官方的@Repository
文档:
指示带注解的类是“存储库”,最初由Domain-Driven Design(Evans,2003)定义为“一种封装存储,检索和搜索行为的机制,该机制模仿对象的集合”。
实现诸如“数据访问对象”之类的传统Java EE模式的团队也能够将这种构造型应用于DAO类,尽管在这样作以前应注意理解数据访问对象和DDD样式存储库之间的区别。此注解是通用的刻板印象,各个团队能够缩小其语义并适当使用。
除了将特定的类标记为处理数据源的组件以外,Spring框架还将对@Repository注解
的bean进行特殊的异常处理。 为了维护一致的数据接口,Spring能够将本机存储库引起的异常(例如SQL或Hibernate实现)转换为能够统一处理的常规异常。 为了包括用@Repository注解
的类的异常翻译,咱们实例化了PersistenceExceptionTranslationPostProcessor类型的bean(咱们将在后面的部分中看到如何使用@Configuration
和@Bean注解
):
1 @Configuration 2 public class FooConfiguration { 3 @Bean 4 public PersistenceExceptionTranslationPostProcessor exceptionTranslator() { 5 return new PersistenceExceptionTranslationPostProcessor() 6 } 7 }
包括该bean将通知Spring寻找PersistenceExceptionTranslator的全部实现,并在可能的状况下使用这些实现将本机RuntimeException
转换为DataAccessExceptions
。有关使用@Repository注解
进行异常转换的更多信息,请参见官方的Spring Data Access文档。
@Component注解
的最后一个专业化能够说是三人组中最经常使用的。Spring Model-View-Controller(MVC)是Spring Framework最受欢迎的部分之一,它使开发人员可使用@Controller注解轻松建立REST API。该注解在应用于类时,指示Spring框架将该类视为应用程序的Web界面的一部分。
经过将@RequestMapping注解
应用于该类的方法来在此类中建立端点——其中@RequestMapping注解
的值是路径(相对于API端点绑定到的控制器的根路径),而且 method是终结点绑定到的超文本传输协议(HTTP)方法。例如:
1 @Controller 2 public class FooController { 3 @RequestMapping(value = "/foo", method = RequestMethod.GET) 4 public List<Foo> findAllFoos() { 5 // ... return all foos in the application ... 6 } 7 }
这将建立一个端点,该端点在/foo
路径上侦听GET
请求,并将全部Foo
对象的列表(默认状况下表示为JavaScript Object Notation(JSON)列表)返回给调用方。例如,若是Web应用程序在https://localhost
上启动,则端点将绑定到https://localhost/foo
。咱们将在下面更详细地介绍@RequestMapping注解
,可是就目前而言,足以知道 @Controller注解
是Spring框架的重要组成部分,而且它指示Spring框架建立大型而复杂的Web服务实现。
如在Java中建立注解中所述,注解自己不会执行任何逻辑。相反,注解只是标记,它们表示有关构造的某些信息,例如类,方法或字段。为了使注释有用,必须对其进行处理。对于@Component注解
及其专业化,Spring不知道在哪里能够找到全部使用@Component注解
的类。
为此,咱们必须指示Spring应该扫描类路径上的哪些包。在扫描过程当中,Spring DI Framework处理提供的包中的每一个类,并记录全部用@Component
或@Component
特化注解的类。扫描过程完成后,DI框架就会知道哪些类适合进行注入。
为了指示Spring扫描哪些软件包,咱们使用@ComponentScan注解
:
1 @Configuration 2 @ComponentScan 3 public class FooConfiguration { 4 // ... 5 }
在后面的部分中,咱们将深刻研究@Configuration注解
,但就目前而言,足以知道@Configuration注解
指示Spring批注的类提供了可供DI框架使用的配置信息。默认状况下(若是没有为@ComponentScan注解
提供任何参数)将扫描包含配置的包及其全部子包。要指定一个包或一组包,请使用basePackages
字段:
1 @Configuration 2 @ComponentScan(basePackages = "com.example.foo") 3 public class FooConfiguration { 4 // ... 5 }
在上面的示例中,Spring将扫描com.example.foo
软件包及其全部子软件包中的合格组件。若是仅提供一个基本软件包,则@ComponentScan注解
能够简化为@ComponentScan("com.example.foo")
。若是须要多个基本软件包,则能够为basePackages
字段分配一组字符串:
1 @Configuration 2 @ComponentScan(basePackages = {"com.example.foo", "com.example.otherfoo"}) 3 public class FooConfiguration { 4 // ... 5 }
对于任何DI框架,第二个相当重要的问题是:建立bean时必须知足哪些依赖关系?为了通知Spring框架咱们指望将哪些字段或构造函数参数与依赖项一块儿注入或链接,Spring提供了@Autowired
annotation。此注解一般适用于字段或构造函数——尽管也能够将其应用于设置方法(这种用法不太常见)。
当应用于字段时,即便没有设置器,Spring也会在建立时将符合条件的依赖项直接注入到字段中:
1 @Component 2 public class FooComponent { 3 @Autowired 4 private Bar bar; 5 }
这是将依赖项注入组件的便捷方法,可是在测试类时确实会产生问题。例如,若是咱们要编写一个执行FooComponent
类的测试夹具,而没有在夹具中包括Spring测试框架,那么咱们将没法在bar
字段中注入模拟Bar
值(而无需执行繁琐的反射)。咱们能够将@Autowired注解
添加到接受Bar
参数并将其分配给bar
字段的构造函数中:
1 @Component 2 public class FooComponent { 3 private final Bar bar; 4 @Autowired 5 public Foo(Bar bar) { 6 this.bar = bar; 7 } 8 }
这仍然使咱们可使用模拟Bar
实现直接实例化FooComponent
类的对象,而不会给Spring测试配置增长负担。例如,如下将是有效的JUnit测试用例(使用Mockito进行模拟):
1 public class FooTest { 2 @Test 3 public void exerciseSomeFunctionalityOfFoo() { 4 Bar mockBar = Mockito.mock(Bar.class); 5 FooComponent foo = new FooComponent(mockBar); 6 // ... exercise the FooComponent object ... 7 }
使用@Autowired注解
构造函数还容许咱们在将注入的Bar
bean分配给bar
字段以前对其进行访问和操做。 例如,若是咱们要确保注入的Bar
Bean永远不会为null
,则能够在将提供的Bar Bean分配给bar
字段以前执行此检查:
1 @Component 2 public class FooComponent { 3 private final Bar bar; 4 @Autowired 5 public FooComponent(Bar bar) { 6 this.bar = Objects.requireNonNull(bar); 7 } 8 } 9
在某些状况下,可能有多个候选关系。这给Spring带来了一个问题,由于它必须在建立组件时决定要注入哪一个特定的bean,不然,若是没法肯定单个候选对象,它将失败。例如,如下代码将引起 NoUniqueBeanDefinitionException
:
1 public interface FooDao { 2 public List<Foo> findAll(); 3 } 4 @Repository 5 public class HibernateFooDao implements FooDao { 6 @Override 7 public List<Foo> findAll() { 8 // ... find all using Hibernate ... 9 } 10 } 11 @Repository 12 public class SqlFooDao implements FooDao { 13 @Override 14 public List<Foo> findAll() { 15 // ... find all using SQL ... 16 } 17 } 18 @Controller 19 public class FooController { 20 private final FooDao dao; 21 @Autowired 22 public FooController(FooDao dao) { 23 this.dao = dao; 24 } 25 }
Spring不知道是否要注入HibernateDooDao
或SqlFooDao
,所以会抛出致命的NoUniqueBeanDefinitionException
。为了帮助Spring解决选择哪一个bean,咱们可使用@Qualifier注解
。经过为@Qualifier注解
提供与@Component注解
(或其任何专业化)提供的名称相匹配的键,以及@Autowired注解
,咱们能够缩小合格的注入候选对象的范围。例如,在如下代码段中,将HibernateFooDao
注入到FooController
中,而且不会引起NoUniqueBeanDefinitionException
:
1 public interface FooDao { 2 public List<Foo> findAll(); 3 } 4 @Repository("hibernateDao") 5 public class HibernateFooDao implements FooDao { 6 @Override 7 public List<Foo> findAll() { 8 // ... find all using Hibernate ... 9 } 10 } 11 @Repository("sqlDao") 12 public class SqlFooDao implements FooDao { 13 @Override 14 public List<Foo> findAll() { 15 // ... find all using SQL ... 16 } 17 } 18 @Controller 19 public class FooController { 20 private final FooDao dao; 21 @Autowired 22 @Qualifier("hibernateDao") 23 public FooController(FooDao dao) { 24 this.dao = dao; 25 } 26 }
因为Spring框架的巨大规模-处理从DI到MVC到事务管理的全部内容,所以须要开发人员提供的配置级别。例如,若是咱们但愿定义一组可用于自动装配的Bean(例如上面看到的PersistenceExceptionTranslationPostProcessor Bean),则必须告知Spring一些配置机制。Spring经过适当命名的@Configuration注解
提供了这种机制。当将此注解应用于类时,Spring将该类视为包含可用于参数化框架的配置信息的类。根据官方的Spring @Configuration
文档:
指示一个类声明了一个或多个@Bean
方法,而且能够由Spring容器进行处理以在运行时为这些bean生成bean定义和服务请求,例如:
正如咱们在上面看到的,咱们能够手动建立Spring将包含的新bean做为注入的候选对象,而无需注解类自己。当咱们没法访问该类的源代码或者该类存在于不属于组件扫描过程的软件包中时,可能就是这种状况。在上面的@Qualifier
示例中,咱们也能够放弃@Repository
annotations并在带有@Configuration
注释的类中使用@Bean注解
,以指示Spring在须要FooDao
时使用HibernateFooDao
:
1 public interface FooDao { 2 public List<Foo> findAll(); 3 } 4 public class HibernateFooDao implements FooDao { 5 @Override 6 public List<Foo> findAll() { 7 // ... find all using Hibernate ... 8 } 9 } 10 public class SqlFooDao implements FooDao { 11 @Override 12 public List<Foo> findAll() { 13 // ... find all using SQL ... 14 } 15 } 16 @Configuration 17 public class FooConfiguration { 18 @Bean 19 public FooDao fooDao() { 20 return new HibernateFooDao(); 21 } 22 }
使用此配置,Spring如今将具备在请求FooDao
时实例化HibernateDooDao
所需的逻辑。本质上,咱们建立了一个Factory方法,框架能够在须要时使用该方法来实例化FooDao
的实例。若是在建立bean时排除了@Autowired
参数,咱们能够经过向使用@Bean注解
的方法中添加参数来反对这种依赖性。若是咱们用@Component
或@Component
的任何特化来注解组件,Spring会在建立组件时知道注入依赖项,可是因为咱们是在Spring Framework外部直接调用构造函数,所以必须提供依赖项。例如:
1 @Component 2 public class Bar {} 3 public class FooComponent { 4 private final Bar bar; 5 @Autowired 6 public FooComponent(Bar bar) { 7 this.bar = bar; 8 } 9 } 10 @Configuration 11 public class FooConfiguration { 12 @Bean 13 public FooComponent fooComponent(Bar bar) { 14 return new FooComponent(bar); 15 } 16 }
Spring寻找知足fooComponent
方法参数的已注册候选者,当找到一个候选者时,它将被传入并最终传递给FooComponent
构造函数。请注意,任何使用@Component注解
或任何特殊化注解的bean或使用其余@Bean
method建立的bean均可以注入@Bean
方法参数中。例如:
1 public class Bar {} 2 public class FooComponent { 3 private final Bar bar; 4 @Autowired 5 public FooComponent(Bar bar) { 6 this.bar = bar; 7 } 8 } 9 @Configuration 10 public class FooConfiguration { 11 @Bean 12 public Bar bar() { 13 return new Bar(); 14 } 15 @Bean 16 public FooComponent fooComponent(Bar bar) { 17 return new FooComponent(bar); 18 } 19 }
请注意,使用@Bean注解方法的惯例与@Bean
相同,首字母小写。例如,若是咱们要建立一个FooComponent
,则用于建立bean(并用@Bean注解
)的方法一般称为fooComponent
.。
@Controller注解
的大部分功能都来自@RequestMapping注解
,该注解指示Spring建立一个映射到带注解方法的Web终结点。建立Web API时,框架须要知道如何处理对特定路径的请求。例如,若是对https://localhost/foo
进行了HTTP GET
调用,Spring须要知道如何处理该请求。此绑定(或映射)过程是@RequestMapping注解
的权限,该注解通知Spring应该将特定的HTTP动词和路径映射到特定的方法。例如,在上一节中,咱们看到咱们能够指示Spring使用如下代码段将HTTP GET
映射到/ foo
:
1 @Controller 2 public class FooController { 3 @RequestMapping(value = "/foo", method = RequestMethod.GET) 4 public List<Foo> findAll() { 5 // ... return all foos in the application ... 6 } 7 }
请注意,能够将多个HTTP动词提供给method参数,但这在实践中是异常的。 因为几乎老是将单个HTTP动词提供给method参数——而且这些动词一般最终以GET
, POST
, PUT
, 和 DELETE
结尾,所以Spring还包括四个附加注解,可用于简化@RequestMapping
方法的建立:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
若是须要根路径(即与控制器路径匹配的路径),则不须要value参数。@RequestMapping注解
也能够应用于控制器自己,该控制器设置整个控制器的根路径。例如,如下控制器在/foo
路径中建立一个GET
端点,在/foo/bar
中建立另外一个POST
端点:
1 @Controller 2 @RequestMapping("/foo") 3 public class FooController { 4 @GetMapping 5 public List<Foo> findAll() { 6 // ... return all foos in the application ... 7 } 8 @PostMapping("/bar") 9 public void doSomething() { 10 // ... do something ... 11 } 12 }
在某些状况下,可能会在路径中提供路径变量,这是正确处理请求所必需的。若要获取此路径变量的值,能够向使用@RequestMapping注解
的方法提供参数,而且能够将@PathVariable注解
应用于此参数。例如,若是须要实体的ID来删除它,则能够将该ID做为路径变量提供,例如对/foo/1
的DELETE
请求。为了捕获提供给负责处理DELETE
请求的方法的1
,咱们捕获路径变量,方法是用大括号将变量名括起来,并为处理程序方法的参数应用@PathVariable注解
,其中将值提供给@PathVariable
匹配路径中捕获的变量的名称:
1 @Controller 2 public class FooController { 3 @DeleteMapping("/foo/{id}") 4 public void deleteById(@PathVariable("id") String id) { 5 // ... delete Foo with ID "id" ... 6 } 7 }
默认状况下,假定@PathVariable
的名称与带注解的参数的名称匹配,所以,若是参数的名称与路径中捕获的变量的名称彻底匹配,则无需为@PathVariable注解
提供任何值:
1 @Controller 2 public class FooController { 3 @DeleteMapping("/foo/{id}") 4 public void deleteById(@PathVariable String id) { 5 // ... delete Foo with ID "id" ... 6 } 7 }
Spring将尝试将捕获的路径变量强制转换为以@PathVariable注解
的参数的数据类型。例如,若是咱们将ID path变量的值除为整数,则能够将id参数的数据类型更改成int
:
1 @Controller 2 public class FooController { 3 @DeleteMapping("/foo/{id}") 4 public void deleteById(@PathVariable int id) { 5 // ... delete Foo with ID "id" ... 6 } 7 }
若是在路径中提供了诸如字符串baz
之类的值(即/foo/baz
),则会发生错误。
除了捕获路径变量以外,咱们还可使用@RequestParam注解
捕获查询参数。@RequestParam
以与@PathVariable注解
相同的方式将参数装饰处处理程序方法,可是提供给@RequestParam
annotation的值与查询参数的键匹配。例如,若是咱们但愿对/foo?limit=100
的路径进行HTTP GET
调用,则能够建立如下控制器来捕获限制值:
1 @Controller 2 public class FooController { 3 @GetMapping("/foo") 4 public List<Foo> findAll(@QueryParam("limit") int limit) { 5 // ... return all Foo objects up to supplied limit ... 6 } 7 }
与@PathVariable
同样,能够省略提供给@RequestParam注解
的值,而且默认状况下将使用参数的名称。一样,若是可能的话,Spring将把捕获的查询参数的值强制转换为参数的类型(在上述状况下为int
)。
在调用中提供请求正文的状况下(一般经过建立或更新条目的POST
或PUT
调用完成),Spring提供了@RequestBody注解
。与前两个注解同样,@RequestBody注解
应用于处理程序方法的参数。 而后,Spring会将提供的请求主体反序列化为参数的类型。例如,咱们可使用具备相似于如下内容的请求主体的HTTP调用建立新的Foo
:
1 {"name": "some foo", "anotherAttribute": "bar"}
而后,咱们能够建立一个包含与指望的请求主体匹配的字段的类,并建立一个捕获该请求主体的处理程序方法:
1 public class FooRequest { 2 private String name; 3 private String anotherAttribute; 4 public void setName(String name) { 5 this.name = name; 6 } 7 public String getName() { 8 return name; 9 } 10 public void setAnotherAttribute(String anotherAttribute) { 11 this.anotherAttribute = anotherAttribute; 12 } 13 public String getAnotherAttribute() { 14 return anotherAttribute; 15 } 16 } 17 @Controller 18 public class FooController { 19 @PostMapping("/foo") 20 public void create(@RequestBody FooRequest request) { 21 // ... create a new Foo object using the request body ... 22 } 23 }
尽管有许多Java框架,但Spring倒是无处不在的,它是最广泛的一种。 从REST API到DI,Spring包括丰富的功能集,这些功能使开发人员无需编写大量样板代码便可建立复杂的应用程序。 Spring提供的一种机制是注解,它使开发人员能够修饰类和方法,并为它们提供上下文信息,Spring框架可使用这些信息来表明咱们建立组件和服务。因为Spring的广泛性,每一个Java开发人员均可以从理解这些Spring注解以及它们在实践中的应用中受益不浅。