zl程序教程

您现在的位置是:首页 >  Java

当前栏目

java基础-反射与内省

2023-03-31 10:45:20 时间

以下为本人的学习笔记

 

1.反射

反射:通过对象获取对象的类信息

反射:JVM 在运行的时、对任意的一个对象、都能访问到这个类的属性和方法。

java反射机制是在运行状态中,对于任意一个类,都能直到这个类的所有属性和方法;对于任意一个对象,都能调用他的任意一个方法和属性,这种动态获取的信息以及动态调用对象方法的功能称为反射

 

2.Class类

Class类是一切的反射根源(所有类的共性,把类的消息抽象为一个对象)

Class类表示什么?

很多的人---可以定义为一个Person类(有年龄,性别,姓名等)

很多的车---可以定义为一个Car类(有发动机,颜色,车轮等)

很多的类---Class类(类名,构造方法,属性,方法)

2.1得到Class类的对象有三种方式:
  1. Object类的getClass()方法

  2. 类.class

  3. 通过Class.forName方法

public class Dog{
    private String name;
    private int agel;
    private String color;
    ....
}
public class ReflectionDeom{
    public static void main(String[] args){
        /**
        获取Class对象的三种形式
*/
    public void test(){
    //1.通过对象的getClass()方法
        Dog dog = new Dog("wang",4,"白色");
        Class dClass1 = dog.getClass();//字节码对象
    
    //2.通过类.class  (没有创建对象时使用)
    Class dClass2 = Dog.class;
    
    //3.通过Class.forName方法,Class里的静态方法
    try{    
    Class dClass3 = Class.forName("comlyh1024.test.Dog")//Dog类的路径
Thread.sleep(500);
             }catch (ClassNotFoundException e){
                 e.printStackTrace();
            }
}
    }
}
2.2使用Class类进行对象的实例化操作

反射给开发者提供了可根据类信息动态生成对象并执行对象行为的一系列操作,这个特性让编写更加灵活的底层框架时尤为重要

使用反射机制,我们不需要在代码中指明我们想要创建什么类型的实例,而是让代码自动地去获取我们想要创建的实例的它的类的信息再自动去创建

在反射包中:

(1)我们常用的类主要有Constructor类,Constructor类可以表示Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象

(2) Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)

(3) Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private),

