什么是注解
传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop、事物,这么做有两个缺点:
- 如果所有的内容都配置在.xml文件中,那么.xml文件将会十分庞大;如果按需求分开.xml文件,那么.xml文件又会非常多。总之这将导致配置文件的可读性与可维护性变得很低
- 在开发中在.java文件和.xml文件之间不断切换,是一件麻烦的事,同时这种思维上的不连贯也会降低开发的效率
为了解决这两个问题,Spring引入了注解,通过"@XXX"的方式,让注解与Java Bean紧密结合,既大大减少了配置文件的体积,又增加了Java Bean的可读性与内聚性。
注解组成
java annotation 的组成中,有3个非常重要的主干类。它们分别是:
- Annotation.java
- ElementType.java
- RetentionPolicy.java
Annotation.java
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
- Annotation 是个接口
- 有四个函数
ElementType.java
public enum ElementType {
TYPE, // 类、接口(包括注释类型)或枚举
FIELD, // 字段(包括枚举常量)
METHOD, // 方法
PARAMETER, // 参数
CONSTRUCTOR, // 构造方法
LOCAL_VARIABLE, // 局部变量
ANNOTATION_TYPE, // 注释类型
PACKAGE // 包
}
ElementType 是Enum枚举类型,它用来指定Annotation的类型。
“每1个Annotation” 都与 “n个ElementType”关联。当Annotation与某个ElementType关联时,就意味着:Annotation有了某种用途。
例如,若一个Annotation对象是METHOD类型,则该Annotation只能用来修饰方法。
RetentionPolicy.java
public enum RetentionPolicy {
SOURCE, // 只保留在源代码中,编译器编译时,直接丢弃这种注解,不记录在.class文件中
CLASS, // 编译器把注解记录在class文件中。当运行Java程序时,JVM中不可获取该注解信息,这是默认值
RUNTIME // 编译器把注解记录在class文件中。当运行Java程序时,JVM可获取该注解信息。程序可以通过反射获取该注解的信息
}
RetentionPolicy 是Enum枚举类型,它用来指定Annotation的策略。
通俗点说,就是不同RetentionPolicy类型的Annotation的作用域不同
“每1个Annotation” 都与 “1个RetentionPolicy”关联。
- SOURCE:只保留在源代码中,编译器编译时,直接丢弃这种注解,不记录在.class文件中。
- CLASS:编译器把注解记录在class文件中。当运行Java程序时,JVM中不可获取该注解信息,这是默认值。
- RUNTIME:编译器把注解记录在class文件中。当运行Java程序时,JVM可获取该注解信息。程序可以通过反射获取该注解的信息。
准备工作
使用注解是在 Spring2.5(2.0以后陆续加入) 以后新加的功能,所以至少要保证版本是 2.5+,并且引入了注解的包
然后需要在 Spring 的配置文件中告诉它你使用了注解,最简单的一种方式就是加入下面的一句:
<context:component-scan base-package="com.xxx" />
它的意思就是开启自动扫描,会自动扫描你设置的包路径下的所有类,如果有注解就进行解析
这就是所谓的用注解来构造 IoC 容器;base-package 是可以指定多个包的,用逗号分割
注解分类
注解可以按照很多种方式分类,这里我按照下面的来分类
1、声明bean的注解
- @Componentn
- @Repository
- @Service
- @Controller
2、注入bean的注解
- @Autowired
- @Qualifier
- @Resource
3、Spring MVC常见注解
- @Controller
- @RequestMapping
- @RequestParam
- @PathVariable
- @RequestBody
- @RespopnseBody
- @ResController
其他。。。
声明bean的注解
注解 |
作用范围 |
含义 |
@Component |
注解在类上,可以作用在任何层次。 |
泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。 是一个泛化的概念,仅仅表示一个组件 (Bean) ,将一个实体类,放入bean中。 |
@Repository |
注解在类上 |
用于标注数据访问组件,即DAO组件。 |
@Service |
注解在类上 |
用于标注业务层组件 |
@Controller |
注解在类上 |
用于标注控制层组件(如struts中的action) |
所有声明后的类,都用统一被Spring IoC容器管理。
@Component
源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}
源码说明:
- @Target(ElementType.TYPE):可以用在类、接口(包括注释类型)或枚举上
- @Retention(RetentionPolicy.RUNTIME):编译器把注解记录在class文件中。当运行Java程序时,JVM可获取该注解信息。程序可以通过反射获取该注解的信息。
- @Documented:该注解能出现在Javadoc中
当一个类加上@Component注解后,
- 声明该类为通用的bean,并会被Spring IoC容器所管理
- 可以指定value,也就是bean的名字。不指定的话,默认为此类的首字母小写
- @Component 是所有受 Spring 管理组件的通用形式
- @Component 不推荐使用:因为太通用所以不推荐,实在不好归类的时候再用。
@Repository
源码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(annotation = Component.class)
String value() default "";
}
源码说明:
- @Target(ElementType.TYPE):可以用在类、接口(包括注释类型)或枚举上
- @Retention(RetentionPolicy.RUNTIME):编译器把注解记录在class文件中。当运行Java程序时,JVM可获取该注解信息。程序可以通过反射获取该注解的信息。
- @Documented:该注解能出现在Javadoc中
- @Component:说明该注解拥有@Component注解的所有属性,可理解为继承自@Component
当一个类加上@Controller注解后,
- 声明该类为数据访问层的bean,并会被Spring IoC容器所管理
- 可以指定value,也就是bean的名字。不指定的话,默认为此类的首字母小写
使用例子
@Repository
public class UserDaoImpl extends BaseDaoImpl<User> {
//TODO
}
@Service
源码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";
}
源码说明
- @Target(ElementType.TYPE):可以用在类、接口(包括注释类型)或枚举上
- @Retention(RetentionPolicy.RUNTIME):编译器把注解记录在class文件中。当运行Java程序时,JVM可获取该注解信息。程序可以通过反射获取该注解的信息。
- @Documented:该注解能出现在Javadoc中
- @Component:说明该注解拥有@Component注解的所有属性,可理解为继承自@Component
当一个类加上@Service注解后,
- 声明该类为业务层的bean,并会被Spring IoC容器所管理
- 可以指定value,也就是bean的名字。不指定的话,默认为此类的首字母小写
使用例子:
@Service
public class UserService{
//TODO
}
@Controller
源码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(annotation = Component.class)
String value() default "";
}
源码说明
- @Target(ElementType.TYPE):可以用在类、接口(包括注释类型)或枚举上
- @Retention(RetentionPolicy.RUNTIME):编译器把注解记录在class文件中。当运行Java程序时,JVM可获取该注解信息。程序可以通过反射获取该注解的信息。
- @Documented:该注解能出现在Javadoc中
- @Component:说明该注解拥有@Component注解的所有属性,可理解为继承自@Component
将一个类加上@Controller注解后,
- 声明该类为控制层的bean,并会被Spring IoC容器所管理
- 可以指定value,也就是bean的名字。不指定的话,默认为此类的首字母小写
使用例子
@Controller
public class CompanyController {
//TODO
}
注入bean的注解
@Autowired
源码
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
源码说明
- @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}):可以作用在CONSTRUCTOR、METHOD、PARAMETER、FIELD、ANNOTATION上
- @Retention(RetentionPolicy.RUNTIME):编译器把注解记录在class文件中。当运行Java程序时,JVM可获取该注解信息。程序可以通过反射获取该注解的信息。
- @Documented:该注解能出现在Javadoc中
- @Component:说明该注解拥有@Component注解的所有属性,可理解为继承自@Component
补充说明
- @Autowired注解可用于为类的属性、构造器、方法进行注值。
- 默认情况下,其依赖的对象必须存在(bean可用),如果需要改变这种默认方式,可以设置其required属性为false。
- @Autowired注解默认按照类型装配,如果容器中包含多个同一类型的Bean,那么启动容器时会报找不到指定类型bean的异常,解决办法是结合@Qualified注解进行限定,指定注入的bean名称
@Qualifier
源码
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
String value() default "";
}
源码说明
- @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}):可以作用在CONSTRUCTOR、METHOD、PARAMETER、FIELD、ANNOTATION上
- @Retention(RetentionPolicy.RUNTIME):编译器把注解记录在class文件中。当运行Java程序时,JVM可获取该注解信息。程序可以通过反射获取该注解的信息。
- @Inherited:@Inherited指定注解具有继承性。如果某个类使用了@xxx注解(定义该注解时使用了@Inherited修饰)修饰,则其子类将自动被@xxx修饰。
- @Documented:该注解能出现在Javadoc中
补充说明
这个注解需要配合@Autowired使用,是用来指定注入 Bean 的名称 ;因为@Autowired是按照类型来匹配的,如果一个类型有两个实现,直接使用@Autowired就会报错。
也就是说如果容器中有一个以上匹配的 Bean,则可以通过 @Qualifier 注解限定 Bean 的名称,否则调用的时候会抛异常
比如:某个 bean 中引用了一个接口,实现这个接口的 bean 有多个,Spring 在注入的时候就不知道注入那一个了,这样要么删除其他的 bean 要么就是使用 @Qualifier 注解。
我们来举个例子
Car.java
public interface Car{
public String carName();
}
两个实现类BMW和Benz:
BMW.java
@Service
public class BMW implements Car{
public String carName(){
return "BMW car";
}
}
Benz.java
@Service
public class Benz implements Car{
public String carName(){
return "Benz car";
}
}
写一个CarFactory,引用Car:
CarFactory.java
@Service
public class CarFactory{
// 报错!!!
@Autowired
private Car car;
public String toString(){
return car.carName();
}
}
不用说,一定是报错的,Car接口有两个实现类,Spring并不知道应当引用哪个实现类。这种情况通常有两个解决办法:
- 删除其中一个实现类,Spring会自动去base-package下寻找Car接口的实现类,发现Car接口只有一个实现类,便会直接引用这个实现类
- 实现类就是有多个该怎么办?此时可以使用@Qualifier注解
使用Qualifier()后的CarFactory.java
@Service
public class CarFactory{
@Autowired
@Qualifier("BMW")
private Car car;
public String toString(){
return car.carName();
}
}
@Resource
源码
// 并不是来自Spring,而是来自于Java
package javax.annotation;
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
String name() default "";
String lookup() default "";
Class<?> type() default java.lang.Object.class;
enum AuthenticationType {
CONTAINER,
APPLICATION
}
AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
boolean shareable() default true;
String mappedName() default "";
String description() default "";
}
源码说明:
- @Target({TYPE, FIELD, METHOD}):可以作用在TYPE、FIELD、METHOD上
- @Retention(RUNTIME):编译器把注解记录在class文件中。当运行Java程序时,JVM可获取该注解信息。程序可以通过反射获取该注解的信息。
补充说明
- 对于@Resource注解,它并不属于spring的注解,而是来自于JSR-250。
- @Resource默认为名称注入,但也可以指定 name或type 进行注入。
使用例子
public class Zoo {
// 通过名称进行注入
@Resource(name = "tiger")
private Tiger tiger;
// 通过类型进行注入
@Resource(type = Monkey.class)
private Monkey monkey;
}
@Resource与@Autowired区别
- @Autowired 默认按照 byType 方式进行 bean 匹配,@Resource 默认按照 byName 方式进行 bean 匹配
- @Autowired 是 Spring 的注解,@Resource 是 J2EE 的注解
Spring 属于第三方的,J2EE 是 Java 自己的东西
Spring MVC常见注解
@Controller
同上
@RequestMapping
源码
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
// 有以下属性
String name() default "";
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
源码说明:
- @Target({ElementType.METHOD, ElementType.TYPE}):可以作用在方法和类上
- @Retention(RetentionPolicy.RUNTIME):作用在运行时
- RequestMethod[]:以指定访问方式,如果不指定,默认既可以通过GET也可通过POST方式来访问
这个注解的属性有很多,这里只介绍常用的几种
使用例子
@Controller
// 可以作用在类上
@RequestMapping(value = "/aaa")
public class HappyController {
// 可以作用在方法上
// value可省略不写
@RequestMapping("/bbb")
public void sayHello() {
//TODO
}
// 可指定访问方式,GET还是POST
@RequestMapping(value = "/ccc", method = RequestMethod.GET)
public void sayHaHa() {
//TODO
}
}
@RequestParam
源码
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
源码说明:
- @Target(ElementType.PARAMETER):只能作用在参数上
- @Retention(RetentionPolicy.RUNTIME):运行时
- boolean required() default true;:required默认为true,也就是说参数必填
- defaultValue:默认参数,也就是不填参数时,默认是什么
补充说明:
@RequestParam :将请求的参数绑定到方法中的参数上,有required参数,默认为true,也就是改参数必须要传。如果改参数可以传可不传,可以配置false。
使用例子
@RequestMapping("/happy")
public String sayHappy(
// 作用在参数上
// 参数名为name,必填
@RequestParam(value = "name", required = true) String name,
// age不是必填项,默认为20
@RequestParam(value = "age", required = false, defaultValue = "20") String age){
//TODO
}
@PathVariable
源码
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
}
源码说明:
- @Target(ElementType.PARAMETER):作用在参数上
- @Retention(RetentionPolicy.RUNTIME):运行时
补充说明:
- @PathVariable : 该注解用于方法修饰方法参数,会将修饰的方法参数变为可供使用的uri变量(可用于动态绑定)。
- @PathVariable中的参数可以是任意的简单类型,如int, long, Date等等。Spring会自动将其转换成合适的类型或者抛出 TypeMismatchException异常。当然,我们也可以注册支持额外的数据类型。
- @PathVariable支持使用正则表达式,这就决定了它的超强大属性,它能在路径模板中使用占位符,可以设定特定的前缀匹配,后缀匹配等自定义格式。
使用例子:
@RequestMapping(value="/happy/{dayid}",method=RequestMethod.GET)
// 绑定 {dayid} 到String dayid
public String findPet(@PathVariable String dayid, Model mode) {
//TODO
}
@RequestBody
源码
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
boolean required() default true;
}
源码说明:
- @Target(ElementType.PARAMETER):只能作用在参数上
- @Retention(RetentionPolicy.RUNTIME):运行时
补充说明
添加 @RequestBody 后 Spring 会根据请求中的 Content-Type 头信息来选择合适的转换器, 将请求数据转为 Java 对象
比如Content-Type是application/json, 那么就是 JSON -> Model
使用例子:
@RequestMapping(value = "/something", method = RequestMethod.PUT)
public void handle(@RequestBody String body, Writer writer) throws IOException {
writer.write(body);
}
@ResponseBody
源码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}
源码说明:
- @Target({ElementType.TYPE, ElementType.METHOD}):可以作用在类和方法上
- @Retention(RetentionPolicy.RUNTIME):运行时
补充说明:
- 添加 @ResponseBody 后 Spring 会根据请求中的 Accept 头信息来选择合适的转换器, Java 对象转化为客户端可接受的表述形式,比如Accept头部信息包含“application/json”, 就是Model -> JSON
- @ResponseBody在输出JSON格式的数据时,会经常用到。
使用例子
@RequestMapping("/getJson")
@ResponseBody
public User jsonTest() {
User user = new User ();
user.setName("张三");
user.setAge(20);
return user;
}
结果
@RestController
- @RestController=@Controller+@ResponseBody
- 如果一个类有这个注解,那么就会在这个类下的每个方法都默认加上@ResponseBody
Java配置
除了xml和注解配置,Spring还提供了Java配置,什么叫java配置,即创建一个类来进行信息的注入,它和注解配置相似,不同的是它不是在bean的实现类中进行注解,而是新创建一个类进行配置: 这里涉及到了两个注解:
- @Configuration
- @Bean
User.java
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
//...
}
JavaConfig.java
//用来声明这个是由Java来配置
@Configuration
public class JavaConfig {
@Bean
public publisher createUser() {
return new user("张三",20);
}
}
JavaConfigTest.java
@Test
public void javaConfigTest() {
ApplicationContext context =
new AnnotationConfigApplicationContext(JavaConfig.class);
User user = context.getBean("user", User.class);
System.out.println(user.getName());
}
运行结果
参考:
http://www.voidcn.com/article/p-vnuuxhnq-bcq.html
https://www.bilibili.com/video/av21450362