文章目录
注解的基本元素
如果说注释是写给人看的,那么注解就是写给程序看的。它更像一个标签,贴在一个类、一个方法或者字段上。
当声明一个注解时,用到的东西:
- 修复符: 访问修饰符必须为public,不写默认为pubic
- 关键字:关键字为@interface;
- 注解名称: 注解名称为自定义注解的名称,使用时还会用到;
- 注解类型元素:注解类型元素是注解中内容,可以理解成自定义接口的实现部分;
- ()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法
- 基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组
public @interface Info {
String value() default "tracy";
boolean isDelete();
}
使用元注解修饰注解
JDK中有一些元注解,主要有@Target,@Retention,@Document,@Inherited用来修饰注解
@Target值介绍
表明该注解可以应用的java元素类型
Target的类型 | 值描述 |
---|---|
ElementType.TYPE | 应用于类、接口(包括注解类型)、枚举 |
ElementType.FIELD | 应用于属性(包括枚举中的常量) |
ElementType.METHOD | 应用于方法 |
ElementType.PARAMETER | 应用于方法的形参 |
ElementType.CONSTRUCTOR | 应用于构造函数 |
ElementType.LOCAL_VARIABLE | 应用于局部变量 |
ElementType.ANNOTATION_TYPE | 应用于注解类型 |
ElementType.PACKAGE | 应用于包 |
ElementType.TYPE_PARAMETER | 1.8版本新增,应用于类型变量 |
ElementType.TYPE_USE | 1.8版本新增,应用于任何使用类型的语句中(例如声明语句、泛型和强制转换语句中的类型) |
@Retention值介绍
表明该注解的生命周期
Retention取值 | 值描述 |
---|---|
RetentionPolicy.SOURCE | 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃 |
RetentionPolicy.CLASS | 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期 |
RetentionPolicy.RUNTIME | 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在 |
@Documented
描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息
@Inherited
表明使用了@Inherited注解的注解,所标记的类的子类也会拥有这个注解
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Info {
String value() default "tracy";
boolean isDelete();
}
自定义注解
比如有一个要评分的活动,对于其中的每个子项目进行评分(加分,减分,不加分也不减分)
定义一个注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.FIELD)
public @interface MarkReason {
//评分项目名称
String reasonName();
//是否减分项
boolean isSubtraction() default false;
//是否待评估
boolean isUnknown() default false;
//是否加分项
boolean isAddition() default true;
}
定义一个类,里面使用到注解:
public class UserMark {
@MarkReason(reasonName="卫生清扫干净")
private Double score1;
@MarkReason(reasonName="节约用电",isUnknown = true,isAddition=false)
private Double score2;
@MarkReason(reasonName="节约水资源",isSubtraction=true,isAddition = false)
private Double score3;
private Double score4;
}
获取注解里面的内容:
public static void setMarkReasons() throws Exception {
Class beanClass = UserMark.class;
/**
* getFields():获得某个类的所有的公共(public)的字段,包括父类中的字段。
* getDeclaredFields():获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。
*/
Field[] fields = beanClass.getDeclaredFields();
//遍历属性
for (Field field : fields) {
//判断该属性是否被 MarkReason 注解修饰
if (field.isAnnotationPresent(MarkReason.class)) {
StringBuilder reasonsSb = new StringBuilder();
//允许私有属性访问
field.setAccessible(true);
MarkReason reasonAnno = field.getAnnotation(MarkReason.class);
//判断是否是减分项
String isSubtraction = "无";
if (reasonAnno.isSubtraction()) {
isSubtraction = "-10";
}
//判断是否是加分项
String isAddition = "无";
if (reasonAnno.isAddition()) {
isAddition = "+10";
}
//判断是否待评估
String isUnknown="否";
if(reasonAnno.isUnknown()){
isUnknown = "是";
}
//拼装加减分原因
reasonsSb.append(reasonAnno.reasonName() + ":" + field.getName() + ";-->减分:"+isSubtraction+ ";-->加分:"+isAddition+ ";-->待评估:"+isUnknown);
System.out.println("被注解修饰的结果:"+reasonsSb.toString());
}else{
System.out.println("没有被注解修饰的:"+field.getName());
}
}
}
运行时注解(如@Autowired)会采用反射机制处理,针对编译时注解(如@Override)会采用 AbstractProcessor
自定义注解解析器
自己创建一个类,继承AbstractProcessor
@AutoService(Processor.class)
//等同于下面写的getSupportedSourceVersion()方法
@SupportedSourceVersion(SourceVersion.RELEASE_8)
//等同于下面写的getSupportedAnnotationTypes()方法
@SupportedAnnotationTypes(value= {"com.example.usersport.annotation.MarkReason"})
public class FactoryProcessor extends AbstractProcessor {
private Types typeUtils;
private Elements elementUtils;
private Filer filer;
private Messager messager;
/**返回值表示注解是否由当前Processor 处理。如果返回 true,则这些注解由此注解来处理,
* 后续其它的 Processor 无需再处理它们;如果返回 false,则这些注解未在此Processor中处理,
* 那么后续 Processor 可以继续处理它们
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
System.out.println("进入了process");
Messager messager = processingEnv.getMessager();
for (Element ele : roundEnvironment.getElementsAnnotatedWith(MarkReason.class)) {
if (ele.getKind() == ElementKind.FIELD) {
messager.printMessage(Diagnostic.Kind.NOTE, "输出值是:" + ele.toString());
}
}
return true;
}
/**
* 这个方法用于初始化处理器,方法中有一个ProcessingEnvironment类型的参数,ProcessingEnvironment是一个注解处理工具的集合。它包含了众多工具类。例如:
* Filer可以用来编写新文件;
* Messager可以用来打印错误信息;
* Elements是一个可以处理Element的工具类
* @param processingEnvironment
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
System.out.println("进入了init");
super.init(processingEnv);
typeUtils = processingEnv.getTypeUtils();
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}
/**
* 这个方法的返回值是一个Set集合,集合中指要处理的注解类型的名称
* (这里必须是完整的包名+类名,例如com.example.annotation.Factory)。
* 由于在本例中只需要处理@Factory注解,因此Set集合中只需要添加@Factory的名称即可
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
System.out.println("进入了getSupportedAnnotationTypes");
Set<String> annotations = new LinkedHashSet<>();
annotations.add(MarkReason.class.getCanonicalName());
annotations.add(Controller.class.getCanonicalName());
return annotations;
}
/**
* 用来指定当前正在使用的Java版本,通常return SourceVersion.latestSupported()即可
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
System.out.println("getSupportedSourceVersion");
return SourceVersion.latestSupported();
}
}
类的顶部加入注解:@AutoService(Processor.class),这个注解处理器是Google开发的。
当启动程序时,可以用来生成 META-INF/services/javax.annotation.processing.Processor
效果图:
常用的注解
@Component
用来定义Bean, 不好归类时使用
- @Controller:定义控制层Bean
- @Service:定义业务层Bean
- @Repository:定义DAO层Bean
@Deprecated
若某类或某方法加上该注解之后,表示此方法或类不再建议使用,调用时也会出现删除线,但并不代表不能用,只是说,不推荐使用,因为还有更好的方法可以调用。
//给方法添加此注解
@Deprecated
OCRInfo<BankCardResult> getBankCardInfo(String image,String platform);
加入注解后,在调用此方法的位置会有提示:
@Controller和@RestController
- @Controller
- @RestController :@RestController=@ResponseBody+@Controller;
- 直接在这个类上使用@RestController,类里面的方法上就可以不加@Responsebody
- 如果你想处理完请求后,然后跳到另外一个页面,不要在你的这个类上使用@RestController
@Autowired与@Resource
都可以用来装配bean. 都可以写在字段上,或写在setter方法上。
- @Autowired默认按类型装配(这个注解是属于spring的),
- 默认情况下必须要求依赖对象必须存在,即:@Autowired(required=true),
- 如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配
- 可以结合@Qualifier注解进行使用,解决按类型匹配找到多个Bean问题
- @Resource(这个注解属于J2EE的),
- 默认按name注入,找不到名称匹配的bean再按类型装配,可以通过name和type属性进行选择性注入
- 可以写在成员属性上,或写在setter方法上
- 可以通过@Resource(name=“beanName”) 指定被注入的bean的名称, 要是未指定name属性, 默认使用成员属性的变量名,一般不用写name属性
@RequestParam
将请求参数绑定到你控制器的方法参数上
语法:@RequestParam(value=”参数名”,required=”true/false”,defaultValue=””)
value:请求中传入参数的名称
required:是否包含该参数,默认为true,表示该请求路径中必须包含该参数,如果不包含就报错。
defaultValue:默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值
@RequestBody
主要用来接收前端传递给后端的json字符串中的数据,GET方式无请求体,前端不能使用GET方式提交数据,而是用POST方式进行提交
请求的类:
@RequestMapping("/json")
@ResponseBody
public UserJsonTest json(@RequestBody UserJsonTest userJsonTest) {
System.out.println("进入了UserJsonTest");
System.out.println(userJsonTest.toString());
return userJsonTest;
}
实体类:(省略了getter,setter)
public class UserJsonTest {
@JsonAlias(value={"Name","name1","name2"})
private String name;
private int age;
private String gender;
@JsonProperty("NOTEMESSAGE")
private String noteMessage;
private String flag;
}
请求报文:
{
"name2":"张三",
"age":"18",
"gender":"男男",
"gender":"女女",
"test":"没有用",
"NOTEMESSAGE":"这是个注释",
"noteMessage":"666"
}
返回报文:
{
"name": "张三",
"age": 18,
"gender": "女女",
"flag": null,
"NOTEMESSAGE": "这是个注释"
}
运行时控制台日志:
进入了UserJsonTest
UserJsonTest{name='张三', age=18, gender='女女', noteMessage='这是个注释', flag='null'}
解释:
根据不同的Content-Type等情况,Spring-MVC会采取不同的HttpMessageConverter实现来进行信息转换解析。
下面介绍的是最常用的:前端以Content-Type 为application/json,传递json字符串数据;后端以@RequestBody
模型接收数据的情况。
- 使用 @JsonAlias注解,实现:json转模型时,使json中的特定key能转化为特定的模型属性;但是模型转json时,对应的转换后的key仍然与属性名一致
- 使用@JsonProperty注解,实现:json转模型时,使json中的特定key能转化为指定的模型属性;同样的,模型转json时,对应的转换后的key为指定的key(示例中的NOTEMESSAGE字段)
- 使用@JsonAlias注解需要依赖于setter、getter,而@JsonProperty注解不需要。
- 在不考虑上述两个注解的一般情况下,key与属性匹配时,默认大小写敏感
- 有多个相同的key的json字符串中,转换为模型时,会以相同的几个key中,排在最后的那个key的值给模 型属性复制,因为setter会覆盖原来的值。(见示例中的gender属性)
- 后端@RequestBody注解对应的类在将HTTP的输入流(含请求体)装配到目标类(即:@RequestBody后面
的类)时,会根据json字符串中的key来匹配对应实体类的属性,如果匹配一致且json中的该key对应的值
符合(或可转换为)实体类的对应属性的类型要求时,会调用实体类的setter方法将值赋给该属性
@ResponseBody
将java对象转为json格式的数据,作用在方法上,将该方法的返回结果直接写入 HTTP response body 区;用此注解之后不会再走视图处理器
使用@ResponseBody例子:
@RequestMapping("/json/response/1")
@ResponseBody
public UserJsonTest jsonResponse1() {
System.out.println("进入了jsonResponse");
UserJsonTest user = new UserJsonTest();
user.setAge(78);
user.setName("李四1");
user.setNoteMessage("这是注释");
return user;
}
浏览器中请求的效果图:
不使用@ResponseBody例子:
@RequestMapping("/json/response/2")
public void jsonResponse2( HttpServletResponse response) throws Exception {
System.out.println("进入了jsonResponse");
UserJsonTest user = new UserJsonTest();
user.setAge(78);
user.setName("李四2");
user.setNoteMessage("这是注释");
response.setCharacterEncoding("utf-8");
response.setContentType("application/json");
response.getWriter().write(user.toString());
}
浏览器中请求的效果图:
@RequestMapping
它可以在方法和类的声明中使用;看它的属性中有好多方法均返回数组,也就是可以定义多个属性值
它的源码:
@Target({ElementType.TYPE, ElementType.METHOD})
@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 {};
}
@AliasFor表示别名,它可以注解到自定义注解的两个属性上,表示这两个互为别名,也就是说这两个属性其实同一个含义;无论指明设置哪个属性名设置属性值,另一个属性名也是同样属性值。若两个都指明属性值,要求值必须相同(必须声明相同的返回类型,必须声明默认值且默认值要相同),否则会报错。
举例属性值的使用:
@RequestMapping(value="/json/requestMapping",
method= {RequestMethod.POST,RequestMethod.GET},
params={"username=picc","password=123"},
headers={"Host=localhost:8087"},
produces=MediaType.APPLICATION_JSON_VALUE+";charset=iso-8859-1",
consumes= {MediaType.APPLICATION_JSON_VALUE,MediaType.APPLICATION_ATOM_XML_VALUE})
@ResponseBody
public String jsonRequestMapping( HttpServletResponse response) throws Exception {
System.out.println("进入了requestMapping");
UserJsonTest user = new UserJsonTest();
user.setAge(78);
user.setName("李四2");
user.setNoteMessage("这是注释");
return user.toString();
}
注解中的属性值作用:
属性值示例 | 作用描述 | 报错时参考图 |
---|---|---|
method= {RequestMethod.POST,RequestMethod.GET} | 同时指定多个请求方式 | ![]() |
params={“username=picc”,“password=123”} | 该属性表示请求参数,也就是追加在URL上的键值对,多个请求参数以&隔开(此时要求请求的url后面必须要有username=picc&password=123) | ![]() |
headers={“Host=localhost:8087”} | 可以限制客户端发来的请求(此处限制请求为本地请求且端口是8087) | ![]() |
consumes= {MediaType.APPLICATION_JSON_VALUE,MediaType.APPLICATION_ATOM_XML_VALUE} | 指定处理请求的提交内容类型(Content-Type) | ![]() |
produces=MediaType.APPLICATION_JSON_VALUE+";charset=iso-8859-1" | 可以设置返回值类型还可以设定返回值的字符编码 | ![]() |