美文网首页
第2讲.注解/JavaBean/内省机制

第2讲.注解/JavaBean/内省机制

作者: 祥祺 | 来源:发表于2020-02-18 20:15 被阅读0次

    注解/JavaBean/内省机制

    加载配置文件

    什么是硬编码?

    在Java开发中,我们很多地方都需要使用到配置文件
    以前我们所有的数据都是放在Java代码中的,

    如:
    String username=”admin”;
    String password=”123”;
    

    在今后,我们的密码是可以修改的,所以在登录的时候需要将Java代码中写死的密码同时修改。
    首先要找到项目的源码--->再修改对应的Java代码--->编译---->发布

    总结:硬编码的概念。

    1.将数据写死在Java代码中
    
    2.后期会不定时的去做修改
    

    解决方案:使用配置文件解决硬编码问题。

    把数据写到一个配置文件中,通过Java在运行时去读取里面的数据

    什么是配置文件

    在java的开发中,配置一些以后可能经常修改的数据,参数到一个文件中,这个文件存放在项目中,通过写代码进行加载该文件,并进行读取文件中存的数据。

    配置文件特点:无论是开发过程中还是编译之后,还是生产环境中,都是一个状态

    编写配置文件的分类

    在java中,配置文件分为两类。

    一个是以.properties为扩展名的文件

    配置文件是以properties为扩展名的,其内容为键值对形式存储,且键名和键值都是字符串格式。JAVA提供java.util.Properties类,可以非常方便的读取配置文件的信息。

    java在对*.properties文件进行操作的时候,实际上是通过IO对文档进行逐行的扫描,然后将文中非注释的部分存放在一个properties对象中。Properties 实际上是继承了hashtable,实现了Map接口。可以这样理解,它是进行了进一步封装的HashMap。存放到properties中后,可以对properties进行一系列的操作,此时的数据保存在内存中。最后,当需要保存数据的时候,是将properties中所有的键值重新写入到文件中去。 对properties文件的操作,jdk提供了一系列的API。实现了对properties文件的增删查改的功能。

    一个是以.xml为扩展名的文件

    是一种可扩展的标记语言,类似HTML。

    文件是使用一堆标签来包装数据的。

    一般存储的数据具有一定的结构性,层次性。例如省市区。

    两种配置文件相比较,第一种编写起来和使用起来更为简单些。

    编写properties配置文件

    选中项目右击鼠标,选择SourceFolder选项,创建一个resources文件夹,这个文件夹,是专门用来存储配置文件的。

    在开发工具中,源文件夹(Source Folder)中的资源都会同步输出到字节码输出目录。

    什么是字节码输出目录?

    Java源文件编译后,字节码存放的目录,对于STS开发工具来讲默认就在当前项目的bin目录中

    我们在Java开发中,如果要加载外部的资源到JVM中通常情况下都是从字节码输出目录中加载的,因为对于Java程序而言无论什么情况下,都一定会有字节码输出目录的

    加载properties配置文件

    类加载器是把Java字节码加载到JVM中的工具,其主要是从字节码输出目录中加载

    类加载器可以从字节码的输出目录中加载资源

    ClassLoader类中的方法:

    InputStream getResourceAsStream(String name) : 从字节码输出路径中加载资源
    

    读取到的数据在流中,但是取出数据操作时存在很多的不方便

    在java中提供了Properties类来方便我们操作流中的属性信息

        properties = new Properties();
    
    //加载配置文件方式一:(不推荐) 通过类的加载器从项目的编译路径classPath路径加载 ,不安全
    
    //InputStream inputStream = 当前类.class.getClassLoader().getResourceAsStream("config.properties");
    
    //加载配置文件方式二:(推荐)通过Thread类来获取当前类的类加载器 
    
    InputStream inputStream = Thread.currentThread().getContextClassLoader()
    
                    .getResourceAsStream("config.properties");
    
    properties.load(inputStream); 
    

    JavaBean/内省机制

    什么是JavaBean?

    JavaBean 是一种JAVA语言写成的可重用组件。

    为写成JavaBean,类必须是具体的和公共的,并且具有无参数的构造器。

    JavaBean 通过提供符合一致性设计模式的公共方法将内部域暴露成员属性。众所周知,属性名称符合这种模式,其他Java 类可以通过自省机制发现和操作这些JavaBean 的属性。

    JavaBean中的属性和字段

    字段:在类中定义的成员变量

    private String name;
    

    属性:由类中的getter或setter方法决定

        getName--->属性名是去掉get,将首字母小写得到的名称就是属性名:name
    
        setName---->属性名是去掉set,将首字母小写得到的名称就是属性名:name
    

    字段名和属性名绝大多数时候是一样的,但是可以不一样

    注意:普通的字段对应的get方法就是getXxx();
    
    如果字段类型是boolean,那么对应的get方法是:isXxx();
    

    JavaBean的类中包含的成员

    1.字段

    2.方法

    3.事件:了解,SE中的内容

    Introspector:内省机制核心类

    如果使用反射,我们可以操作JavaBean中的字段/方法/构造器等,如果我们在开发中需要大量的操作JavaBean的属性(getter/setter方法)

    所以,我们可以选择一种更专业的手段来操作JavaBean的属性---内省机制(底层使用反射实现)

    内省机制的主要作用:用于操作javabean中的属性

    操作步骤:

    1.获取JavaBean相关的信息对象:BeanInfo
    
    2.该BeanInfo中就会封装有当前Bean的成员(字段/属性/事件)
    
    3.获取到对应的属性,对其操作
    

    java.beans.Introspector类:

    常用API:

    static BeanInfo getBeanInfo(Class<?> beanClass) : 获取字节码对象对应的JavaBean信息 
    
    static BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass)
    

    通过一张图来认识一下这两个API的区别。

    1.png

    java.beans.BeanInfo接口:

    常用API:

    PropertyDescriptor[] getPropertyDescriptors() : 获取所有的属性描述器
    

    java.beans.PropertyDescriptor类:

    常用API:

    String getName() : 获得属性的名称 
    
    Class<?> getPropertyType() : 获得属性的类型 
    
    Method getReadMethod() : 获得用于读取属性值的方法
    
    Method getWriteMethod() : 获得用于设置属性值的方法
    

    lombok插件

    Lombok是什么

    Lombok是一款小巧的代码生成工具。官方网址:http://projectlombok.org/

    LomBok主要特性有:自动生成默认的getter/setter方法、自动化的资源管理(通过@Cleanup注解)及注解驱动的异常处理等。目前在国外广泛应用。

    LomBok它和jquery一样,目标是让程序员写更少的代码,以及改进一些原始语法中不尽人意的地方。Lombok能做到这一点。既不是用annotations process,也不是用反射。而是直接黑到了编译过程中。所以对运行效率没有任何影响,我们可以通过反编译class文件进行验证。

    为何项目中要引入Lombok

    主要因为以下三点:

      1. 提高开发效率
      1. 使代码直观、简洁、明了、减少了大量冗余代码(一般可以节省60%-70%以上的代码)
      1. 极大减少了后期维护成本

    如何安装,如何使用

    • 1:java -jar lombok.jar

    • 2:在Java项目中导入lombok.jar包

    • 3:使用注解@Setter/@Getter/@ToString/@Data/@AllArgsConstructor/@NoArgsConstructor等

    插件lombok的安装流程图
    3.png 4.png

    流程图中通过java -jar的命令,来安装lombok插件。其本质是:

    5.png
    lombok插件的使用流程图
    6.png 7.png 8.png 9.png 10.png

    JavaBean和MAP的互转

    Map是由key-value组成,key是不能重复的.

    JavaBean是由属性名和属性值组成,属性名是不同的.

    如果把JavaBean的属性名看做是Map的key,把属性值看做是Map的value,那么一个Map对象和一个JavaBean是等级的.
    如图:

    2.png
    // 需求:把一个javaBean 转换成Map
        public Map<String, Object> bean2Map(Object bean) throws Exception {
            Map<String, Object> map = new HashMap<String, Object>();
            // 获取传入对象的字节码对象
            Class<? extends Object> clz = bean.getClass();
            // 通过内省获取该类的BeanInfo对象
            BeanInfo beanInfo = Introspector.getBeanInfo(clz, Object.class);
            // 通过BeanInfo对象获取属性描述器
            PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor propertyDescriptor : descriptors) {
                // 获取属性名称
                String name = propertyDescriptor.getName();
                // 获取getter方法对象
                Method readMethod = propertyDescriptor.getReadMethod();
                // 通过反射执行方法获取值
                Object value = readMethod.invoke(bean);
                map.put(name, value);
            }
            return map;
        }
    
    
    // 需求:把一个Map转换成javaBean
        public <T> T map2Bean(Map<String, Object> map, Class<T> clz) throws Exception {
            // 根据字节码对象获取类的真实对象
            T instance = clz.newInstance();
            // 通过内省获取该类的BeanInfo对象
            BeanInfo beanInfo = Introspector.getBeanInfo(clz, Object.class);
            // 通过BeanInfo对象获取属性描述器
            PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor propertyDescriptor : descriptors) {
    
                // 属性名称作为map中的key,根据key获取value
                Object value = map.get(propertyDescriptor.getName());
                // 获取方法对象
                Method writeMethod = propertyDescriptor.getWriteMethod();
                // 把value 作为参数,通过反射调用setter方法
                writeMethod.invoke(instance, value); // 相当于instance.setXxx(value) 方法
            }
            return instance;
        }
    }
    

    注解

    什么是注解

    java.lang.annotation,接口 Annotation。

    对于Annotation,是Java5的新特性,JDK5引入了Metadata(元数据)很容易的就能够调用Annotations。Annotations提供一些本来不属于程序的数据,比如:一段代码的作者或者告诉编译器禁止一些特殊的错误。An annotation 对代码的执行没有什么影响。Annotations使用@annotation的形式应用于代码:类(class),属性(attribute),方法(method)等等。一个Annotation出现在上面提到的开始位置,而且一般只有一行,也可以包含有任意的参数。

    通过一张图来深入理解一下注解的含义:

    11.png

    使用注解需要注意,必须有三方参与才有意义:

    1):得有注解标签本身;
    2):被贴的程序元素(类,字段,构造器,方法,参数上,包上等);
    3):由第三方的程序使用反射的手段来赋予注解特殊的功能(也是Java代码). 
    

    JDK中的内置注解

    public class CommonAnnotation {
    
        // JDK5中提供的三个注解
        /*
        @Override    只能贴在方法上
        @Deprecated   可以贴在变量上,方法上, 类上 
        @SuppressWarnings 可以贴在变量上,方法上,类上
        */
    
        /*作用:表示该方法是重写父类的方法,
         会帮我们验证方法名称是否和父类中的方法名称是否一致*/
        @Override
        public String toString() {
            return super.toString();
        }
    
        /*作用:表示该方法已经过时,
        不建议使用,但是可以使用*/
        @Deprecated
        private void MylocalString() {
    
        }
    
        /*作用:表示抑制警告,
        只是看不到警告,不代表没有警告*/
        @SuppressWarnings("rawtypes")
        List list = new ArrayList();
    
        // JDK7中新增了一个注解
        /*
         @SafeVarargs 一个方法中同时出现可变参数和泛型
         */
        /*抑制堆污染发出的警告
        问题依然存在*/
        @SafeVarargs
        public static <T> List<T> asList(T... a) {
            return null;
        }
    
        // JDK8中增加一个注解
        /*@FunctionalInterface  函数式接口 
         */
        /*接口中只有一个抽象方法*/
        @FunctionalInterface
        public interface MyRunnable {
            void run();
        }
    }
    

    JDK中的元注解

    元注解有4个:

    @Retention、@Target、@Documented、@Inherited
    
    其中@Documented、@Inherited只需要了解
    

    @Documented 表示注解会被javadoc指令编辑到API中

    @Inherited 表示注解会遗传给子类


    @Target

    @Target:决定了该注解可以贴在什么地方

    可以贴注解的地方有很多,都封装在枚举:ElementType中

    ElementType.ANNOTATION_TYPE 贴在注解上
    
    ElementType.CONSTRUCTOR  贴在构造方法上    
    
    ElementType.FIELD  贴在字段上(包括枚举常量)
    
    ElementType.LOCAL_VARIABLE   贴在局部变量上          
    
    ElementType.METHOD  贴在方法上
    
    ElementType.PACKAGE  贴在包上(极少使用) 
    
    ElementType.PARAMETER  贴在参数上 
    
    ElementType.TYPE 贴在类、接口或枚举上
    

    其中贴在类/字段/方法上要重点知道,以后要用到


    @Retention

    @Retention:决定注解可以保存到哪个时期

    注解的有效期有3个都封装在枚举:RetentionPolicy中

    RetentionPolicy.SOURCE: 表示注解只存在于源文件中,不会被编译到字节码中
    
    RetentionPolicy.CLASS:  表示注解会被编译到字节码中,但是JVM不加载注解
    
    RetentionPolicy.RUNTIME: 表示注解会被编译到字节中,会随着字节码的加载而进入JVM,因此可以反射性地读取
    

    通过一张图来区分三者的有效期的不同

    12.png

    我们开发中的自定义的注解的有效期都要使用RUNTIME,这样才能在程序运行时使用反射赋予注解功能

    反编译演示有些注解不会被编译到字节码中

    思考:

    自定义的注解应该保存到哪个时期呢?

    RUNTIME时期,为啥?
    
    需要使用反射来赋予注解特殊的含义,反射是在运行阶段才生效
    

    定义注解的语法

    使用@interface 注解名称

        例如:public @interface VIP {
                //抽象方法 属性
              }
    

    注解的抽象方法称为属性,如果要给这个属性赋予默认值可以在抽象方法后使用default 值


    使用注解语法:@注解名[(属性名=属性值,属性名=属性值)]

    例如:@VIP
    

    注意:给属性赋值时如果只有1个属性要赋值且名称叫value时,可以省略value不写 如:@VIP("xxx")


    注意:属性的返回值类型只能是基本类型/枚举/Class/注解/String/枚举以及他们各自的数组

    自定义注解的步骤:

    1.自定注解: public @interface 注解名称{}

    2.说明当前方法能够贴的位置和能够保存的时期

    @Target:  值保存在ElementType枚举类中
    
    @Retention: 值保存在RetentionPolicy枚举类中
    

    3.将注解贴在能够贴的位置

    4.为注解传递参数

        需要在注解中定义属性
    
    语法:和接口中定义抽象方法是一样的
    
    类型  属性名()  default  默认值 ;
    
    特殊使用: 如果属性名是value,同时在使用的时候我们只需要为value设值的话,此时可以省略value属性名
    

    例如:在后面的Servlet中,会用到一个注解: @WebServlet(“/hello”)

    操作注解:

    意识:注解是贴在类上,方法上,字段上等位置
    
    所以,对应的Class/Method/Field类上应该会有操作注解的方法
    
    <T extends Annotation>  T   getAnnotation(Class<T> annotationClass) 
    

    自定义注解的案例:

    MyAnnotation.java

    @Target({ ElementType.METHOD, ElementType.TYPE }) // 确定作用目标
    @Retention(RetentionPolicy.RUNTIME) // 存活周期
    public @interface MyAnnotation {
        String value() default "少壮不努力老大徒伤悲";
        //
        //  int age();
        //
        //  String name();
    
        /*
         注意:
          ElementType.METHOD 表示可以作用在方法上,
          ElementType.TYPE 表示可以作用在类上,接口上,
          RetentionPolicy.RUNTIME 表示当前注解可以在运行阶段使用
             如果在使用注解的时候,属性名为value的属性传值并且注解中只有这一个抽象方法,
             此时可以省略属性名
            可以通过default 来设置默认值
        */
    }
    
    

    Employee.java

    @MyAnnotation("哈哈")
    // 注解括号中的key=value 内容 相当于注解中的抽象方法的实现
    public class Employee {
    
        @MyAnnotation(value = "哈哈")
        public void init() {
            System.out.println("init 方法 ");
        }
    }
    

    AnnotationTest.java

    public class AnnotationTest {
    
        // 通过反射获取注解的内容
        @Test
        public void testAnno() throws Exception {
            // 通过反射获取Employee的字节码对象
            Class<?> clz = Class.forName("cn.wolfcode.day02._02_.annotation.Employee");
            // 通过字节码对象获取自定义注解对象
            MyAnnotation annotation = clz.getAnnotation(MyAnnotation.class);
            // 通过注解对象调用自身的抽象方法获取对应的值
            String value = annotation.value();
            System.out.println(value);
    
            Method method = clz.getMethod("init");
            MyAnnotation annotation2 = method.getAnnotation(MyAnnotation.class);
            System.out.println(annotation2.value());
    }
    }
    

    相关文章

      网友评论

          本文标题:第2讲.注解/JavaBean/内省机制

          本文链接:https://www.haomeiwen.com/subject/hpmpyqtx.html