zoukankan      html  css  js  c++  java
  • 15 Java的反射

    反射的概念

    先从人的正向思考分析,比如你看到一个物品,你马上就想到了这个物品的名字,就比如下面的例子:

    反射就是正向思考的相反,给一个名字,然后你想象,这个名字的具体信息,如下

     把反射概念引入Java,就比如下面的例子:

    通过类名去寻找该类的详细信息,这个过程称之为”反射“。

    反射

    Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

    Java反射机制提供的功能:

    在运行时判断任意一个对象所属的类
    在运行时构造任意一个类的对象
    在运行时判断任意一个类所具有的成员变量和方法
    在运行时调用任意一个对象的成员变量和方法
    生成动态代理

    反射机制的研究及应用

     Class类

    在Object类中定义了以下的方法,此方法将被所有子类继承:
    public final Class getClass()

    以上的方法返回值的类型是一个Class类,此类是Java反射的源头, 实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。

    反射可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。
    1. Class本身也是一个类
    2. Class 对象只能由系统建立对象
    3. 一个类在 JVM 中只会有一个Class实例
    4. 一个Class对象对应的是一个加载到JVM中的一个.class文件
    5. 每个类的实例都会记得自己是由哪个 Class 实例所生成
    6. 通过Class可以完整地得到一个类中的完整结构

    Java内存分析

     类加载过程

    补充类的生命周期:加载->链接->初始化->使用->卸载。

     类加载与ClassLoader的理解

     类初始化时机

     类加载器的作用

     类加载器分类

     加载器例子:

    /**
     * 类加载器
     * @author leak
     *
     */
    public class Test9 {
        public static void main(String[] args) {
            //获取系统类的加载器,
            ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
            System.out.println(systemClassLoader);
            
            //获取系统类加载器的父类加载器-》扩展类加载器
            ClassLoader parent = systemClassLoader.getParent();
            System.out.println(parent);
            
            //获取扩展类加载器的父类加载器-》根加载器(c/c++写的,获取不到)
            //所以类加载器层次关系:系统类加载器->扩展类加载器->根加载器
            ClassLoader parent1 = parent.getParent();
            System.out.println(parent1);//返回null,因为根加载器获取不到
            
            
            //测试当前类是哪个加载器加载的
            try {
                //自定义的类是哪个类加载器加载
                ClassLoader classLoader = Class.forName("org.reflaction.day14.Test9").getClassLoader();
                System.out.println(classLoader);
                
                
                //jdk自带的类是哪个类加载器加载
                classLoader = Class.forName("java.lang.Object").getClassLoader();
                System.out.println(classLoader);//返回null,因为根加载器获取不到
                
                //如果获取到系统类加载器可以加载的路径
                System.out.println(System.getProperty("java.class.path"));
                
                
                //扩展:双亲委派机制
                //java.lang.String比如你写了一个String类,但是这个机制会让你这个类跑不起来,该机制检测多层加载器,保证安全性,以最顶级加载器为基础
                //如果你写了一个类和其他加载器的类重名,那么按照优先顺序,使用该类,保证底层核心类不被破坏
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    View Code

    总结:类加载器分为三类,从大到小排序:系统类加载器->扩展类加载器->根加载器(引导类加载器)。这三个加载器都是继承关系,系统类加载器一般是加载我们自己写的类,扩展类加载器是加载一些额外需求的类,根加载器是加载项目基本的类(比如,String,Object,Integer等)。

    额外:双亲委派机制,该机制作用保证类加载器的安全,比如你写了一个同名String类,但是最顶级跟加载器java.lang.String类已经存在,为了保证核心类不被破坏,导致崩溃,所以自己写的String类根本不会别加载。

    Class类的常用方法

     实例化Class类对象

    例子:

    //Person类
    public class Person {
        public String name;
        int age;
        
        @Override
        public String toString() {
            // TODO Auto-generated method stub
            return "已经获取到了该类的实例对象";
        }
    }
    
    //测试类
    /**    反射
     * 四种方法创建对应类的Class实例
     * @author leak
     */
    public class Test {
        public static void main(String[] args) {
            Person p = new Person();
            // c对象包含了对象p所属的Person类的所有信息
            // 1. getClass()方法通过 实例对象p获取实例对象 的类,创建该类的实例对象返回
            Class c = p.getClass();
            System.out.println("1 :"+c);
    
            // 2. 通过类名.class创建指定类的Class实例
            Class c1 = Person.class;
            System.out.println("2 :"+c1);
    
            // 3通过Class.forName("全类名") 全类名:包名+类名,不过需要捕捉异常,可能找不到该类
            try {
                // 通过Class的静态方法forName()来获取一个类的class实例
                // 方法3是最常用获取类的实例对象的方法
                Class c2 = Class.forName("org.chen.day14.Person");
                System.out.println("3 :"+c2);
            } catch (Exception e) {
                e.printStackTrace();
            }
            
            // 调用4方法实现获取Class对应的实例对象
            Test t = new Test();
            //输出获取到的实例对象
            System.out.println("4 :"+t.classLoaderGetClass());
        }
    
        public Class classLoaderGetClass() {
            // 4通过类加载器,获取对应类的Class实例
            //根据当前类,然后获取当前类的加载器,然后根据类加载器获取需要的Class的实例对象
            ClassLoader c3 = this.getClass().getClassLoader();
            Class c4 = null;
            try {
                c4 = c3.loadClass("org.chen.day14.Person");
            } catch (Exception e) {
                e.printStackTrace();
            }
            return c4;
        }
    }
    View Code

    最常用的方法就是forName方法,只需要一个全类名就可以获取对应类的实例对象,其他方法都比较麻烦。

    Class类和Class类实例区别

    Java程序中的各个Java类属于同一类事物,描述这类事物的Java类就是Class类。

    对比提问:众多的人用一个什么类表示?众多的Java类用一个什么类表示?

    人 -> Person

    Java类 -> Class

    对比提问: Person类代表人,它的实例对象就是张三,李四这样一个个具体的人,Class类代表Java类,它的各个实例对象又分别对应什么呢?

    对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等;一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的;一个类在虚拟机中只有一份字节码;

    获得Class对象

    每个类被加载后,系统会为该类生成对应的Class对象,通过Class对象可以访问到JVM中的这个类,3种方式:

    1.使用Class类的forName(String className)静态方法,className表示全限定名;如String的全限定名:java.lang.String;

    2.调用某个类的class属性获取Class对象,如Date.class会返回Date类对应的Class对象(其实就是得到一个类的一份字节码文件);

    3.调用某个对象的getClass()方法。该方法属于Object类;Class<?> clz = new Date().getClass();

    总结:Class实例对象是一个类,属于包含关系:Class类 <- Class类的实例对象(也是一个类,比如String类),平常的类(String,Object,Person,Date)是一个类,但是对于Class类,他就是一个Class类的实例对象。

    例子:创建Class实例对象(相对Class是类)和平常类的对象,还有根据Class类的实例对象,调用指定的构造器,下面的例子是通过反射调用指定构造方法

    //Person类,接口,Student类上面的例子有
    
    //测试类
    import java.lang.reflect.Constructor;
    
    /**
     * 
     * @author leak
     *    Student类有toString方法,所以打印对象就知道是创建了Student对象,还是创建了Student类
     */
    public class Test2 {
        public static void main(String[] args) {
            // 使用反射的构造方法创建对象
            try {
                Class student = Class.forName("org.chen.day14.Student");
                System.out.println("这里的student是一个类,但是对于Class类,student是一个Class类的实例对象:"+student);
                System.out.println("----------------");
                // newInstance相当于调用无参公有构造方法创建对象
                Student students = (Student) student.newInstance();
                System.out.println("这里才是创建Student对象:"+students);
                System.out.println("--------------");
                
                //通过Class实例student获取指定构造器,这里调用了两个参数的构造器,形参要对应指定的构造方法的形参,接收.class类型参数
                //注意:getDeclaredConstructor和getConstructor区别,一个是获取所有构造,一个是公有构造
                Constructor sc = student.getDeclaredConstructor(String.class,int.class);
                
                //上面获取了私有的构造方法,所以要解除封装
                sc.setAccessible(true);//解除封装
                //解除封装才可以使用该构造创建Student对象。
                Student stu = (Student) sc.newInstance("猪头",22);
                stu.school= "第一中学";
                System.out.println(stu);
                
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    View Code

    补充:Class的实例对象和平常类(String,Date,Object)在使用层面是指同一个,但是从理论上,Class类的实例对象是一个对象,平常类(Class类的实例对象)是一个类。看不懂看上面的红字。

    反射获取类的全部结构

    Field属性、Method方法、Constructor构造器、Superclass父类、Interface接口、Annotation注解
    1.实现的全部接口
    2.所继承的父类
    3.全部的构造器
    4.全部的方法
    5.全部的Field

    6.获取注解

    注意:获取上面的6种类型,只要获取的getXX方法带有Declared在里面,则获取Class实例的所有,但是不包含父类;

    平常的getXX方法只获取public修饰的结构(上面6种),但是包含父类的public修饰的。如果被private修饰的属性/方法/类构造器,需要setAccessible(true)方法解除封装性,

    setAccessible(true)除了可以解除封装性,还可以关闭反射的访问检查,如果要提高反射效率,就关闭反射的访问检查。

    还有反射获取的属性/方法,进行赋值时,要指定为哪个对象进行赋值。

     强调:区分好类的结构关系,比如类里面有属性,类有注解,属性也有注解,如果直接通过类去获取注解,只会获取到类的注解,不会获取到类里面属性的注解。

    所以区分不清类结构之间的关系就直接获取,如果该结构(结构:上面6中类型)不存在,直接返回空指针异常。

    使用反射可以取得:

    实现的全部接口

    public Class<?>[] getInterfaces()
    确定此对象所表示的类或接口实现的接口。

    所继承的父类

    public Class<? Super T> getSuperclass()
    返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。

    全部的构造器

    public Constructor<T>[] getConstructors()
    返回此 Class 对象所表示的类的所有public构造方法。
    public Constructor<T>[] getDeclaredConstructors()
    返回此 Class 对象表示的类声明的所有构造方法。

    Constructor类中:
    取得修饰符: public int getModifiers();
    取得方法名称: public String getName();
    取得参数的类型:public Class<?>[] getParameterTypes();

    例子:

    //Person类,接口,Student类代码,上一个例子有
    
    //测试类
    import java.lang.reflect.Constructor;
    
    /**
     * 反射获取该类的实例对象的父类/接口/构造器(方法)构造器的参数,类型
     * @author leak
     */
    public class Test1 {
        public static void main(String[] args) {
            try {
                //通过全类名,调用Class.forName方法获取指定类的实例对象
                Class student = Class.forName("org.chen.day14.Student");
                System.out.println("student类:"+student);
                
                //获取该实例继承的父类
                Class superclass = student.getSuperclass();
                System.out.println("student类的父类:"+superclass);
                System.out.println();
                
                //获取该实例实现的接口
                Class[] interfaces = student.getInterfaces();
                for(Class interfac : interfaces) {
                    //打印实现的所有接口
                    System.out.println("student类的接口:"+interfac);
                }
                System.out.println();
                
                
                //获取该实例的构造器(public)
                //获取到类的公有构造方法
                Constructor[] cs = student.getConstructors();
                
                for(Constructor c : cs) {
                    //getName获取构造方法名称
                    System.out.println("student类的公有public构造方法:"+c.getName());
                    //getModifers获取修饰符
                    //返回的数字1代表public,2代表是private
                    System.out.println("student类的公有public构造方法:"+c.getName()+"的修饰符:"+c.getModifiers());
                    
                    //获取每个构造器的参数类型
                    Class[] parameterTypes = c.getParameterTypes();
                    for(Class s : parameterTypes) {
                        System.out.println("student类的公有public构造方法:"+c.getName()+", 参数类型:"+s.getName());
                    }
                }        
                System.out.println();
                
                
                //获取该实例的构造器(所有类型)
                //获取到类的所有构造方法
                Constructor[] css = student.getDeclaredConstructors();
                System.out.println("获取到类的所有类型的构造方法");
                int i = 1,j = 1;//i是第几个构造方法,j是构造方法的第几个形参
                for(Constructor c : css) {
                    System.out.println("第"+i+"个构造方法----------------------");
                    //getModifers获取构造方法的修饰符
                    System.out.println("student类的构造方法:"+c.getName()+"的修饰符:"+c.getModifiers());
                    //获取构造器的参数类型
                    //有几个参数数组的   元素就有几个
    //                default是0 , public是1 ,private是 2 ,protected是 4,static是 8 ,final是 16。
                    Class[] parameterTypes = c.getParameterTypes();
                    for(Class s : parameterTypes) {
                        System.out.println("student类的构造方法:"+c.getName()+"的第"+j+"个参数, 参数类型:"+s.getName());
                    j++;
                    }
                    i++;
                    System.out.println("------------------------");
                }
                System.out.println();
                
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    View Code

     全部的方法

    public Method[] getDeclaredMethods()
    返回此Class对象所表示的类或接口的全部方法,不包含父类
    public Method[] getMethods()
    返回此Class对象所表示的类或接口的public的方法,包含父类

    Method类中:
    public Class<?> getReturnType()取得全部的返回值
    public Class<?>[] getParameterTypes()取得全部的参数
    public int getModifiers()取得修饰符

     例子

    //Person类,接口从上上面的例子拿
    //Student类,添加了private方法,区别getMethods和getDeclaresMethods的区别
    //
    public class Student extends Person implements Move,Study{
        
        String school;
        public Student() {
            System.out.println("创建Student对象,无参构造");
        }//无参构造
        
        public Student(String school) {
            this.school = school;
            System.out.println("创建Student对象,有参构造,1个参数");
        }//有参构造
        
        //私有构造
        private Student(String name,int age) {
            this.name = name;
            this.age = age;
            System.out.println("创建Student对象,私有构造,2个参数");
        }
        
        private void privateMethod() {
            System.out.println("私有方法");
        }
        public void showInfo() {
            System.out.println("学校:"+school);
        }
        
        @Override
        public void studyInfo() {
            System.out.println("学习中文");
        }
    
        @Override
        public void moveType() {
            System.out.println("走路");
        }
        
        @Override
        public String toString() {
            // TODO Auto-generated method stub
            return "姓名:"+name+",年龄:"+age+",学校:"+school;
        }
    }
    
    //测试类
    import java.lang.reflect.Method;
    
    public class Test3 {
        public static void main(String[] args) {
            try {
                //反射获取Class的实例对象(Student类)
                Class student = Class.forName("org.chen.day14.Student");
                //获取所有公有public的方法,包括父类
    //            Method[] methods = student.getMethods();
                //获取所有的方法,包括私有,但是不包含父类
                Method[] methods = student.getDeclaredMethods();
                for(Method meth : methods) {
                    System.out.println("方法--------------------");
                    System.out.println("方法的名称:"+meth.getName());
                    System.out.println("方法的修饰符:"+ meth.getModifiers());
                    System.out.println("方法的返回值:"+meth.getReturnType());
                    
                    //获取方法的参数类型,一个方法有几个形参(数组),就返回形参的类型,返回的是数组
                    //为什么使用Class[]数组接收呢,getParameterTypes()返回的方法形参类型,不是返回类型,而是返回参数类型的类,比如java.lang.String
                    Class[] types = meth.getParameterTypes();
                    if(types != null && types.length >0) {
                        for(Class c : types) {
                            System.out.println("方法的形参类型:"+c);
                        }
                    }
                    
                    System.out.println("---------------------");
                    System.out.println();
                }
                
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    View Code

    全部的属性

    public Field[] getFields()
    返回此Class对象所表示的类或接口的public的Field。
    public Field[] getDeclaredFields()
    返回此Class对象所表示的类或接口的全部Field。

    Field方法中:
    public int getModifiers() 以整数形式返回此Field的修饰符
    public Class<?> getType() 得到Field的属性类型
    public String getName() 返回Field的名称。

    补充:获取Class实例对象的包,类.getPackage()

    例子

    //Student类,Person类,接口从上面的例子拿
    
    //测试类
    import java.lang.reflect.Field;
    
    /**
     * 通过反射返回Class实例的属性,包名,包对象
     * @author leak
     *
     */
    public class Test4 {
        public static void main(String[] args) {
            try {
                Class student = Class.forName("org.chen.day14.Student");
                //获取student类的所有公有public属性,包含父类
    //            Field[] fields = student.getFields();
                //获取student类的所有属性,包含私有,不包含父类
                Field[] fields = student.getDeclaredFields();
                for(Field f : fields) {
                    System.out.println("------------------------");
                    System.out.println("属性的名字:"+f.getName());
                    System.out.println("属性的类型:"+f.getType());
                    System.out.println("属性的修饰符:"+f.getModifiers());
                    System.out.println("------------------------");
                }
                
                //获取包对象
                Package package1 = student.getPackage();
                System.out.println(package1);
                //获取包名
                System.out.println(student.getPackageName());
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
    View Code

     调用指定方法

    1.调用指定方法
    通过反射,调用类中的方法,通过Method类完成。步骤:
    1.通过Class类的getMethod(String name,Class…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
    2.之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。

    Object invoke(Object obj, Object … args)方法
    说明:
    1.Object 对应原方法的返回值,若原方法无返回值,此时返回null
    2.若原方法若为静态方法,此时形参Object obj可为null,这里的Object对象是调用方法的对象
    3.若原方法形参列表为空,则Object[] args为null
    4.若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法解除封装,将可访问private的方法。

    //Person类,接口上面例子拿
    //Student类
    public class Student extends Person implements Move,Study{
        
        public String school;
        private String privateField;
        public Student() {
            System.out.println("创建Student对象,无参构造");
        }//无参构造
        
        public Student(String school) {
            this.school = school;
            System.out.println("创建Student对象,有参构造,1个参数");
        }//有参构造
        
        //私有构造
        private Student(String name,int age) {
            this.name = name;
            this.age = age;
            System.out.println("创建Student对象,私有构造,2个参数");
        }
        
        private void privateMethod() {
            System.out.println("私有方法");
        }
        public void showInfo() {
            System.out.println("学校:"+school);
        }
        public String getSchool() {
            return school;
        }
        @Override
        public void studyInfo() {
            System.out.println("学习中文");
        }
    
        @Override
        public void moveType() {
            System.out.println("走路");
        }
        private void test(String name) {
            System.out.println("这是私有方法private void test(String name)");
        }
        public void setInfo(String name,String school) {
            this.name = name;
            this.school = school;
            System.out.println("这个是setInfo(String name,String school)方法");
        }
        public void setInfo(int age) {
            this.age = age;
            System.out.println("这个是setInfo(int age)重载方法");
        }
        @Override
        public String toString() {
            // TODO Auto-generated method stub
            return "姓名:"+name+",年龄:"+age+",学校:"+school;
        }
    }
    
    //测试类
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Method;
    
    /**
     * 反射调用指定方法
     * @author leak
     *
     */
    public class Test5 {
        public static void main(String[] args) {
            try {
                //创建Class实例对象 student  (类)
                Class<Student> student = (Class<Student>) Class.forName("org.chen.day14.Student");
                
                //getMethod(方法名,方法的形参类型(类)...)参数类型个数根据方法的形参决定
                //getMethod只能获取公有public方法,包含父类
                Method method = student.getMethod("setInfo",String.class,String.class);
                /**
                 * 注意:下面不论是反射调用setInfo还是test方法
                 * 都是使用student1实例对象来调用的
                 */
                //通过反射创建对象,调用方法
                Constructor con = student.getConstructor();
                Student student1 =(Student) con.newInstance();
                
                //获取到方法后,需要一个对象调用它,参数1是实例化对象,参数2调用当前的方法的实际参数
                method.invoke(student1,"猪头","第一中学");
                
                //如果想调用私有方法呢?
                //getDeclaredMethod调用本类的任意方法包含私有,不包含父类
                Method method2 = student.getDeclaredMethod("test",String.class);
                //调用私有方法,需要解除封装
                method2.setAccessible(true);
                method2.invoke(student1,"XXX");
                
                //调用重载方法
                Method method3 = student.getMethod("setInfo",int.class);
                method3.invoke(student1, 33);
                
                //调用有返回值的方法
                Method method4 = student.getMethod("getSchool");
                //调用方法后,接收方法的返回值
                Object returnValue = (String)method4.invoke(student1);
                System.out.println(returnValue);
                
                
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
    View Code

    调用指定属性

    在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作。
    public Field getField(String name) 返回此Class对象表示的类或接口的指定的public的Field。
    public Field getDeclaredField(String name)返回此Class对象表示的类或接口的指定的Field。
    在Field中:
    public Object get(Object obj) 取得指定对象obj上此Field的属性内容
    public void set(Object obj,Object value) 设置指定对象obj上此Field的属性内容
    注:在类中属性都设置为private的前提下,在使用set()和get()方法时,首先要使用Field类中的setAccessible(true)方法将需要操作的属性设置为可以被外部访问,解除封装
    public void setAccessible(true)访问私有属性时,让这个属性可见。

    例子

    //student类,接口,person类上面例子找
    
    //测试类
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    
    /**
     * 通过反射调用指定属性
     * @author leak
     *
     */
    public class Test6 {
        public static void main(String[] args) {
            try {
                //通过反射,创建Class实例对象(类)
                Class<?> student = Class.forName("org.chen.day14.Student");
                //想调用指定属性,首先要创建Student实例对象去调用,因为每个对象的属性都不一致
                //通常创建对象,是调用构造方法,这里先获取构造方法
                Constructor<?> constructor = student.getConstructor();
                //然后通过构造方法,创建student的实例对象
                Student student1 =(Student) constructor.newInstance();
                
                //获取指定属性,getField(属性名字),getField方法是获取公有public的属性,包含父类
                //一个Field类就一个属性
                Field field = student.getField("school");
                Field field1 = student.getField("name");
                //set(对象,形参) 对象:给哪个对象赋值,形参:赋值的参数
                //给每个相同的对象,不同的属性,赋值
                field.set(student1, "第二中学");
                field1.set(student1, "猪头");
                //Field类的实例对象的get()方法   获取当前field属性对象的值
                String mess  =(String) field1.get(student1);//获取field1对象的值打印
                System.out.println(mess);
                //或者打印student1对象的school属性    检查field对象是否已经赋值
                System.out.println(student1.school);//这里的输出仅限public的属性
                
                //获取指定的属性,包含私有的,其他的,getDeclaredField("属性名字")方法是获取本类所有类型的属性,不包含父类
                Field field2 = student.getDeclaredField("privateField");
                field2.setAccessible(true);//解除封装
                field2.set(student1, "私有的属性");//给哪个对象设置value
                String mess2  =(String) field2.get(student1);//获取field2对象的值打印
                System.out.println(mess2);
                
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
    View Code

    获取各种结构的注解

    注解修饰符只有public类型,而且默认是public类型的,所以getXXX方法范围就比getDeclaredXXX范围大了,因为getXXX可以搜索到父类,但是getDeclared只允许本类,还可以访问private,default,protected,但是注解只允许public,所以getXX方法范围大。

    public <A extends Annotation> A getAnnotation(Class<A> annotationClass) 返回public修饰的注解,包含父类。

    public <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)返回所有修饰符的注解,不包含父类。

    注意:区分好结构之间的关系,比如类的属性的注解和类的注解不是同一个getAnnotation方法就可以获取到,想获取属性的注解,要先获取属性,再获取属性的注解,不可以直接通过类去获取属性的注解。

    例子

    //所有类型的注解
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**    统一说明下面的注解类的属性和方法的属性是不同的东西,虽然调用的方法是同一个,
     * 但是如果是方法的属性,则要通过反射先获取方法类,再通过方法类去获取方法类的属性
     *  类class,方法method,构造器constructor,接口interface,属性Field
     *  
     */
    
    
    /**
     * 测试用于类的注解
     * @author leak
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TestAnnotationClass {
        String className() default "";//默认类名null
    }
    
    /**
     * 测试用于方法的注解
     * @author leak
     *
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface TestAnnotationMethod{
        String methodName() default "";//默认方法名Null
    }
    
    //测试用于构造器的注解
    @Target(ElementType.CONSTRUCTOR)
    @Retention(RetentionPolicy.RUNTIME)
    @interface TestAnnotationConstructor{
        String constructorName();
    }
    
    //测试用于属性的注解
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface TestAnnotationField{
        String FieldName();
        String FieldType();
    }
    
    //测试用于局部变量的注解
    @Target(ElementType.LOCAL_VARIABLE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface TestAnnotationLocalV{
        String localVariable();
        String VariableType();
    }
    
    //测试用于接口的注解
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface TestAnnotationInterface{
        String InterfaceName();
    }
    
    
    //使用注解的类
    //1接口注解
    @TestAnnotationInterface(InterfaceName = "AA")
    interface AA{
        @TestAnnotationMethod(methodName = "接口的空方法")
        void kong();
    }
    //2类注解
    @Deprecated
    @TestAnnotationClass(className = "TestClass")
    public class TestClass {
        //3属性注解
        @TestAnnotationField(FieldName = "Age",FieldType = "int")
        int age;
        
        //4构造器注解
        @TestAnnotationConstructor(constructorName = "constructor")
        public TestClass() {
        
        }
        
        //5方法注解
        @TestAnnotationMethod(methodName = "Test")
        public void test(String name) {
            name = "zhutou";
            //6局部变量注解,暂时找不到获取局部变量的方法
            @TestAnnotationLocalV(localVariable = "l",VariableType = "boolean")
            boolean l;
        }
    }
    
    //测试获取注解的值的类
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.lang.reflect.Parameter;
    
    /**
     * 注意区分某些注解是属于类/接口/方法里面的,如果分不清就可能返回空指针异常
     * @author leak
     *
     */
    public class Test8 {
        public static void main(String[] args) {
            try {
                /**下面的注解对象都是是通过动态代理生成的,注解在运行时,会生成
                 * 1关于类的注解
                 */
                //反射获取Class类的实例对象(还是类)相对Class是对象
                Class<?> classAnnotation = Class.forName("org.annotaion.day11.TestClass");
                //获取类的注解对象1
                TestAnnotationClass annotation = classAnnotation.getAnnotation(TestAnnotationClass.class);
                //获取类的注解对象2
                Deprecated annotation4 = classAnnotation.getAnnotation(Deprecated.class);
                //获取类的注解对象的值
                System.out.println("类注解的注解名字:"+annotation.className()+",其他注解:"+annotation4);
                System.out.println("----------------------------");
                
                /**
                 * 1.1关于类的属性的注解
                 */
                //因为是类里面的属性,所以可以使用上面反射获取到的Class对象
                Field field = classAnnotation.getDeclaredField("age");
                //获取类里面的属性的注解
                TestAnnotationField annotationField = field.getAnnotation(TestAnnotationField.class);
                System.out.println("属性注解的属性名字:"+annotationField.FieldName()+",属性注解的属性类型"+annotationField.FieldType());
                System.out.println("-----------------------------");
                
                /**
                 * 1.2关于类的方法的注解
                 */
                //因为是类里面的方法,所以可以使用上面反射获取到的Class对象
                //获取类里面的方法对象
                Method classMethod = classAnnotation.getMethod("test",String.class);
                //获取方法的注解
                TestAnnotationMethod methodAnnotation = classMethod.getDeclaredAnnotation(TestAnnotationMethod.class);
                //获取方法注解的值,这里不包含方法属性的注解
                System.out.println(methodAnnotation.methodName());
                //获取方法的形参
                Parameter[] parameters = classMethod.getParameters();
                for(Parameter p : parameters) {
                    System.out.println(p);
                }
                //暂时找不到获取方法里面局部变量的注解的值,待解决
                System.out.println("---------------------------");
                
                /**
                 * 1.3关于类里面的构造器的注解
                 */
                //因为是类里面的构造器,所以可以使用上面反射获取到的Class对象
                //获取类里面的构造器
                Constructor<?> constructor = classAnnotation.getConstructor();
                //获取构造器里面的注解
                TestAnnotationConstructor constructorAnnotation = constructor.getDeclaredAnnotation(TestAnnotationConstructor.class);
                //获取构造器注解里面的值
                System.out.println(constructorAnnotation.constructorName());
                
                
                /**
                 * 关于接口的注解
                 */
                //反射获取Class类的实例对象(还是类)相对Class是接口对象
                Class<?> InterfaceAnnotation = Class.forName("org.annotaion.day11.AA");
                //获取接口类的注解
                TestAnnotationInterface annotation2 = InterfaceAnnotation.getAnnotation(TestAnnotationInterface.class);
                //获取接口注解对象的值
                System.out.println(annotation2.InterfaceName());
                
                //获取接口类里面的方法对象
                Method method = InterfaceAnnotation.getMethod("kong");
                //获取接口里面的方法的注解
                TestAnnotationMethod annotation3 = method.getAnnotation(TestAnnotationMethod.class);
                System.out.println(annotation3.methodName());
                
                
                System.out.println("----------------------------");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    View Code

     反射和注解的一个练习,编写一个类似SpringIOC容器

    这个练习,实现了IOC容器,和Spring里面的Bean注解和AutoWire注解的实现方法

    //三层
    //实体类
    import java.io.Serializable;
    
    /**
     * 
     * @author leak
     *     实体类
     *    序列化可以通过IO流进行网络传输
     */
    public class User implements Serializable{
        private int id;
        private String username;
        private String password;
        private int age;
        
        public User() {}
        
        public User(int id, String username, String password, int age) {
            this.id = id;
            this.username = username;
            this.password = password;
            this.age = age;
        }
    
    
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public String getUsername() {
            return username;
        }
        public void setUsername(String username) {
            this.username = username;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        @Override
        public String toString() {
            return "User [id=" + id + ", username=" + username + ", password=" + password + ", age=" + age + "]";
        }
        
    }
    
    //dao层
    import java.util.List;
    
    import org.chen.entity.User;
    
    public interface IUserDao {
        
        User findUserById(int id);
        
        List<User> findAllUsers();
        
        List<User> findUsersByUserName();
        
        void saveUser(User user);
    }
    
    //dao层实现类
    import java.util.ArrayList;
    import java.util.List;
    
    import org.chen.annotation.Bean;
    import org.chen.dao.IUserDao;
    import org.chen.entity.User;
    
    @Bean
    public class UserDaoImpl implements IUserDao{
    
        @Override
        public User findUserById(int id) {
            System.out.println("这里是dao-findUserById");
            return null;
        }
    
        @Override
        public List<User> findAllUsers() {
            System.out.println("这里是dao-findAllUsers");
            List<User> list = new ArrayList<User>();
            return list;
        }
    
        @Override
        public List<User> findUsersByUserName() {
            System.out.println("这里是dao-findUsersByUserName");
            return null;
        }
    
        @Override
        public void saveUser(User user) {
            System.out.println("service层把对象传过来,注意,只有dao层有对象,才可以调用这里的方法,因为方法的调用,要先创建对象,才可以使用");
            System.out.println("这里是dao-saveUser");
            
        }
    
    }
    
    //service层
    //接口
    public interface IUserService {
        
        void login();
        void regist();
    }
    
    //service层实现类
    import org.chen.annotation.AutoWire;
    import org.chen.annotation.Bean;
    import org.chen.dao.IUserDao;
    import org.chen.dao.impl.UserDaoImpl;
    import org.chen.entity.User;
    import org.chen.service.IUserService;
    
    @Bean
    public class UserServiceImpl implements IUserService{
        
        @AutoWire
        private IUserDao userDao;
        
        @Override
        public void login() {
            userDao.findUsersByUserName();
            System.out.println("登录业务的实现方法");
        }
    
        @Override
        public void regist() {
            System.out.println("调用userDao的方法,看看userDao属性是否已经注入对象了,只有存在对象,才可以调用方法");
            userDao.saveUser(new User(1,"猪头","123",22));
            System.out.println("注册业务的实现方法");
        }
        
    }
    
    //注解
    //bean注解
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Bean {
    
    }
    
    //AutoWire注解
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 自动装配,把对象装配到属性里面
     * @author leak
     *
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AutoWire {
    }
    
    //核心配置类,ioc容器的bean和autowire在这里实现
    import java.io.File;
    import java.io.InputStream;
    import java.lang.reflect.Field;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.Properties;
    import java.util.Set;
    
    import org.chen.annotation.AutoWire;
    import org.chen.annotation.Bean;
    
    /**
     * 配置类,包含反射创建对象,通过Bean注解的类放入beanFacotry的方法,
     * 通过AutoWire注解的类把beanFacotry的对象取出注入该属性的方法
     * @author leak 
     *         比如dao层和service层不想直接new对象来使用方法 而是直接一次创建对象后,直接拿到同一个对象
     */
    public class ApplicationContext<T> {
    
        // Class就是接口/类,Object就是类创建的对象
        private HashMap<Class, Object> beanFacotry = new HashMap<>();
        // 项目的编译后的路径,比如当前编译后的路径是/E:/workspace-sts/IOC/bin/,注意本项目的src是源文件目录,不是编译后的路径
        private String filePath;
    
        // bean就是一个个对象,dao层对象,service层对象
        // 这个方法测试,是否已经把bean放入了beanFacotry里面,根据接口/类,去获取beanFacotry里面的对象
        public T getBean(Class clazz) {// 可以拿,那么需要一个东西存
            // 根据接口/类,去拿对应的对象
            return (T) beanFacotry.get(clazz);
        }
    
        //load方法作用:实现ioc容器初始化,把Bean注解的类放入ioc容器中,AutoWire注解的属性就注入对象
        public void load() {
            // getPath仅仅返回路径的字符串,getFile返回的是文件,如果路径一样,就是后面没有?xx=xx就返回和getPath一样的结果
            // getResource()先将搜索父类加载器的资源; 如果父级是null ,则会搜索内置到虚拟机的类加载器的路径,返回一个URL对象
            // substring(1)截取第一个字符/
            // 获取当前项目的编译后的项目路径,也就是存放.class的项目路径
            filePath = ApplicationContext.class.getClassLoader().getResource("").getFile().substring(1);
    //        System.out.println(filePath);
            
            //下面就是实现ioc核心方法的两个方法,一个是bean,一个自动装配
            
            // loadOne方法已经把bean注解的类创建对象放入IOC容器中(BeanFactory)
            loadOne(new File(filePath));
            // 接下来要从ioc容器中把对象拿出来,注入到属性中
            assembly();
        }
    
        // 自动装配
        private void assembly() {
            //遍历beanFacotry容器把全部对象拿出来
            Collection<Object> objs = beanFacotry.values();
            //遍历每个对对象
            for(Object obj : objs) {
                //获取当前对象的类
                Class class1 = obj.getClass();
    //            System.out.println(class1);
                //获取该类的所有属性
                Field[] fields = class1.getDeclaredFields();
                //判断该类是否存在属性,  Collections.emptyList().toString()就是一个[]空数组而已
                if(fields.length > 0 || fields.toString() != Collections.emptyList().toString()) {
                    //如果有属性,就遍历
                    for(Field field : fields) {
                        //有可能属性是私有类型,解除封装
                        field.setAccessible(true);
                        //如果当前的类的属性有AutoWire注解
                        AutoWire autoWire = field.getDeclaredAnnotation(AutoWire.class);
                        if(autoWire !=null) {
                            try {
                                //beanFacotry.get(field.getType())的field.getType()获取的是接口/类,然后get(接口/类)获取对象
                                //field属性的set方法(调用该属性的对象,注入该对象)
                                System.out.println("正在给:"+class1.getName()+"的属性:"+field.getName()+"注入对象:"+beanFacotry.get(field.getType()));
                                field.set(obj, beanFacotry.get(field.getType()));
                            }catch(Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }//结束if判断
            }
        }
    
        // 递归遍历编译后路径,找到所有的class文件
        public void loadOne(File file) {
            // 判断是否目录,如果是目录,继续里面找文件
            if (file.isDirectory()) {
                // 把该目录底下的所有文件和目录存放到file数组
                File[] listFiles = file.listFiles();
                // 如果数组为空,直接停止方法
                if (listFiles != null && listFiles.length == 0) {
                    return;
                }
                // 否则遍历file数组,把数组里面的文件传进自己方法本身,继续循环判断
                else {
                    for (File child : listFiles) {
                        loadOne(child);
                    }
                }
            }
            // 如果是文件
            else {
                // 如果一开始就是文件,就截取掉编译后的项目路径,只保留全类名
                // 比如 该文件路径是 E:workspace-stsIOCinorgchenannoationBean.class
                // 然后只截取后面的orgchenannoationBean.class
                String pathWithClass = file.getAbsolutePath().substring(filePath.length());
    //            System.out.println(pathWithClass);
    
                // 先判断拿到的是不是class类型的文件
                if (pathWithClass.contains(".class")) {
                    // 然后把orgchenannoationBean.class的换成.因为反射创建对象需要通过全类名
    //                //这里使用双反斜杆\是一个反斜杠的意思,前面进行转义
                    // 把路径的换成. 然后把后面的.class去掉,就成了全类名,substring(截取起点,截取结束点)不包含结束点
                    // 注意:substring里面的结束是pathWithClass.indexOf("."),pathWithClass里面的内容是orgchenannoationBean.class
                    // 所以换位.后,字符数还是不变的,所以直接截取到了全类名
                    String AllclassName = pathWithClass.replace("\", ".").substring(0, pathWithClass.indexOf("."));
    //                System.out.println(AllclassName);
                    // 拿到全类名后,反射创建对象
                    try {
                        Class<?> class1 = Class.forName(AllclassName);
                        // 判断全类名获取到的Class对象是不是接口类型
                        if (!class1.isInterface()) {
                            // 优化,下面不能把所有的bean都放进beanFacotry中,必须要通过注解放入
                            // 判断该类是否存在bean注解
                            Bean bean = class1.getAnnotation(Bean.class);
                            if (bean != null) {
                                // 不是接口就可以创建对象
                                Object obj = (Object) class1.getConstructor().newInstance();
                                // 如果有接口就用多态
                                if (class1.getInterfaces().length > 0) {
                                    //装配的日志
                                     System.out.println("正在加载接口【"+ class1.getInterfaces()[0] +"】,实例对象是:" + obj.getClass().getName());
                                    beanFacotry.put(class1.getInterfaces()[0], obj);
                                } else {
                                    
                                    //装配的日志
                                    System.out.println("正在加载类【"+ class1.getName() +"】,实例对象是:" + obj.getClass().getName());
                                    // 没有接口就用本类装
                                    beanFacotry.put(class1, class1.getDeclaredConstructor().newInstance());
                                }
                            }
                        } 
                        else {
    //                        System.out.println("不能把接口放入IOC容器,都实例化不了");
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    //测试类
    /**
     * 启动测试类,作用:测试标有Bean注解的类,是否已经放进beanFactory工厂;标有AutoWire注解的属性,是否已经注入对象
     * 
     * @author leak
     *
     */
    @Bean
    public class Bootstrap {
        public static void main(String[] args) {
            //创建配置类
            ApplicationContext application = new ApplicationContext();
            //初始化配置类
            application.load();
            
            //试试根据接口/类名,去获取beanFactory里面对象,看看Bean和AutoWire注解是否生效
            IUserService bean =(IUserService) application.getBean(IUserService.class);
            //获取到了,才可以调用方法,因为非静态方法,需要通过对象才能调用
            bean.regist();
        }
    }
    View Code

     这个练习,可以把三层去掉,只保留注解类和配置类,然后把保留的部分,打包成jar包,这就是一个轻量级的框架了,哈哈。

    Java动态代理

    动态代理其实可以分为两个概念,”动态“和”代理“,首先了解设计模式中的”代理模式“。

    代理模式

    定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用。

    目的:(1)通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性;(2)通过代理对象对原有的业务增强。

    接下来用一个例子说明代理模式的作用:

     比如张三需要买一个美国进口的剃须刀,他有两种方式,一种直接跑去美国买;一种通过代购李四去买。如果是自己去美国购买,要签证,办理机票,到了美国,也有语言障碍,还需要找该产品的销售地方,这里很好的解释了代理模式的  目的(1),直接访问给系统带来了不必要复杂性。经历这么多步骤,张三肯定是选择第二种方式,通过代购李四直接购买,省事多了,而且代购李四对剃须刀的市场很了解,只需告诉李四对产品的习惯,类型。李四提供一条龙的服务,直接给你买到(购买前的业务增强),买到之后还提供售后服务(购买后面的业务增强)。这里也解释了代理模式的 目的(2),通过代理对象对原有的业务增强,你需求有多个,只要告诉代理对象需求就行,代理对象对你的需求进行增强。

    解释动态代理前,先说明静态代理,因为静态代理是有缺陷的,动态代理是对静态代理的一个完善。

    静态代理模式类图

    静态代理例子:

    //剃须刀接口
    /**
     * 
     * @author leak
     *    剃须刀的接口类
     */
    public interface ShaverFacotry {
        //销售剃须刀的方法,接收价格参数
         void saleShaver(String price);
    }
    
    //生产剃须刀的具体工厂对象
    /**
     * 
     * @author leak
     *    AboradShaverFactory具体的国外剃须刀工厂
     *    真实类
     */
    public class AboradShaverFactory implements ShaverFacotry{
    
        @Override
        public void saleShaver(String price) {
            System.out.println("根据你的价格: "+price+",给你推荐普通剃须刀。");
        }
    
    }
    
    //代理类
    /**
     * 代理类ProxyLisi
     * 代理类作用:对真实类AboradShaverFactroy业务增强
     * @author leak
     *    
     */
    public class ProxyLisi implements ShaverFacotry{
        //代理人不会生产产品,所以要包含工厂,通过工厂拿产品
        //代理类需要包含真实类,产生真实对象
        public AboradShaverFactory factory;
        
        //构造方法传入真实工厂对象给代理人李四
        public ProxyLisi(AboradShaverFactory factory) {
            this.factory = factory;
        }
        
        /**
         * 代理人李四的销售方法,根据价格,然后到工厂拿货,
         * 然后对销售方法进行业务增强,提供更好服务
         */
        @Override
        public void saleShaver(String price) {
            dosomeThingBefore();//前置增强
            
            //代理人李四的购买方法:把价格给工厂,选择对应的剃须刀
            factory.saleShaver(price);
            
            dosomeThingEnd();//后置增强
        }
        
        //售前服务
        private void dosomeThingBefore() {
            System.out.println("根据你的需求,进行市场调研和产品分析");
        }
        
        //售后服务
        private void dosomeThingEnd() {
            System.out.println("你购买产品后,给你的产品进行精美包装,快递一条龙服务!");
        }
        
    }
    
    //测试类
    /**
     * 测试类
     * @author leak
     *    顾客张三通过代理对象李四,进行购买产品
     */
    public class Test {
        public static void main(String[] args) {
            //1.首先要创建工厂对象,传给李四代理对象
            AboradShaverFactory factory = new AboradShaverFactory();
            //2.创建李四代理对象,然后把工厂传给李四,李四根据工厂拿货
            ProxyLisi lisi = new ProxyLisi(factory);
            
            //3.然后有顾客张三要购买剃须刀,然后代理对象李四谈好价格后,
            //根据价格张三的价格,去工厂拿货
            lisi.saleShaver("1000");//假设张三给的价格是1000元
            
        }
    }    
    View Code

    静态代理应用场景:当业务比较简单,实现类不是很多,需求的变化不是很频繁,但是又想增强真实对象的业务能力,但是又不想修改真实对象的内部代码。

    静态代理缺陷:在一些复杂的情况,静态代理就有局限性。

    接下来举一个例子说明静态代理的缺陷:

      王五听说代理人李四,经常去美国给别人代购,于是想拜托李四从美国购买别的产品,但是李四的业务只是购买剃须刀,但是有钱赚,肯定不会拒绝顾客了,所以代理对象李四就需要对自己的业务进行扩展。

    按照静态代理的流程,扩展业务需要创建新的接口,然后创建具体的产品类实现该接口,并且代理类需要多实现新业务的接口,还要重写接口方法,并且添加新的工厂类。

    代码:

    //新的产品接口
    //其他产品工厂接口
    public interface AFactory {
        void saleProduct();
    }
    
    //真实类
    //真实类,产生具体产品
    public class AAFactory implements AFactory{
        
        @Override
        public void saleProduct() {
            System.out.println("销售AA产品。。");
        }
    
    }
    
    //原代理类的增强业务
    /**
     * 代理类ProxyLisi
     * 代理类作用:对真实类AboradShaverFactroy业务增强
     * @author leak
     *    
     */
    public class ProxyLisi implements ShaverFacotry,AFactory{
        //代理人不会生产产品,所以要包含工厂,通过工厂拿产品
        //代理类需要包含真实类,产生真实对象
        public AboradShaverFactory factory;
        
        //产品2工厂
        public AFactory facotry1;
        
        //构造方法传入真实工厂对象给代理人李四
        public ProxyLisi(AboradShaverFactory factory) {
            this.factory = factory;
        }
        
        //传进新的工厂给李四
        public ProxyLisi(AFactory facotry) {
            this.facotry1 = facotry;
        }
        
        /**
         * 代理人李四的销售方法,根据价格,然后到工厂拿货,
         * 然后对销售方法进行业务增强,提供更好服务
         */
        @Override
        public void saleShaver(String price) {
            dosomeThingBefore();//前置增强
            
            //代理人李四的购买方法:把价格给工厂,选择对应的剃须刀
            factory.saleShaver(price);
            
            dosomeThingEnd();//后置增强
        }
        
        //售前服务
        private void dosomeThingBefore() {
            System.out.println("根据你的需求,进行市场调研和产品分析");
        }
        
        //售后服务
        private void dosomeThingEnd() {
            System.out.println("你购买产品后,给你的产品进行精美包装,快递一条龙服务!");
        }
    
        //产品2的销售方法
        @Override
        public void saleProduct() {
            dosomeThingBefore();//产品2的前置增强
            facotry1.saleProduct();
            dosomeThingEnd();//产品2的后置增强
        }
        
    }
    
    //测试类
    /**
     * 测试类
     * @author leak
     *    顾客张三通过代理对象李四,进行购买产品
     */
    public class Test {
        public static void main(String[] args) {
            //1.首先要创建工厂对象,传给李四代理对象
            AboradShaverFactory factory = new AboradShaverFactory();
            //2.创建李四代理对象,然后把工厂传给李四,李四根据工厂拿货
            ProxyLisi lisi = new ProxyLisi(factory);
            
            //3.然后有顾客张三要购买剃须刀,然后代理对象李四谈好价格后,
            //根据价格张三的价格,去工厂拿货
            lisi.saleShaver("1000");//假设张三给的价格是1000元
            
            System.out.println("-----------------------");
            //新需求
            //创建不同工厂对象
            AFactory factory1 = new AAFactory();
            //传给李四
            lisi = new ProxyLisi(factory1);
            lisi.saleProduct();
        }
    }
    View Code

       可以发现业务扩展需要修改原有代码,而且如果业务扩展量比较大的情况,按照静态代理模式,就需要多个产品接口,真实产品类,代理类还需要根据业务扩展的类,进行多次修改,这相当麻烦,而且违反了设计模式的 ”开闭原则“ 。

     违反开闭原则的带来的后果有两个:

      一个是代理类扩展性变差了,想象一下,如果每次有新需求,就需要在原有代理类增加实现的接口,而且属性和方法也会变多,那么代理类就会变得越来越大,代理类的可扩展性就变差,可读性差;

      一个是可维护性变差,可维护性差体现在两个方面,一个方面是编译期,比如接口的需求发生改变,之前接口的形参的数据类型需要改变,变为整型,那么实现接口的真实类也要跟着改变,而且代理类也需要改变,所谓的牵一发而动全身,接口发生改变,真实类和代理类全部报错;一个方面是运行期,代理类的一个接口报错,那么其他的接口也提供不了服务了。

    这里谈谈设计模式的几个原则:

    单一职责原则:一个类或者一个接口只负责唯一项职责,尽量设计出功能单一的接口;

    依赖倒转原则:高层模块不应该依赖底层模块具体实现,解耦高层与低层。既面向接口编程,当实现发生变化时,只需提供新的实现类,不需修改高层模块代码;

    开放-封闭原则:程序对外扩展开放,对修改关闭;换句话说,当需求发生变化时,我们可以通过添加新模块来满足新需求,而不是通过修改原有的实现代码来满足新需求;

    了解静态代理的缺陷后,接下来引入动态代理。

    动态代理

     还是用一个例子说明动态代理的概念,接着上面剃须刀的例子,自从听说了李四可以前往美国帮别人代购,于是越来越多人来找李四代购各种产品,可是李四只对剃须刀的市场比较理解,其他产品没接触过,而且去调研其他产品市场,还需要时间,但是客户可没时间等。这时李四突然想到一句话,”专业的人做专业的事",代购这个圈子很少,李四认识很多其他代购的人,而且李四手上有优质的客户资源,李四可不可以把代购这个圈子整合在一起呢?整合在一起后,李四就成立一间代购公司。以前的静态代理就是李四自己亲自服务,现在的动态代理就是李四的公司根据不同客户的需求,让不同的员工去服务客户。

      动态代理的实现方式和静态代理实现的几个点是一样的,动态代理也需要实现接口,还有包含真实类。不同的地方在于动态代理的扩展性非常强,可以同时代理多个真实类,可以实现多个接口。

    动态代理类图

    动态代理其实就是一个代理工厂,根据用户需求,自动生成对应的代理类,这个代理类去实现接口和包含真实类。

    所以动态代理(代理工厂)完全符合单一原则和开闭原则。

    动态代理内部情况

     动态代理究竟是怎么样实现扩展性强的呢?接下来用例子解析动态代理内部情况。

    李四的代购公司就是一个动态代理例子,李四根据不同客户的需求(InvocationHandler),然后根据需求分发需求给对应的代购员工(Proxy)。

    而且动态代理符号单一职责原则,因为一个员工只做一种代购。

     接下来用代码演示,动态代理如何符合开闭原则和单一原则的。

    代码:

    //其他产品工厂接口
    public interface AFactory {
        void saleProduct(String employer,int size);
    }
    
    /**
     * 
     * @author leak
     *    剃须刀的接口类
     */
    public interface ShaverFacotry {
        //销售剃须刀的方法,接收价格参数
         void saleShaver(String employer,String price);
    }
    
    //真实类,产生具体产品
    public class AAFactory implements AFactory{
    
        @Override
        public void saleProduct(String employer, int size) {
            System.out.println("你好我是lisi公司的"+employer+"员工,根据的你需求大小:"+size+",推荐你购买AA产品。");
        }
    }
    
    
    /**
     * 
     * @author leak AboradShaverFactory具体的国外剃须刀工厂 真实类
     */
    public class AboradShaverFactory implements ShaverFacotry {
    
        @Override
        public void saleShaver(String employer, String price) {
            System.out.println("你好我是lisi公司的" + employer + "员工,根据的你需求价格:" + price + ",推荐你购买普通剃须刀。");
        }
    }
    
    
    //代理类
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 动态代理类
     * 
     * @author leak 代理工厂,制定员工的工作流程
     */
    public class LisiCompany implements InvocationHandler {
    
        // 包含真实类(被代理的类)
        //为了提高代码的复用,所以工厂的类型需要同一,不然要写多个工厂属性和方法
        private Object factory;
    
        public Object getFactroy() {
            return factory;
        }
    
        public void setFactory(Object factory) {
            this.factory = factory;
        }
    
        // 通过Proxy获取动态代理的对象
        //newProxyInstance(类加载器,接口,LisiCompany实例对象)
        //动态代理之所以能扩展性强,可维护性好,是因为代理类Proxy的newProxyInstance方法封装了实现接口的类,这个类根据不同需求自动生成
        //Proxy代理类是负责实现接口,生成对应的类;InvocationHandler是负责生成的类执行的工作流程
        //Proxy和InvocationHandler是通过newProxyInstance方法进行合作的,newProxyInstance前面的两个参数是通过传递的工厂反射获取到对应的方法
        //最后的this参数是指LisiCompany实例后的对象,这个对象可以调用当前类的所有东西(类的工作流程)
        //所以getProxyInstance方法是返回一个(根据传递的工厂生成对应的类,这个类还有工作的流程(业务增强))对象
        public Object getProxyInstance() {
            return Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(), this);
        }
    
        //通过动态代理对象对方法进行增强
        //是通过反射获取被代理类的方法
        @Override   
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            dosomeThingBefore();//前置增强
            Object result = method.invoke(factory, args);
            dosomeThingEnd();//后置增强
            return result;
        }
    
        // 售前服务
        private void dosomeThingBefore() {
            System.out.println("根据你的需求,进行市场调研和产品分析");
        }
    
        // 售后服务
        private void dosomeThingEnd() {
            System.out.println("你购买产品后,给你的产品进行精美包装,快递一条龙服务!");
        }
    }
    
    //测试类
    /**
     * 测试类
     * @author leak
     *    顾客张三通过代理对象李四,进行购买产品
     */
    public class Test {
        public static void main(String[] args) {
            //张三需要的产品
            ShaverFacotry factory1 = new AboradShaverFactory();
            //王五需要的产品
            AFactory factory2 = new AAFactory();
            
            //创建李四代理工厂
            LisiCompany lisiCompany = new LisiCompany();
            //根据张三的需求,确定所需的产品
            lisiCompany.setFactory(factory1);
            //创建一个符合张三需求的产品的代购员工
            ShaverFacotry lisi1 =(ShaverFacotry) lisiCompany.getProxyInstance();
            //代理员工根据张三的需求购买产品,lisi1.getClass().getName()获取当前的类名,可以发现不同的需求,有不同的类
            //代理工厂就是通过这些自动生成的封装类实现接口,从而保持动态代理的可扩展性和可维护性
            lisi1.saleShaver(lisi1.getClass().getName(), "120");
            System.out.println("--------------------------------");
            
            //根据王五的需求,确定所需的产品
            lisiCompany.setFactory(factory2);
            //然后创建一个符合王五需要的产品的代理员工
            AFactory lisi2 =(AFactory) lisiCompany.getProxyInstance();
            //根据王五需求购买产品
            lisi2.saleProduct(lisi2.getClass().getName(),30);
        }
    }
    View Code

    总结:动态代理的单一原则和开闭原则主要是依赖InvocationHandler接口和Proxy代理类,InvocationHandler作用是制定每个不同需求的工作流程(业务增强),Proxy代理类的作用是根据不同需求生成动态代理对象。InvocationHandler和Proxy是通过Proxy代理类下的newProxyInstance(类加载器,接口,当前需求类的实例对象)  进行合作的。

      newProxyInstance方法根据不同的需求(传递进来的工厂),通过反射获取该需求的需要的接口和实现类动态生成一个代理类(代理类(静态代理的lisi)本来就需要实现接口和包含真实类,动态代理的动态就是通过自动生成代理类,然后代理工厂根据需求生成的代理类实例化对象),然后这个代理类根据InvocationHandler实现类制定的业务增强(工作流程),动态生成一个代理对象(不同领域的代购lisi)。

    补充:静态代理和动态代理都有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的继承的类,该怎么办?推荐使用CGLib动态代理,CGLib是避免了实现接口的方式,采用了继承的形式,代理类继承真实类,然后代理类有一个方法拦截器,业务增强就在方法拦截器里面添加。大概流程:客户需求->代理类继承客户需求的真实类->代理类调用真实类->被方法拦截器拦截进行业务增强->调用增强后的方法。

  • 相关阅读:
    Bootstrap表格的使用
    [JS练习] 瀑布流照片墙
    [C#基础] 委托
    [C#基础] 泛型
    [C#基础] 继承
    [C#基础] 类
    [C#基础] 数据类型
    Unity获取手机的电量时间
    C#网络通信Socket详解
    记C#一次服务器搭建和数据库应用
  • 原文地址:https://www.cnblogs.com/unlasting/p/12775671.html
Copyright © 2011-2022 走看看