欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java必知:注解(注解方式实现SpringIoc的思路)

程序员文章站 2024-01-03 11:58:22
...

什么是注解

注解和注释的区别:
注释(comment): 单词直译是”评论,解释”,在Java的编写过程中我们需要对一些程序进行说明,除了自己方便阅读,更为别人更好理解自己的程序。
注解(annotation): 单词直译是”注文,评注”,最为主要的点在于”解”。即程序在运行过程中,需要按照规范来进行解释它,甚至将会影响程序的运行逻辑。
注释与注解最主要的区别是注释不参与编译和运行。只是文字的说明而已,注解的话参与代码的解释和运行过程。
注解的详细定义:
修饰java程序元素的元信息,java语言的元素包括类,方法,变量,参数,包。注解的核心作用就是修饰这些元素,甚至在编译或者运行的过程中,影响这些元素。
注解产生的背景:
开发时间长点的朋友肯定配置过Spring或者Struts的XML配置。在以往我们是基于XML进行相关的配置,但是如果你总结过的话,整个XML万变不离其宗的是,他的配置都是对java的元素进行配置。比如配置类的包类名全路径,接口名,方法名,字段名等等…但是大家会发现随着业务的复杂度和项目不变的变大。XML的内容也越来越复杂,而且维护成本很高.因为本身代码和XML的配置之间并没有那么直观的产生联系,后续加入团队的成员需要从头理顺这个配置文件的含义和表达,成本是很高的。所以有人提出来,是否有一种标记式的高耦合的配置方法来解决这样得问题。所以就产生了“注解”,注解是JAVA5.0推出来的新特性。
注解的优势:
下面通过代码,我们来实际的看看注解给我们的开发带来的便利。
例如:spring中bean实例化:

XML的方式:

Java必知:注解(注解方式实现SpringIoc的思路)
Java必知:注解(注解方式实现SpringIoc的思路)
Java必知:注解(注解方式实现SpringIoc的思路)

Annotation的方式:

Java必知:注解(注解方式实现SpringIoc的思路)
Java必知:注解(注解方式实现SpringIoc的思路)
Java必知:注解(注解方式实现SpringIoc的思路)
通过上诉的案例,我们通过xml和annotation的方式都可以完成bean实例对象的创建过程,但是annotation在代码的简洁度和可读性上具有非常大的优势。所以注解目前广泛的运用于我们的各大框架技术。后续我们想阅读源码理解大牛的设计思想,注解是非常常见的内容,所以学好注解也是一个必然的趋势。

注解的学习

注解的本质理解:
注解从代码层面来看,其实就是一个接口,一个继承了Annotation的接口。
注解的架构:
Java必知:注解(注解方式实现SpringIoc的思路)
从上图,我们可以得知:
ElementType:代表当前注解用于那种元素类型上(比如,类,方法等…)。Annotation与ElementType是1:N的关系
RetentionPolicy:代表当前注解的有效期范围。Annotation与RetentionPolicy是1:1的关系
Deprecated,Override,Documented...:JDK自带的一些内置的注解实现

接下来我们通过学习JDK中内置的注解的方式,来体系的了解注解。

内置注解:

Deprecated:
这个注解是用来标记已经过时(即不被建议使用)的元素[方法.字段.类…]

Java必知:注解(注解方式实现SpringIoc的思路)
例如:
使用了这样注解的元素,将会出现这样的标记,这个标记即表示这个构造器是一个过时的构造器,不建议再使用。
Java必知:注解(注解方式实现SpringIoc的思路)

Override:
这个注解相信大家很熟悉吧。这个注解是用来标记方法的重写。
通过@Override注解我们可以很快速的知道当前方法是覆写自接口或者父类。

Java必知:注解(注解方式实现SpringIoc的思路)
通过上述案例.我们知道了@Deprecated这个注解是用来标记已经过时(即不被建议使用)的元素[方法.字段.类…]
通过@Override注解我们可以很快速的知道当前方法是覆写自接口或者父类.
那@Deprecated到底能作用于哪些元素,@Override能作用于哪些元素?接下来我们来讲解JDK中定义的元注解

元注解:

