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可以完整地得到一个类中的完整结构

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

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

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

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

使用反射可以取得:

实现的全部接口

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

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是避免了实现接口的方式,采用了继承的形式,代理类继承真实类,然后代理类有一个方法拦截器,业务增强就在方法拦截器里面添加。大概流程:客户需求->代理类继承客户需求的真实类->代理类调用真实类->被方法拦截器拦截进行业务增强->调用增强后的方法。

猜你喜欢

转载自www.cnblogs.com/unlasting/p/12775671.html