/**
通过反射来实例化对象
*/
private static void out(){
    Class<Dog> dogClass = Dog.class;
    try{
        //通过Class对象实例化对象,调用了默认无参构造方法,然后就能调用这个对象了
        Dog dog = (Dog)dogClass.newInstance();
​
        }catch (InstantiationException e){
                 e.printStackTrace();
        }catch (IllegalAccessException e){
                 e.printStackTrace();
        }
            
    @Test
    //获取所有构造方法
    public void test3(){
        Class<Dog> dogClass = Dog.class;
        Constructor<?>[] constructors = dogClass.getConstructor();
        for(int i = 0;i<constructors.length;i++){
            System.out.println(constructors[i].getName());
            System.out.println(constructors[i].getParameterCount());//构造方法参数个数
        }
        //获取一个指定的构造方法
        try{
 Constructor<Dog> constructor = dogClass.getConstructor(String.class,int.class,String.class);//创建一个有参构造方法
            //调用带参数的构造器来实例化对象 
        Dog dog = constructor.newInstance("xiaomi",5,"绿色")  
            
        }catch (NoSuchMEthodException e){
                 e.printStackTrace();
        }catch (InstantiationException e){
                 e.printStackTrace();
        }catch (InvocationTargetException e){
                e.printStackTrace();
        }
        //获取所有的属性
        public void test4(){
            Class<Dog> dogClass = Dog.class;
            //获取非私有属性
            Field[] fields = dogClass.getFields()
            //获取所有属性(包括私有属性)
            Field[] declaredFields =  dogClass.getDeclaredFields();    
            for(int i = 0 ;i<declaredFields.length;i++){
                int modifiers =declaredFields[i].getModifiers();
               // System.out.println(modifiers);//输出int型的修饰符
                System.out.println(Modifier.toString(modifiers)+" "+decalredFields[i].getType()+" "+ declaredFields[i].getName());//例如:,输出:private calss java.lang.String name
​
            }
        }

 

3,通过Class类取得类信息

取得类所在的包

public Package getPackage()//得到一个类所在的包

public String getName() //得到名字

取得一个类中的全部方法

public Method[] getMethods()

public int getModifiers()//Modifier.toString(mod);//还原修饰符

public Class<?> getReturn Type()

public Class<?>[] getParameter Types()

public Class<?>[] getException Types()

public static String toString(int mod)

取得一个类中的全部属性

public Field[] getFields()

public Field[] getDeclaredFields()

public Class<?> getType()

public int getModifiers()

public String getName()

通过反射调用给对象的属性赋值,对学习框架底层原理时很有用

public void test5(){
    Dog dog = new Dog("wang",2,"baise")
    Class<Dog> dogClass = Dog.class;
    //获取类的包名
    Package aPackage = dogClass.getPackage();
    System.out.println(aPackage.getName());
    
    //获取公共的方法,包括继承的公有方法
    Method[] methods =  dogClass.getMethods();
    for(int i = 0;i<methods.length;i++){
        System.out.println(methods[i]);
        if(methods[i].getName().equals("toString")){
           try{
               //调用toString方法
            String s = (String)methods[i].invoke(dog)
                System.out.println(s);
           }catch(IlleagalAccessException e){
               e.printStackTrace();
           }catch(InvocationTargetException e){                                 
               e.printStackTrace();
​
           }
        }
    }
    //访问私有方法,获取本类中定义的所有方法(不包括父类)
    Method[] declaredMethods = dogClass.getDeclaredMethods();
     for(int i = 0;i<declaredMethods.length;i++){
        System.out.println(declaredMethods[i]);
         if(declaredMethods[i].getName().equals("set")){
             //setAccessible设置私有方法可访问(去除访问修饰符的检查)
             declaredMethods[i].setAccessible(true);
           try{
               
             declaredMethods[i].invoke(dog)
               
           }catch(IlleagalAccessException e){
               e.printStackTrace();
           }catch(InvocationTargetException e){                                 
               e.printStackTrace();
​
           }
         }
    }
}

 

4.通过Class类调用属性或方法

调用类中的方法

调用类中的方法,传入实例化对象,以及具体的参数内容

public Object invoke (Object obj,Object...args)

直接调用属性

public Object get(Object obj)//取得属性

public void set(Object obj,Object value)//设置属性,等同于使用“=”完成操作

public void setAccessible(boolean flag)//让属性对外部可见

 

5.类加载器原理分析

5.1类的加载过程

JVM将类加载过程分为三个步骤:装载(Load),链接(Link)和初始化(Initiailiaze)链接又分为三个步骤,如图所示

1)装载:查找并加载类的二进制数据(将二进制数据读取到内存)

2)链接:

验证:确保被加载类的正确性;

准备:为类的静态变量分配内存,并将其初始化为默认值;

解析:把类中的符号引用转换为直接引用;(符号就是变量)

3)初始化:为类的静态变量赋予正确的初始值

5.2类的初始化,类什么时候才被初始化:

1)创建类的实例,也就是new一个对象

2)访问某个类或接口的静态变量,或者对该静态变量赋值

3)调用类的静态方法

4)反射(Class.forName("com.test.dog")

5)初始化一个类的子类(会首先初始化子类的父类)

6)JVM启动时标明的启动类(main方法,主方法),即文件名和类名相同的那个类

5.3类的加载

指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在

堆区创建一个这个类的java.lang.Class对象(数据以Class对象存在),用来封装类在方法区类的对象。(实例化对象时,在方法区把Class对象拿出来,通过Class对象创建对象)

理解类加载机制有助于排查程序出现的类加载失败等技术问题,对理解java虚拟机的连接模型和java语言的动态性都有很大帮助

 

6.JavaBean的概念

6.1什么是JavaBean?

