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-sts\IOC\bin\org\chen\annoation\Bean.class
            // 然后只截取后面的org\chen\annoation\Bean.class
            String pathWithClass = file.getAbsolutePath().substring(filePath.length());
//            System.out.println(pathWithClass);

            // 先判断拿到的是不是class类型的文件
            if (pathWithClass.contains(".class")) {
                // 然后把org\chen\annoation\Bean.class的\换成.因为反射创建对象需要通过全类名
//                //这里使用双反斜杆\\是一个\反斜杠的意思,前面\进行转义
                // 把路径的\换成. 然后把后面的.class去掉,就成了全类名,substring(截取起点,截取结束点)不包含结束点
                // 注意:substring里面的结束是pathWithClass.indexOf("."),pathWithClass里面的内容是org\chen\annoation\Bean.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是避免了实现接口的方式,采用了继承的形式,代理类继承真实类,然后代理类有一个方法拦截器,业务增强就在方法拦截器里面添加。大概流程:客户需求->代理类继承客户需求的真实类->代理类调用真实类->被方法拦截器拦截进行业务增强->调用增强后的方法。

 

posted @ 2020-05-04 10:56  HainChen  阅读(230)  评论(0编辑  收藏  举报