反射的概念
先从人的正向思考分析,比如你看到一个物品,你马上就想到了这个物品的名字,就比如下面的例子:
反射就是正向思考的相反,给一个名字,然后你想象,这个名字的具体信息,如下
把反射概念引入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; } }
最常用的方法就是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(); } } }
补充: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(); } } }
全部的方法
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(); } } }
全部的属性
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(); } } }
调用指定方法
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(); } } }
调用指定属性
在反射机制中,可以直接通过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(); } } }
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元 } }
静态代理应用场景:当业务比较简单,实现类不是很多,需求的变化不是很频繁,但是又想增强真实对象的业务能力,但是又不想修改真实对象的内部代码。
静态代理缺陷:在一些复杂的情况,静态代理就有局限性。
接下来举一个例子说明静态代理的缺陷:
王五听说代理人李四,经常去美国给别人代购,于是想拜托李四从美国购买别的产品,但是李四的业务只是购买剃须刀,但是有钱赚,肯定不会拒绝顾客了,所以代理对象李四就需要对自己的业务进行扩展。
按照静态代理的流程,扩展业务需要创建新的接口,然后创建具体的产品类实现该接口,并且代理类需要多实现新业务的接口,还要重写接口方法,并且添加新的工厂类。
代码:
//新的产品接口 //其他产品工厂接口 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(); } }
可以发现业务扩展需要修改原有代码,而且如果业务扩展量比较大的情况,按照静态代理模式,就需要多个产品接口,真实产品类,代理类还需要根据业务扩展的类,进行多次修改,这相当麻烦,而且违反了设计模式的 ”开闭原则“ 。
违反开闭原则的带来的后果有两个:
一个是代理类扩展性变差了,想象一下,如果每次有新需求,就需要在原有代理类增加实现的接口,而且属性和方法也会变多,那么代理类就会变得越来越大,代理类的可扩展性就变差,可读性差;
一个是可维护性变差,可维护性差体现在两个方面,一个方面是编译期,比如接口的需求发生改变,之前接口的形参的数据类型需要改变,变为整型,那么实现接口的真实类也要跟着改变,而且代理类也需要改变,所谓的牵一发而动全身,接口发生改变,真实类和代理类全部报错;一个方面是运行期,代理类的一个接口报错,那么其他的接口也提供不了服务了。
这里谈谈设计模式的几个原则:
单一职责原则:一个类或者一个接口只负责唯一项职责,尽量设计出功能单一的接口;
依赖倒转原则:高层模块不应该依赖底层模块具体实现,解耦高层与低层。既面向接口编程,当实现发生变化时,只需提供新的实现类,不需修改高层模块代码;
开放-封闭原则:程序对外扩展开放,对修改关闭;换句话说,当需求发生变化时,我们可以通过添加新模块来满足新需求,而不是通过修改原有的实现代码来满足新需求;
了解静态代理的缺陷后,接下来引入动态代理。
动态代理
还是用一个例子说明动态代理的概念,接着上面剃须刀的例子,自从听说了李四可以前往美国帮别人代购,于是越来越多人来找李四代购各种产品,可是李四只对剃须刀的市场比较理解,其他产品没接触过,而且去调研其他产品市场,还需要时间,但是客户可没时间等。这时李四突然想到一句话,”专业的人做专业的事",代购这个圈子很少,李四认识很多其他代购的人,而且李四手上有优质的客户资源,李四可不可以把代购这个圈子整合在一起呢?整合在一起后,李四就成立一间代购公司。以前的静态代理就是李四自己亲自服务,现在的动态代理就是李四的公司根据不同客户的需求,让不同的员工去服务客户。
动态代理的实现方式和静态代理实现的几个点是一样的,动态代理也需要实现接口,还有包含真实类。不同的地方在于动态代理的扩展性非常强,可以同时代理多个真实类,可以实现多个接口。
动态代理类图
动态代理其实就是一个代理工厂,根据用户需求,自动生成对应的代理类,这个代理类去实现接口和包含真实类。
所以动态代理(代理工厂)完全符合单一原则和开闭原则。
动态代理内部情况
动态代理究竟是怎么样实现扩展性强的呢?接下来用例子解析动态代理内部情况。
李四的代购公司就是一个动态代理例子,李四根据不同客户的需求(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); } }
总结:动态代理的单一原则和开闭原则主要是依赖InvocationHandler接口和Proxy代理类,InvocationHandler作用是制定每个不同需求的工作流程(业务增强),Proxy代理类的作用是根据不同需求生成动态代理对象。InvocationHandler和Proxy是通过Proxy代理类下的newProxyInstance(类加载器,接口,当前需求类的实例对象) 进行合作的。
newProxyInstance方法根据不同的需求(传递进来的工厂),通过反射获取该需求的需要的接口和实现类动态生成一个代理类(代理类(静态代理的lisi)本来就需要实现接口和包含真实类,动态代理的动态就是通过自动生成代理类,然后代理工厂根据需求生成的代理类实例化对象),然后这个代理类根据InvocationHandler实现类制定的业务增强(工作流程),动态生成一个代理对象(不同领域的代购lisi)。
补充:静态代理和动态代理都有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的继承的类,该怎么办?推荐使用CGLib动态代理,CGLib是避免了实现接口的方式,采用了继承的形式,代理类继承真实类,然后代理类有一个方法拦截器,业务增强就在方法拦截器里面添加。大概流程:客户需求->代理类继承客户需求的真实类->代理类调用真实类->被方法拦截器拦截进行业务增强->调用增强后的方法。