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

什么是注解

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

XML的方式:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Annotation的方式:

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

注解的学习

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

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

内置注解:

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

在这里插入图片描述
例如:
使用了这样注解的元素,将会出现这样的标记,这个标记即表示这个构造器是一个过时的构造器,不建议再使用。
在这里插入图片描述

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

在这里插入图片描述
通过上述案例.我们知道了@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

在这里插入图片描述
我们指定 @CzyComponent 注解,使用元素 ElementType.TYPE指定作用于类上
有效期范围在运行期都有效 RetentionPolicy.RUNTIME ,因为我们要基于反射进行bean实例的创建,反射的定义我们也了解过,在运行期间的一套机制,所以指定运行期有效。
在这里插入图片描述
我们指定 @CzyBean 注解,使用元素 ElementType.Method指定作用于方法上,有效期范围在运行期都有效 RetentionPolicy.RUNTIME 因为我们要基于反射进行bean实例的创建,反射的定义我们也了解过,在运行期间的一套机制,所以指定运行期有效。

class A,class B,class C :

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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扫描的步骤。
在这里插入图片描述
编写进行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);
            }
        }
    }
}

<<上一篇:springIoc利用反射机制是如何实现的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值