java自定义注解
一、为什么需要自定义注解
有的时候我们需要程序在编译或者运行时可以检测到某些标记而进行一些特殊处理,可以通过自定义注解来实现。注解可以看作时一种特殊的标记,可以用在类,属性,方法和包上,是一种能被添加到java源代码中的元数据。
二、注解的原理
注解的本质是继承了Annotation接口的特殊接口,⑦具体实现类是Java 运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java 运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke 方法。该方法会从memberValues 这个Map 中索引出对应的值。而memberValues 的来源是Java 常量池。
三、了解自定义注解
1.我们先看一段自定义注解的代码,认识一下:
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnnotation {
// 姓名
String name() default "匿名";
// 地址
String adress() default "";
}
2.如何声明自定义注解
修饰符: 必须为public,不写默认为pubic
关键字: @interface,使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
自定义注解名称:自己命名
使用元注解修饰自定义注解:@Target,@Retention,@Document,@Inherited用来修饰注解
声明配置参数:每一个方法实际上是声明了一个配置参数。
3.@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版本新增,应用于任何使用类型的语句中(例如声明语句、泛型和强制转换语句中的类型) |
4.@Retention
表面该注解的生命周期
生命周期类型 | 描述 |
---|---|
RetentionPolicy.RUNTIME | 由JVM 加载,包含在类文件中,在运行时可以被获取到 |
RetentionPolicy.SOURCE | 编译时被丢弃,不包含在类文件中 |
RetentionPolicy.CLASS | JVM加载时被丢弃,包含在类文件中,默认值 |
5.@Document
表明该注解标记的元素可以被Javadoc 或类似的工具文档化
6.@Inherited
表明使用了@Inherited注解的注解,所标记的类的子类也会拥有这个注解
7.配置参数
- 其中的每一个方法就是一个配置参数
- 方法的名称就是参数的名称
- 返回值就是参数的类型(返回值只能是基本类型,Class,String,enum)
- 可以通过default来声明参数的默认值
- 如果返回一个参数成员,一般参数名为value
- 配置参数必须要有值,经常我们用空字符串和0作为默认值
8.jdk1.8又提供了以下两个元注解:
- @Native:指定字段是一个常量,其值引用native code。
- @Repeatable:注解上可以使用重复注解,即可以在一个地方可以重复使用同一个注解,像spring中的包扫描注解就使用了这个。
9.自定义注解的应用
四、条件注解
1.@Conditional注解
Conditional 是由 SpringFramework 提供的一个注解,位于 org.springframework.context.annotation 包内,定义如下。
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
Conditional 注解类里只有一个 value 属性,需传入一个 Condition 类型的数组,我们先来看看这个 Condition 接口长什么样。
package org.springframework.context.annotation;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.core.type.AnnotatedTypeMetadata;
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
其中,matches() 方法传入的参数 ConditionContext 是专门为 Condition 而设计的一个接口类,可以从中获取到Spring容器的以下对象信息:
当一个 Bean 被 Conditional 注解修饰时,Spring容器会对数组中所有 Condition 接口的 matches() 方法进行判断,只有当其中所有 Condition 接口的 matches()方法都为 ture 时,才会创建 Bean 。
2.自定义Conditional
接下来,我们将以一个国际化 I18n Bean 动态创建为例(根据配置中的 i18n.lang 属性值来动态地创建国际化 I18n Bean),对如何使用 Conditional 注解进行简单举例:
- 当 i18n.lang=zh_CN 就创建中文 I18nChs Bean
- 当 i18n.lang=en_US 就创建英文 I18nEng Bean
创建好的两个 Condition 实现类 I18nChsCondition 和 I18nEngCondition 代码如下
package com.lys.myannotation;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class I18nChsCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
String lang = context.getEnvironment().getProperty("i18n.lang");
ConditionOutcome outCome = new ConditionOutcome("zh_CN".equals(lang), "i18n.lang=" + lang);
return outCome;
}
}
package com.lys.myannotation;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class I18nEngCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
String lang = context.getEnvironment().getProperty("i18n.lang");
ConditionOutcome outCome = new ConditionOutcome("en_US".equals(lang), "i18n.lang=" + lang);
return outCome;
}
}
I18n 接口定义:
package com.lys.myannotation;
public interface I18n {
// 获取 name 属性的值
String i18n(String name);
}
I18n 接口的两个实现类 I18nChs 和 I18nEng 定义如下。
package com.lys.myannotation;
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Component;
@Component
@Conditional(I18nChsCondition.class)
public class I18nChsImpl implements I18n {
Map<String, String> map = new HashMap<String, String>() {
private static final long serialVersionUID = 1L;
{
put("lang", "中文");
}
};
@Override
public String i18n(String name) {
return map.get(name);
}
}
package com.lys.myannotation;
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Component;
@Component
@Conditional(I18nEngCondition.class)
public class I18nEngImpl implements I18n {
Map<String, String> map = new HashMap<String, String>() {
private static final long serialVersionUID = 1L;
{
put("lang", "English");
}
};
@Override
public String i18n(String name) {
return map.get(name);
}
}
配置 application.properties 内容如下:
language : zh_CN/Chinese,en_US/America
i18n.lang=zh_CN
测试代码如下:
package com.lys;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.lys.bean.Person;
import com.lys.myannotation.I18n;
@RunWith(SpringRunner.class)
@SpringBootTest
public class LysStartTests {
@Autowired
I18n i18n;
@Test
public void testConditional() {
System.out.println(i18n.getClass().getName());
System.out.println(i18n.i18n("lang"));
}
}
运行testConditional()测试方法,打印结果:
配置 application.properties 内容如下:
language : zh_CN/Chinese,en_US/America
i18n.lang=en_US
再次运行程序,打印结果:
为了书写和调用方便,我们还可以把上面的条件定义成注解,以 I18nChsCondition 为例,定义代码如下。
package com.lys.myannotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(I18nChsCondition.class)
public @interface I18nChs {
}
将 I18nChs 注解添加到 I18nChsImpl 上。
package com.lys.myannotation;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Component;
@Component
@I18nChs
public class I18nChsImpl implements I18n {
Map<String, String> map = new HashMap<String, String>() {
private static final long serialVersionUID = 1L;
{
put("lang", "中文");
}
};
@Override
public String i18n(String name) {
return map.get(name);
}
}
3.SpringBoot 扩展注解
从上面的示例不难看出,如果要使用我们自定义条件类实现起来还是有点小麻烦的,不过比较庆幸的是, SpringBoot 在 Conditional 注解的基础上已经提前为我们定义好了一系列功能丰富的注解,我们可以直接使用。
接下来我们使用 ConditionalOnProperty 注解来实现上面的国际化示例。
仅需修改 I18nChsImpl 和 I18nEngImpl 两个实现组件类,其他代码不变,程序执行结果与之前相同。
@Component
@ConditionalOnProperty(name = "i18n.lang", havingValue = "zh_CN", matchIfMissing = true)
public class I18nChsImpl implements I18n {//内容同上,此处省略}
@Component
@ConditionalOnProperty(name = "i18n.lang", havingValue = "en_US", matchIfMissing = false)
public class I18nEngImpl implements I18n {//内容同上,此处省略}