概念:注解的注解且只能作用于注解上的注解就是元注解。????有点绕不过不难理解。

@Target
定义该注解可以用于哪些元素。取值范围ElementType枚举,可以多个取值。也就是上边说到的1:N的关系

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE, //用于类
    
    /** Field declaration (includes enum constants) */
    FIELD, //用于字段

    /** Method declaration */
    METHOD, //用于方法

    /** Formal parameter declaration */
    PARAMETER, //用于参数

    /** Constructor declaration */
    CONSTRUCTOR, //用于构造器

    /** Local variable declaration */
    LOCAL_VARIABLE, //用于局部变量

    /** Annotation type declaration */
    ANNOTATION_TYPE, //用于注解类型声明

    /** Package declaration */
    PACKAGE, //用于包

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER, //用于类型参数

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE //使用类型
}

@Retention
定义注解的保留策略,或者说定义注解的有效范围。取值范围RetationPolicy枚举

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE, //注解的作用只存在于源码阶段

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS, //注解的作用存在于源码和编译后的字节码阶段

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME //注解的作用存在于源码阶段,字节码和运行期阶段,且注解的内容会被JVM执行
}

这里的三个枚举值是有等级关系的:
Source < Class < Runtime 也可以这么理解Runtime的有效期范围是最大的.其次是Class,最小的即是Source

注意:
1.在运行期间通过反射的方式去获取元素的注解,那就只能是RetetionPolicy.RUNTIME
2.如果要在编译时进行一些预处理操作,我们可以将我们级别定位成RetetionPolicy.CLASS
3.如果是一些检查性的工作,或者生成辅助的代码等等。比如@Override 就是检查我们的方法必须是覆写的方法,或者lombok 中@Data @Setter @Getter注解

@Documented:
标记使用的注解是否包含在生成的用户文档中。
@Inherited:
如果一个使用了@Inherited修饰的annotation类型被用于一个类,则这个annotation将被用于该class的子类。仅限父类使用@Inherited标记的注解,子类将自动获得父类的该注解,接口的继承和接口的实现都不继承。

自定义注解(注解方式实现SpringIoc的思路)

接下来我们还是基于Spring的IOC ,我们采用模仿 Component 和 Bean 两个注解的方式进行自定义注解的学习。
我们定义了2个注解分别为CzyComponent 和 CzyBean

Java必知:注解(注解方式实现SpringIoc的思路)
我们指定 @CzyComponent 注解,使用元素 ElementType.TYPE指定作用于类上
有效期范围在运行期都有效 RetentionPolicy.RUNTIME ,因为我们要基于反射进行bean实例的创建,反射的定义我们也了解过,在运行期间的一套机制,所以指定运行期有效。
Java必知:注解(注解方式实现SpringIoc的思路)
我们指定 @CzyBean 注解,使用元素 ElementType.Method指定作用于方法上,有效期范围在运行期都有效 RetentionPolicy.RUNTIME 因为我们要基于反射进行bean实例的创建,反射的定义我们也了解过,在运行期间的一套机制,所以指定运行期有效。

class A,class B,class C :

Java必知:注解(注解方式实现SpringIoc的思路)
Java必知:注解(注解方式实现SpringIoc的思路)
Java必知:注解(注解方式实现SpringIoc的思路)

ioc容器:

public class IOCContainer {
    private static HashMap container = new HashMap();
    public static void putBean(String id,Object object){
        container.put(id,object);
    }
    public static Object getBean(String id){
        return container.get(id);
    }
}

接下来基于这样的配置,我们进行注解方式的IOC实现:

第一步进行Class文件的扫描:

