想知道Spring这类框架中的注解为什么能实现那么强大的功能,请看本篇博客,用几乎逐行的注释讲解原理。
反射
利用反射可以动态获取类的信息,并在程序运行期间动态创建对象,许多框架如spring、mybatis等均利用到了这一机制。
在编写代码或编译的过程中,可能无法得知要创建哪个对象,只有在运行时才能确定,这种情况下就需要利用反射机制,在运行时获取对象的各种信息。
利用一个例子帮助理解:
创建两个实体类;
1 2 3 4 5
| public class Pizaa { private Integer id; private String type; }
|
1 2 3 4 5
| public class Hamburger { private Integer id; private String type; }
|
创建一个配置文件,模拟spring等框架的配置文件,此处以properties配置文件为例;
创建一个测试类;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class Test {
private static Properties properties;
static { try { properties = new Properties(); properties.load(Test.class.getClassLoader().getResourceAsStream("bean.properties")); } catch (IOException e) { e.printStackTrace(); } }
public static void main(String[] args) throws Exception{ String bean = properties.getProperty("bean"); Class clazz = Class.forName(bean); Constructor constructor = clazz.getConstructor(null); Object target = constructor.newInstance(null); System.out.println(target); } }
|
此时运行结果为:
1
| Pizaa{id=null, type='null'}
|
如果将配置文件改为
1
| bean=reflection.Hamburger
|
则运行结果为
1
| Hamburger{id=null, type='null'}
|
利用这一机制可以实现一定程度上的解耦,无需用在编码时就指定要创建的实例类型,只需在配置文件中指定即可,使得类型的修改变得容易。
注解
注解需要结合反射来实现,注解本身只起到标记的作用,不进行实际的操作,实际操作由反射进行。
创建两个自定义注解,这里模拟Spring框架中的@Component
注解和@Value
注解;
注意:此处自定义注解的名称和Spring框架中相同,是因为笔者并未引入Spring相关的依赖,故不会产生冲突。
1 2 3 4 5 6 7
|
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) public @interface Component { }
|
1 2 3 4 5 6 7 8
| @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) public @interface Value { String value(); }
|
然后在实体类上打注解;
1 2 3 4 5 6 7
| @Component public class Pizaa { @Value("1") private Integer id; @Value("bacon") private String type; }
|
创捷另一个测试类;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| public class Test2 {
private static Properties properties;
static { try { properties = new Properties(); properties.load(Test.class.getClassLoader().getResourceAsStream("bean.properties")); } catch (IOException e) { e.printStackTrace(); } }
public static void main(String[] args) throws Exception { String bean = properties.getProperty("bean"); Class clazz = Class.forName(bean); Annotation componentAnno = clazz.getAnnotation(Component.class); if (componentAnno != null) { Constructor constructor = clazz.getConstructor(null); Object target = constructor.newInstance(null);
Field[] declaredFields = clazz.getDeclaredFields(); for (Field declaredField : declaredFields) { Value valueAnnoOnId = declaredField.getAnnotation(Value.class); if (valueAnnoOnId != null) { String value = valueAnnoOnId.value(); declaredField.setAccessible(true); switch (declaredField.getType().getName()) { case "java.lang.Integer": Integer val = Integer.parseInt(value); declaredField.set(target, val); break; default: declaredField.set(target, value); break; } } } System.out.println(target); } else { System.out.println("无法创建" + clazz.getName() + "对象"); } } }
|
此时运行结果为:
1
| Pizaa{id=1, type='bacon'}
|
若将实体类上的注解注释掉;
1 2 3 4 5 6 7
| public class Pizaa { @Value("1") private Integer id; @Value("bacon") private String type; }
|
则运行结果为:
通过上述例子可以看到,注解并不进行任何实际的操作,仅仅作为标记作用,而实际的操作需要通过反射机制,在运行时获取目标类后进行判断,若不为空则说明添加了该注解,而后进行一系列的业务处理。