Bean理解为组件意思,JavaBean就是Java组件,在广泛的理解就是一个类,对于组件来说,关键在于要具有“能够被IDE构建工具侦测其属性和事件”的能力(符合它的标准),通常在Java中。

6.2一个JavaBean要具备这样的命名规则:
  1. 对于一个名称为xxx的属性,通常你要写两个方法:getXxx()和setXxx()。任何浏览这些方法的工具,都会把get或set后面的第一个字母自动转换为小写

  2. 对于布尔型属性,可以使用以上get和set的方式,不过也可以把get替换成is

  3. Bean的普通方法不必遵循以上的命名规则,不过他们必须是public的

  4. 对于事件,要使用Swing中处理监听器的方式。如addWindowListener,removeWindowListener

BeanUtils工具类:http://apache.org/ commons里下载.zip ,里面有jar包

(还要搭配org.apache.commons.logging.LogFactory 的jar包使用)

Class Emp{
....
}
public class BeanTest{
    public void test(){
        //从客户端获取到的数据是这样的
        String name = "bin";
        String age = "19";
        String salary = "19999";
        
        Emp emp = new Emp();
        
        try{
            
            BeanUtils.setProperty(emp,"name",name);//setProperty(对象,属性,属性值)
            BeanUtils.setProperty(emp,"age",age);
            BeanUtils.setProperty(emp,"salary",salary);
            
           }catch(IlleagalAccessException e){
               e.printStackTrace();
           }catch(InvocationTargetException e){                                 
               e.printStackTrace();
​
           }
         
        }  emp.toString();
}

 

7.内省基本概念

Java内省机制是指通过标准的get/set方法(利用发射调用成员方法)对成员属性进行操作,而不是直接通过反射对属性操作。

或者说内省(Introspector)是java语言对Bean类属性、事件的一种缺省处理方法。例如类A中有属性name,那我们可以通过getName,setName来得到其值或者设置新的值。

通过getName/setName来访问name属性,这就是默认的规则

java中提供了一套API用来访问某个属性的getter/setter方法,通过这些API可以使你不需要了解这个规则,这些API存放于包java.beans中,

一般的做法是通过类Introspector的getBeanInfo方法来获取某个对象的BeanInfo信息,然后通过BeanInfo来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的getter/setter方法,然后我们就可以通过反射机制来调用这些方法。

内省API结合反射,可以更方便实现动态根据类信息创建对象并完成对象的初始化工作。

反射和内省区别:

反射针对的是任何类、任何对象、而内省只是针对 JavaBean。内省通过反射来操作 JavaBean 的属性。

内省设置属性值肯定调用 setter 方法、而反射则不一定 (Field 对象) 反射就像是镜子、能看到对象的所有、是客观事实。内省更像是主观判断、比如看到 getClass()、内省就会认为这个类中有 class 字段、但事实上则不一定。

 

8.Introspector相关API

1.Introspector类:

Introspector类为通过工具学习有关目标Java Bean支持的属性、事件和方法的知识提供了一个标准方法

static BeanInfo getBeanInfo(Class<?> beanClass)

在Java Bean上进行内省,了解其所有属性、公开的方法和事件

2.BeanInfo类

该类实现此BeanInfo接口并提供有关其bean的方法、实现、事件等显示信息

MethodDescriptor[] getMethodDescriptors()//获得beans MethodDescriptor

PropertyDescriptor[] getPropertyDescriptors()//获得beans PropertyDescriptor

Properties 属性文件工具类的使用

3.PropertyDescriptor类

PropertyDescriptor描述Java Bean通过一对存储器方法导出的一个属性

Method getReadMethod()//获得应该用于读取属性值的方法

Method getWriteMethod()//获得应该用于写入属性值的方法

4.MethodDescriptor类:

MethodDescriptor描述了一种特殊方法,即Java Bean支持从其他组件对其进行外部访问

Method getMethod()//获得此MethodDescriptor封装的方法

