Java注解以及用注解实现简单的小型Spring框架

正文索引 [隐藏]

1. 注解的定义

    注解在Java中是无实际意义的,并不能影响程序的运行结果以及逻辑。但是注解在Java中起着标签的作用,为代码运行提供一些特殊的信息,就像为某件事物加标签一样,从标签我们能得知程序的某些信息。注解也是Java类型的一种,可以看做是一个特殊的类,它的定义如下:

public @interface Value {

    public String value();

}

2. 注解的种类

    在Java中,最初始的注解一共有五个,这些注解被称作元注解,用来注解其它的注解或者其它Java信息,下面讲解这5种元注解:

2.1 @Retention

    @Retention注解的作用为标注注解是保留在什么时期,我们知道程序在运行起来有:源码、编译、运行这三种阶段,而@Retention注解的三种属性恰好对应这三种阶段:

  • RetentionPolicy.RUNTIME:该属性表明注解会保留到程序运行时的阶段,我们常在框架中(比如Spring)使用的注解,都是标注为该属性,因为框架需要通过反射拿到注解的信息,反射是在运行阶段起作用的,所以必须使用该种属性;
  • RetentionPolicy.CLASS:该属性表明注解会保留到Java程序编译后的字节码中,但是在运行阶段会被擦除,即不参与jvm。
  • RetentionPolicy.SOURCE:该属性表明注解只会保留在Java源程序中,而不会参与到字节码的转化中去,只存在源码阶段。

    而如果没有特别用该注解标注注解的话,Java注解默认会保留到字节码中,而不会保留到运行阶段,即保留到RetentionPolicy.CLASS阶段。

2.2 @Target

    @Target注解的作用是表明其它注解该标注在什么类型上,是标注在类级别上,还是字段级别上,亦或者是方法级别上,它有如下属性:

  • ElementType.TYPE:表明注解标注一个类型,比如在类、接口、枚举;
  • ElementType.FIELD:表明注解标注一个字段;
  • ElementType.ANNOTATION_TYPE:表明注解标注一个注解;
  • ElementType.CONSTRUCTOR:表明注解标注一个构造器;
  • ElementType.METHOR:表明注解标注一个方法;
  • ElementType.PARAMETER:表明注解标注一个方法参数;
  • ElementType.PACKAGE:表明注解标注一个包;
  • ElementType.LOCAL_VARIABLE:表明注解标注一个局部变量;
  • ElementType.TYPE_PARAMETER:表明注解用来标注类型参数的,即标注泛型的参数的;
  • ElementType.TYPE_USE:表明注解可以用来标注以上所有可以标注的地方。

2.3 @Documented

    该元注解的作用为将注解中的元素包含到Java doc文档中去。

2.4 @Inherited

    该元注解的作用是标明注解可以被继承,即子类会继承父类被@Inherited注解的注解。比如下面这个例子:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Share {
}

@Share
public class Father {
}

public class Son extends Father {

    public static void main(String []args){
        boolean isAnotatio= Son.class.isAnnotationPresent(Share.class);
        if (isAnotatio){
            System.out.println("已被@Share注解标注");
        }
    }
}
//打印:已被@Share注解标注

    该元注解注解的注解一般用于类与接口上,方便注解被子类继承,简化了代码的幅度。

2.5 @Repeatable

    该元注解表明该注解可以被赋值多次,该注解实质上指定了一个容器注解,如下面的代码:

public @interface Likes {
     Like [] value();
}

@Repeatable(Likes.class)
public @interface Like {
     String role();
}

@Like(role = "蕾姆")
@Like(role = "拉姆")
public class RepeatTest {
}

3.自定义注解

    自定义注解一般都会标注@Retention@Target,表明注解的保留时期与标注的目标是谁,而@Retention注解一般会选择保留到运行期,因为一般用注解来标注一些信息,并在运行时期用反射的方式获取到这些信息,比如:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ZhuJie {
    public int check() default 1;
}

public class ZhuJieTest {

    @ZhuJie(check = 50)
    private int value;

    public static void main(String []args){
        Field[] fields=ZhuJieTest.class.getDeclaredFields();
        boolean isAnotatio= fields[0].isAnnotationPresent(ZhuJie.class);
        if (isAnotatio){
            ZhuJie zhuJie=fields[0].getAnnotation(ZhuJie.class);
            System.out.println(zhuJie.check());//打印:50
        }
    }
}

4.利用注解来实现简单的Spring框架

    为了加深对注解的使用以及理解,下面带大家用注解简单的实现一个小型的Spring框架,首先,我们定义常用的三个注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface Value {

    public String value();

}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Componet {

    public String name() default "";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired{

}

    定义了@Value@Componet@Autowired这三个注解用来实现赋值、自动装配以及将类注册成Bean类,接着我们需要一个Bean类以及Bean的工厂类来存放所有的Bean类:

public class Bean<T> {

    private T object;

    private String beanName;

    public Bean(T o,String beanName){
        this.object=o;
        this.beanName=beanName;
    }


    public T getObject() {
        return object;
    }

    public String getBeanName() {
        return beanName;
    }
}

public class BeanFactory {

    private static Map<String,Bean> map=new HashMap<String, Bean>();

    private BeanFactory(){}

    public static void addBean(Bean o){
        map.put(o.getBeanName(),o);
    }

    public static boolean isExsisBean(Bean o){
        if (map.containsKey(o.getBeanName())){
            return true;
        }
        return false;
    }

    public static Bean getBeanByName(String beanName){
        return map.get(beanName);
    }

    public static Object getBean(String beanName){
        return map.get(beanName).getObject();
    }

    public static int getBeanSize(){
        return map.size();
    }
}

    在Bean类中用了泛型来存放真正的bean实例,里面拥有bean的名字,在这里我们使用首字符小写的方法为Bean类命名,符合Spring的命名规范。而BeanFactory利用一个Map来存放Bean名字到Bean实例的映射,提供获取Bean实例的方法。

    接着就是对Ioc容器的初始化了,首先应当找到所有Bean实例所在的包,将该包下的所有类都找出来,然后利用反射判断该类有无@Componet注解,有则代表这个类需要实例化成一个Bean类,然后再接着判断@Value@Autowired注解,为其相应的属性赋值,代码如下:

public class IocUtil {

    /**
     * 传入bean实例所在包的相对引用
     * 例:com.suyeq.bean,
     * @param pakageReference
     */
    public void initIoc(String pakageReference) {
        try {
            ClassLoader classLoader = ClassLoader.getSystemClassLoader();
            URL url=classLoader.getResource(pakageReference);
            File file=new File(url.toString().substring(6));
            if (file.isDirectory()){
                File [] fils=file.listFiles();
                for (File file1:fils){
                    String []strings=file1.getName().replace(".","/").split("/");
                    loadBean(pakageReference+"."+strings[0]);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 将Bean实例化
     * @param beanLimitedName 类的相对引用
     */
    public void loadBean(String beanLimitedName){
        try {
            Class aclass=ClassLoader.getSystemClassLoader().loadClass(beanLimitedName);
            Object object=aclass.newInstance();
            //判断是否有Componet注解
            if (aclass.isAnnotationPresent(Componet.class)){
                Field [] fields=aclass.getDeclaredFields();
                for (int i=0;i<fields.length;i++){
                    fields[i].setAccessible(true);
                    if (fields[i].isAnnotationPresent(Value.class)){
                        String value=fields[0].getDeclaredAnnotation(Value.class).value();
                        fields[i].set(object,value);
                    }
                    if (fields[i].isAnnotationPresent(Autowired.class)){
                        Class bClass=fields[i].getType();
                        String className=bClass.getSimpleName();
                        //bean类实例首字母小写
                        className=className.toLowerCase().charAt(0)+""+className.substring(1);
                        Bean bean=BeanFactory.getBeanByName(className);
                        if (bean != null){
                            fields[i].set(object,bean.getObject());
                        }
                    }
                }
                String nowBeanName=aclass.getSimpleName();
                Bean bean=new Bean<>(object,nowBeanName.toLowerCase().charAt(0)+""+nowBeanName.substring(1));
                BeanFactory.addBean(bean);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public static void main(String []args) {
        new IocUtil().initIoc("bean");
        //System.out.println(BeanFactory.getBeanSize());
        UserService userService=(UserService) BeanFactory.getBean("userService");
        userService.showData();
    }
}

    我们定义两个Bean类,来测试代码:

@Componet
public class UserDao {

    public void findUserById(String id){
        System.out.println("已查询id为"+id+"的数据");
    }
}

@Componet
public class UserService {

    @Value("10")
    String id;

    @Autowired
    UserDao userDao;

    public void showData(){
        userDao.findUserById(id);
    }
}

    运行程序后得结果:已查询id为10的数据。例外,整个项目的图示:
在这里插入图片描述