说明:我们在xml中肯定配置的有这样的包扫描标签,spring中肯定还是先读取配置文件,获取出来要扫描的包路径即com.czy.project.demo2下的类,这里我们就先省略读取xml获取包路径的步骤,直接进行class扫描的步骤。
Java必知:注解(注解方式实现SpringIoc的思路)
编写进行Class文件的扫描的方法:

  	/**
     * 得到指定包下所有的class 的全路径
     * @param allClassPathSet:装载类全路径的集合
     * @param scanPackage:指定扫描的包路径
     * 注意:该方法为Main类中的静态方法
     */
    public static void doScanner(Set<String> allClassPathSet, String scanPackage) {
        URL url = Main.class.getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File classDir = new File(url.getFile());
        for (File file : classDir.listFiles()) {
            if (file.isDirectory()) {
                //是文件夹,递归循环
                doScanner(allClassPathSet, scanPackage + "." + file.getName());
            } else {
                //如果文件不是.class结尾
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                //拼装类的全路径
                String clazzName = (scanPackage + "." + file.getName().replace(".class", ""));

                //添加到集合中
                allClassPathSet.add(clazzName);
            }
        }
    }

第二步遍历Class文件通过反射判断注解信息,通过反射完成Bean的实例化过程,放入IOC容器中

        for (String className : allClassPathSet) {
            Class<?> clazz = Class.forName(className);//获取该class实例
            //如果该类上有@CzyComponent注解,就直接将该类添加到ioc容器中
            if (clazz.isAnnotationPresent(CzyComponent.class)) {
                IOCContainer.putBean(className, clazz.newInstance());
            }
            //获取该该类中的所有方法
            Method[] methods = clazz.getDeclaredMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                if (method.isAnnotationPresent(CzyBean.class)) { //如果该方法上有@CzyBean注解
                    //获取beanName例如@CzyBean("b")即获取出;b
                    String beanName = method.getAnnotation(CzyBean.class).value();
                    //判断该方法是否为静态方法
                    if (Modifier.isStatic(method.getModifiers())) {
                        IOCContainer.putBean(beanName, method.invoke(null));//静态工厂创建实例
                    } else {
                        //从容器中获取当前这实例对象
                        //实例工厂创建实例
                        IOCContainer.putBean(beanName, method.invoke(IOCContainer.getBean(className)));
                    }
                }
            }
        }

上诉就是我们手工实现的简单版IOC容器的注解方式。
下边是完整的Main类代码,方便直接拿着测试:

public class Main {
    public static void main( String[] args) throws Exception {
        //装 类 全路径的集合
        HashSet<String> allClassPathSet = new HashSet<>();
        doScanner(allClassPathSet, "com.czy.project.demo2");
        for (String className : allClassPathSet) {
            Class<?> clazz = Class.forName(className);//获取该class实例
            //如果该类上有@CzyComponent注解,就直接将该类添加到ioc容器中
            if (clazz.isAnnotationPresent(CzyComponent.class)) {
                IOCContainer.putBean(className, clazz.newInstance());
            }
            //获取该该类中的所有方法
            Method[] methods = clazz.getDeclaredMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                if (method.isAnnotationPresent(CzyBean.class)) { //如果该方法上有@CzyBean注解
                    //获取beanName例如@CzyBean("b")即获取出;b
                    String beanName = method.getAnnotation(CzyBean.class).value();
                    //判断该方法是否为静态方法
                    if (Modifier.isStatic(method.getModifiers())) {
                        IOCContainer.putBean(beanName, method.invoke(null));//静态工厂创建实例
                    } else {
                        //从容器中获取当前这实例对象
                        //实例工厂创建实例
                        IOCContainer.putBean(beanName, method.invoke(IOCContainer.getBean(className)));
                    }
                }
            }
        }
    }
   	/**
     * 得到指定包下所有的class 的全路径
     * @param allClassPathSet:装载类全路径的集合
     * @param scanPackage:指定扫描的包路径
     * 注意:该方法为Main类中的静态方法
     */
    public static void doScanner(Set<String> allClassPathSet, String scanPackage) {
        URL url = Main.class.getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File classDir = new File(url.getFile());
        for (File file : classDir.listFiles()) {
            if (file.isDirectory()) {
                //是文件夹,递归循环
                doScanner(allClassPathSet, scanPackage + "." + file.getName());
            } else {
                //如果文件不是.class结尾
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                //拼装类的全路径
                String clazzName = (scanPackage + "." + file.getName().replace(".class", ""));

                //添加到集合中
                allClassPathSet.add(clazzName);
            }
        }
    }
}

上一篇:

下一篇: