要理解java注解首先要掌握几个概念,第一字节码中的attribute_info区域的作用,第二java反射,第三xml的解析。
注解起作用的过程中,注解信息只起着配置信息的作用,注解没有行为,没有动作,就把它看成是存储在attribute_info中的一段字符串就行了。
1.字节码中的attribute_info属性
如上图所示,显示的是java代码编译后字节码的结构,u2代表当前结构体占2个字节,cp_info/field_info/method_info/attribute_info
代表当前结构的类型,而这个结构的前一个结构往往是代表着这个结构的数量,比如
u2 fields_count
field_info fields[fields_count]
代表着这个类有fields_count数量的field字段。
我们再来深入看看field_info结构体是什么样子的。
access_flags代表访问权限修饰词(比如private,static,final等),name_index代表field字段的名称在常量池中的索引,descriptor代表着field的描述符比如8种基本类型和对象类型等等,那么其他的信息就存储在attribute_info
中,这个字段它代表着field字段的额外属性,比如假如这个field字段是final static String field=”hello world!”,那么这个“hello world!”字符串就会被保存在attribute_info结构里,代表着这个field的初始化值。
看完field_info
字段,我们再来看看method_info
结构体是什么样子的,如下:
同理可知,差不多和field一样但是有两点不同,就是method_field
的attribute_info
结构图内部还有code属性但是field_info是没有的,该属性存储的是该方法编译后的字节码,另外一点是descriptor_index
索引的常量区的内容会比field_info
更加复杂,因为field只有类型修饰,而method却有方法的参数,返回值,这些信息组合起来就是method_info结构体的description,(由此可见编译器在编译期间区分两个方法的不同,不仅仅通过方法的名称,还会比较方法的描述符,也就是方法的参数和返回值,这个和我们平常所看到的返回值不会作为区分重写的标准情况不一样)
具体的描述符在常量池中的形式举个例子就是如下:
//方法
int indexOf(char[] s,int sOf,int sC,char[] target,int tOf,int tC,int fI)
//方法描述符
([CII[CIII)I
也就是char数组 用“[C”表示,int用“I”表示,返回值放到最后面。从描述符来看,区分描述符的不同就有方法参数的顺序,方法的类型,方法个数以及返回值。
扯远了,然后找到了attribute_info
结构体之后就要看看这个结构体里面到底存着什么东西,如下图所示:
结构体有20多项,只列出和注解相关的属性,可以看到,存储着我们的注解信息。
那什么是注解呢?
其实注解是一个接口,是一个不同于平常的接口代码如下:
// 指定注解保留的范围 (运行期)
@Retention(RetentionPolicy.RUNTIME)
// 允许注解标注的位置 (属性, 方法)
@Target({ElementType.FIELD,ElementType.METHOD })
public @interface MyResource {
// 提供name属性
public String name() default "";
}
注解是一个@interface类型的类,内部方法不需要实现,在编译之后编译器会自动生成实现的类,给其他类注解的属性是以map集合的形式注解的,比如@MyResource(name=”测试类”)注解给main类,那么这个注解类就会保存在main类的attribute_info结构体中,而且方法的返回值就是“测试类”。
@MyResource("测试类")
public class main {
public void test() {
}
public static void main(String[] args){
Test t = main.class.getAnnotation(MyResource.class);
System.out.println(t.value());
}
打印的几个是: 测试类
意料之中,因为MyResource.class在编译后是保存在main类中的,因此可以得到注解类,调用注解类的value方法就可以返回“测试类”这个字符串了,这里没有用map形式是因为注解类只有一个方法,因此不会混淆也就没有必要这样写。
注解就是一个存储了信息的对象,它没有行为,也没有动作。
那么Spring的那种依赖注入到底是怎么实现的呢?
其实是java代码根据注解的信息,通过反射设定field实现的。
我们分三步走:
第一步解析需要注入的beans.xml生成map
public static Map<String,BeanDefinition> read(String name){
Map<String,BeanDefinition> classmap = new HashMap<String,BeanDefinition>();
SAXReader saxReader= new SAXReader();
/**
* 如果path以"/"开头,例如:"/a.txt",则从classpath的根下获取资源;
* 如果path不以"/"开头,例如"a.txt",则从类所在的包路径下取资源。
*/
System.out.println(name+"\n"+XMLparse.class.getClassLoader().getResource(name));
URL xmlpath= XMLparse.class.getClassLoader().getResource(name);
Document document;
try {
document = saxReader.read(xmlpath);
Map<String, String> map=new HashMap<String, String>();
map.put("ns", "http://www.springframework.org/schema/beans");
XPath xpath=document.createXPath("//ns:beans/ns:bean");
xpath.setNamespaceURIs(map);
List<Element> beans= xpath.selectNodes(document);
for(int i=0;i<beans.size();i++){
Element element=beans.get(i);
String id = element.attributeValue("id");
String classname= element.attributeValue("class");
BeanDefinition beanDefinition =new BeanDefinition(id, classname);
XPath Epath=element.createXPath("ns:property");
Epath.setNamespaceURIs(map);
List<Element> eList= Epath.selectNodes(element);
for (Element element2 : eList) {
String propertyname= element2.attributeValue("name");
String ref= element2.attributeValue("ref");
String value= element2.attributeValue("value");
PropertyDefinition propertyDefinition=new PropertyDefinition(propertyname, ref, value);
beanDefinition.getPropertyDefinitions().add(propertyDefinition);
}
classmap.put(id, beanDefinition);
}
} catch (DocumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return classmap;
}
第二步根据第一步生成的map集合,生成object集合:
private void InitObject() {
Iterator iterable= map.keySet().iterator();
BeanDefinition beanDefinition ;
while(iterable.hasNext()){
beanDefinition = map.get(iterable.next());
try {
/**
* 记得给需要反射生成对象的类加上无参构造函数,要不然报错
* at java.lang.Class.newInstance(Unknown Source)
*/
Object object = BeansManager.class.getClassLoader().loadClass(beanDefinition.getClazz()).newInstance();
Beansmap.put(beanDefinition.getId(), object);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
第三步就是扫描所有的object对象,将对象中注解信息解析出来匹配对应的注入对象,然后通过反射设置对象,完成了对象的注入:
/**
* 通过XML内部的property属性注入
*/
public void injectObjectByXML(){
Iterator iterator=map.keySet().iterator();
while (iterator.hasNext()) {
BeanDefinition beanDefinition= map.get(iterator.next());
List proDens= beanDefinition.getPropertyDefinitions();
for (PropertyDefinition prodefine : proDens) {
if(Beansmap.containsKey(prodefine.getRef())){
Object injectobject= Beansmap.get(prodefine.getRef());
Object beInjectObject = Beansmap.get(beanDefinition.getId());
Field[] fields= beInjectObject.getClass().getDeclaredFields();
if (injectobject!=null) { //如果注入的bean存在
for (Field field : fields) {
if (field.getName().equals(prodefine.getName())) {
field.setAccessible(true);
try {
field.set(beInjectObject, injectobject);
break;
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
}
}
/**
* 注解注入
*/
public void injectObjectByAnnotation(){
Iterator iterator=Beansmap.keySet().iterator();
while (iterator.hasNext()) {
Object object= Beansmap.get(iterator.next());
try {
/** 通过函数注解
* 获取这个类的所有域的所有属性描述
*/
PropertyDescriptor[] propertyDescriptors= Introspector.getBeanInfo(object.getClass()).getPropertyDescriptors();
for(int i=0;inull;
//获取第一个域的所有属性描述
PropertyDescriptor proDec= propertyDescriptors[i];
//获取这个域的setter方法
Method setter= proDec.getWriteMethod();
if (setter!=null && setter.isAnnotationPresent(MyResource.class)) {
MyResource myResource= setter.getAnnotation(MyResource.class);
//通过注解找bean注入
if(myResource.name()!=null && !"".equals(myResource.name())){
injectBean= Beansmap.get(myResource.name());
}else{//注解找不到,那么通过属性的名称找
injectBean= Beansmap.get(proDec.getName());
//属性的名称找不到,那么通过属性的类型找
if(injectBean==null){
for(String key: Beansmap.keySet()){
if (proDec.getPropertyType().isAssignableFrom(Beansmap.get(key).getClass())) {
injectBean= Beansmap.get(key);
break;
}
}
}
}
setter.setAccessible(true);
setter.invoke(object, injectBean);
}
}
/**
* 通过域注解
*/
Field[] fields= object.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field=fields[i];
if(field.isAnnotationPresent(MyResource.class)){
MyResource myResource= field.getAnnotation(MyResource.class);
Object value=null;
if(myResource.name()!=null && !"".equals(myResource.name())){
value=Beansmap.get(myResource.name());
}else {
value=Beansmap.get(field.getName());
if (value==null) {
for(String key: Beansmap.keySet()){
if (field.getType().isAssignableFrom(Beansmap.get(key).getClass())) {
value= Beansmap.get(key);
break;
}
}
}
}
field.setAccessible(true);
field.set(object, value);
}
}
} catch (IntrospectionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
通过上面的三个步骤,就可以完成注入对象的功能了,
我这里是shopservice中注入person和shop对象,其中的xml文件配置了这三个对象。
然后使用自定义的注解给shopservice中注入了person和shop。
id="person" class="user.person" />
id="shop" class="user.shop" />
id="shoppingService" class="user.shoppingService">
<property name="mShop" ref="shop">property>
<property name="mPerson" ref="person">property>
下面就来测试一下:
public static void main(String[] args){
/** 记得需要把所有需要注入的类都加上空参数的构造函数,要不然报错
* at java.lang.Class.newInstance(Unknown Source)
* 如果path以"/"开头,例如:"/a.txt",则从classpath的根下获取资源;
* 如果path不以"/"开头,例如"a.txt",则从类所在的包路径下取资源。
*/
BeansManager beansManager= BeansManager.getInstance("beans.xml");
shoppingService service= (shoppingService) beansManager.getObject("shoppingService");
service.service();
}
打印如下:
加载的xml名称是:beans.xml
全路径名是:file:/C:/Users/ezqiulv/Akkaproject/target/classes/beans.xml
王思聪 is shopping at 万达广场。
工程需要依赖dom4j-1.6.1.jar和jaxen-1.1.6.jar。