public Class Config{
    private String username;
    private String password;
    private String url;
    ...
​
}
//配置文件config.properties
#对象的值,对象的文件
bean.name=com.test.introspector.Config
bean.username=admin
bean.password=123
bean.url=http://www.baidu.com
#在配置文件里创建值,把值放进对象,叫组装对象或装配
/**
通过内省的API来装配一个Bean对象,Bean对象的值是通过配置文件中来获取
目的是为了提高维护性。(只需要修改配置文件的值,而不用在程序中创建常量,再修改)
*/
public Class BeanFactory{
     private static Properties prop = new Properties();
    //通过静态代码块读取配置文件
    static{
        InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("com/test/introspector/config.properties");
        try{
             prop.load(in);
​
        }catch(IOException e){
            e.printStackTrace();
        }
    }
    
    //获取一个Bean(java类)
    public static Object getBean(String name){//给定参数Bean的类名,返回一个Bean对象
        Object bean = null;
        String beanName = prop.getProperty(name);
         try{
            Class<?> aClass = Class.forName(beanName);//?代表任何类型
             bean = aClass.newInstance();
            //通过类信息获取javaBean的描述信息
             BeanInfo beanInfo = Introspector.getBeanInfo(aClass);
             //通过javaBean描述信息,获取该类的所有属性描述器
             PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescrptors();
             //通过循环取出描述器
              for(int i = 0;i<propertyDescriptors.length;i++){
                  String propertyName = propertyDescriptors[i].getName();
                  Method writeMethod = propertyDescriptors[i].getWriteMethod();
                  if("username".equals(propertyName)){
                        //调用属性的set方法
                    writeMethod.invoke(bean,prop.getProperty("bean.username"));
                  }else if("password".equals(propertyName)){
                    writeMethod.invoke(bean,prop.getProperty("bean.password"));
                  }else if("url".equals(propertyName)){
                    writeMethod.invoke(bean,prop.getProperty("bean.url"));
                  }
              }
        }catch(ClassNotFoundException e){
            e.printStackTrace();
        }catch(IntrospectionException e){
            e.printStackTrace();
        }catch (InstantiationException e){
                 e.printStackTrace();
        }catch (IllegalAccessException e){
                 e.printStackTrace();
        }catch (InvocationTargetException e){
                 e.printStackTrace();
        }
        return bean;
    }
    
}

 

public class BeanTest{
    @Test
    public void getBeanTest(){
        Config bean = (Config)BeanFactory.getBean("bean.name");
        System.out.println(bean);   
    }
}

 

9.理解可配置的AOP框架

9.1 AOP的概念:

Aspect Oriented Programming(面向切面编程)

AOP编程思想,是把非核心业务的逻辑抽象出来,定义成切面,可以方便的动态绑定到指定的核心业务中。也可以说是把不属于核心的代码提取出来,需要的时候通过代理把它放进去,不需要的时候把它挪走。

9.2可配置AOP框架实现

AOP使用场景

AOP用来封装横切关注点,具体可以在下面的场景中使用:

按横切的位置分有:前置通知,后置通知,环绕通知。

  • 权限

  • 缓存

  • 错误处理

  • 调试

  • 记录跟踪

  • 持久化

  • 同步

  • 事务

  • 等等

简单的AOP框架实现:(包含代理模式,工厂模式,反射,内省)

图解:

public interface IManager{
    public void add(String item);
}
​
public class IManagerImpl implements IManager{
    private ArrayList<String> list = new ArrayList<>();
    
    public void add(String item){//添加操作前后添加记录日志的需求,或有没有权限添加
        //System.out.println("add start "+ System.currentTimeMillis());//这是不核心的业务1
        list.add(item);//核心代码
        System.out.println(item);
       // System.out.println("add end "+ System.currentTimeMillis());//这是不核心的业务2
​
    }
}
/**通知
*/
public interface Advice{
    public void beforeAdvice();
    public void afterAdvice();
}
/**
切面的实现类
*/
public class LogAdvice implements Advice{//记录日志
    public void beforeAdvice(){
        System.out.println("add start "+ System.currentTimeMillis());
    }
    public void afterAdvice(){
        System.out.println("add end "+ System.currentTimeMillis());
​
    }
}
#bean.properties
bean.target=com.test.aop.IManagerImpl
bean.advice=com.test.aop.LogAdvice
bean=com.test.aop.ProxyFactory
public class ProxyFactoryBean implements InvocationHandler{
    //通过代理把目标对象和切面联系在一起
    private Object target;//目标对象
    private Advice advice;//切面
    public object getProxy(){
    object proxy = Proxy.newProxyInstanc(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    return proxy;
    }
    
    public Object invoke(Object proxy ,Method method ,Object[] args)throws Throwable{
    advice.beforeAdvice();
    Object obj = method.invoke(target,args);
    advice.beforeAdvice();
​
    return obj;
    }
    ....(getter and setter)
​
}
public class BeanFactory{
    Properties prop = new Properties();
    public BeanFactory(InputStream in){
        try{
            porp.load(in);
        }catch(IOException e){
            e.printStackTrace();
        }
    }
    //读取配置文件
    //获取一个Bean实例
    public Object getBean(String name){
        String className = prop.getProperty(name);
        Object bean = null;
        try{
            //获取ProxyFactoryBean的Class对象
            Class<?> aClass = Class.forName(className);
            bean = aClass.newInstance(); //实例化对象
            //根据配置文件实例化target和advice对象
            Object target = Class.forName(prop.getProperty(name+".target")).newInstance();
            Object advice = Class.forName(prop.getProperty(name+".advice")).newInstance();
            //通过内省实现对ProxyFactoryBean的属性赋值
            BeanInfo beanInfo = Introspector.getBeanInfo(aClass);
            PropertyDescriptor[] propertyDescriptors beanInfo.getPropertyDescriptors();
            for(PropertyDescriptor pd : propertyDescriptors){
                String propertyName = pd.getName();
                Method writeMethod = pd .getWriteMethod();
                if("target".equals(propertyName)){
                    writeMethod.invoke(bean,target);
                }else if("advice".equals(propertyName)){
                    writeMethod.invoke(bean,advice);
                }
            }
        }catch(ClassNotFoundException e){
            e.printStackTrace();
        }catch(IntrospectionException e){
            e.printStackTrace();
        }catch (InstantiationException e){
                 e.printStackTrace();
        }catch (IntrospectionException e){
                 e.printStackTrace();
        }catch (InvocationException e){
                 e.printStackTrace();
        }
        return bean;
    }
}
public class AOPTest{
    @Test
    public void test(){
    //1.读取配置文件
    inputStream in = Thread.currentThread().getContextClassLoader()
                            .getResourceAsStream("com/test/aop/bean.properties")
    //2.创建Bean的工厂对象
    BeanFactory beanFactory = new BeanFactory(in)
    //3.获取代理对象
        ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)beanFactory.getBean("bean");
     IManager proxy = proxyFactoryBean.getProxy();
        proxy.add("银河落九天");
    }
​
}

 

10.单例模式优化

1.使用同步保证线程安全synchronized

2.使用volatile关键字

volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后端程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。

也就是使用volatile会从内存地址读取数据,而不会从寄存器读取数据

3.防止反射调用私有构造方法

4.让单例类序列化安全

/**
单例模式
1.多线程访问的安全问题(加同步代码块)
2.加上volatile关键字保证变量的一致性
3.防止反射调用私有构造方法(构造器里加判断)
4.让单例类可以被序列化(加Serializable接口)
*/
public class Singleton implement Serializable{
    private volatile static Singleton singleton = null;
    private Singleton(){
        if(singleton!=null){
            throw new RuntimException("此类为单例模式,已经被实例化了...");
        }
    }
    
    public static Singleton getInstance(){
        if(singleton == null){//防止new过还同步
              synchronized(Singleton.class){
                if(singleton == null ){
                singleton = new Singleton();
                }
            }
        }
        
    return singleton;
    }
}

 

■免责申明
⒈ 本站是纯粹个人学习网站,与朋友交流共赏,不存在任何商业目的。
⒉ 本站利用了部分网络资源,版权归原作者及网站所有,如果您对本站所载文章及作品版权的归属存有异议,请立即通知我们,我们将在第一时间予以删除,同时向你表示歉意!