在Java编程中,反射和代理是两个强大的特性,能够在运行时动态地操作和扩展类的行为。通过反射,我们可以在不知道类的具体信息的情况下操作类的属性和方法;而代理则允许我们创建一个代理类来拦截并增强目标对象的行为
像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。
但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制,掌握反射和代理机制,有利于我们了解这些框架的内核原理,提升编程思维
这些内核都是反射和代理机制在其作用
由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。这种通过Class实例获取class信息的方法称为反射(Reflection)。
我们正常得到一个对象实例是通过new方法创建对象;反射就是通过class来实现,
1.获得类class
2.获得类的字段属性和方法
3.获得类的实例对象
4.获得对方法的调用
准备工作
Food.java
public class Food {
private Integer id;
private String name;
// ....省略get set 等
}
获得类有三种方式,看代码演示
类.class
对象.getClass();
Class.forName(“类名”)
/***
* @Description: 得到Class
*/
@Test
public void t1() throws ClassNotFoundException {
Class cls=null;
//1.通过类名直接获得
cls= Food.class;
//2.通过实例获得
cls=new Food(1, "火锅").getClass();
//3.通过路径+类名的字符串获得
cls=Class.forName("com.jsoft.reflection.Food");
printClassInfo(cls);
}
/***
* @Description: 读取类的信息
*/
static void printClassInfo(Class cls) {
System.out.println("Class name: " + cls.getName());
System.out.println("Simple name: " + cls.getSimpleName());
if (cls.getPackage() != null) {
System.out.println("Package name: " + cls.getPackage().getName());
}
System.out.println("is interface: " + cls.isInterface());
System.out.println("is enum: " + cls.isEnum());
System.out.println("is array: " + cls.isArray());
System.out.println("is primitive: " + cls.isPrimitive());
}
打印效果
Class name: com.jsoft.reflection.Food
Simple name: Food
Package name: com.jsoft.reflection
is interface: false
is enum: false
is array: false
is primitive: false
演示代码
public class Father extends Man{
private Integer age;
public String job;
}
public class Man{
private Integer id;
private String name;
public String address;
}
对任意的一个Object实例,只要我们获取了它的Class,就可以获取它的一切信息。
我们先看看如何通过Class实例获取字段信息。Class类提供了以下几个方法来获取字段:
方法 | 说明 |
---|---|
Field getField(name) | 根据字段名获取某个public的field(包括父类) |
Field[] getFields() | 获取所有public的field(包括父类) |
Field getDeclaredField(name) | 根据字段名获取当前类的某个field(不包括父类) |
Field[] getDeclaredFields() | :获取当前类的所有field(不包括父类) |
注意:
Declared修饰就只管当前类字段,和private、public无关
否则就包含父类的public
测试代码
@Test
public void t2() throws NoSuchFieldException {
Class cls= Father.class;
//1.得到当前类的所有字段,不包含父类
Field[] fs=cls.getDeclaredFields();
System.out.println("1.得到当前类的所有字段,不包含父类");
for (Field f : fs) {
printFeild(f);
}
//2.得到当前类某个字段
System.out.println("2.得到当前类某个字段,不包含父类");
Field f=cls.getDeclaredField("job");
printFeild(f);
//3.得到当前类及父类的所有public字段
System.out.println("3.得到当前类及父类的所有public字段");
fs=cls.getFields();
for (Field f1 : fs) {
printFeild(f);
}
//4.得到当前类及父类的public字段
System.out.println("4.得到当前类及父类的public字段,如果是private,则要报错");
f=cls.getField("address");
printFeild(f);
}
/***
* @Description: 打印字段信息
*/
public void printFeild( Field f){
System.out.println("//------------字段信息 start");
System.out.println("字段名称:"+f.getName());
System.out.println("字段类型:"+f.getType().getName());
int m = f.getModifiers();
System.out.println("field is final:"+Modifier.isFinal(m));; // true
System.out.println("field is Public:"+Modifier.isPublic(m));; // true
System.out.println("field is Protected:"+Modifier.isProtected(m));; // true
System.out.println("field is Private:"+Modifier.isPrivate(m));; // true
System.out.println("field is Static:"+Modifier.isStatic(m));; // true
System.out.println("//------------字段信息 end");
}
我们可以通过一个对象对应的class去动态访问或修改对象的字段
Field.set(vo对象,修改值) //修改字段值
Feild.get(vo对象) //获得字段值
Feild.setAccessible(true); //私有字段,需要设置这个允许访问,否则报异常
@Test
public void t3() throws NoSuchFieldException, IllegalAccessException {
Food food=new Food(1, "火锅");
System.out.println("food.getName:"+food.getName());
Class cls=food.getClass();
Field f=cls.getDeclaredField("name");
f.setAccessible(true); //不设置这个private 字段要抛出异常
//通过field获得值
Object name= f.get(food);
System.out.println("name:"+name);
//通过field设置值
f.set(food, "西北风");
System.out.println("name:"+food.getName());
}
方法 | 说明 |
---|---|
Method getMethod(name, Class…) | 获取某个public的Method(包括父类) |
Method[] getMethods() | 获取所有public的Method(包括父类) |
Method getDeclaredMethod(name, Class…) | 获取当前类的某个Method(不包括父类) |
Method[] getDeclaredMethods() | :获取当前类的所有Method(不包括父类) |
示例代码
@Data
public class Father extends Man{
private Integer age;
public String job;
public String play(int type){
System.out.println("param value:"+type);
return type+"";
}
public String sleep(String type, Date date){
System.out.println("param value type:"+type+";date:"+date);
return type+"";
}
private void self(){
System.out.println("this is private method");
}
public void dd(){
System.out.println("没有参数方法()");
}
}
@Data
public class Man{
private Integer id;
private String name;
public String address;
@Override
public Food eat() {
return new Food(1,"火锅");
}
}
测试代码
@Test
public void t4() throws IllegalAccessException, NoSuchMethodException {
Class cls= Father.class;
System.out.println("1. 获取所有public的Method(包括父类)");
Method[] methods=cls.getMethods();
for (Method method : methods) {
printMethod(method);
}
System.out.println("2. 获取某个public的Method(包括父类)");
//第一个参数为方法名,第二参数是可变参数,传递方法的参数类型,如果无参数则不传递
Method m= cls.getMethod("getName");
printMethod(m);
//方法有1个参数
//注意int foat double这些基本类型对用的类是int.class,不是Integer.class
m=cls.getMethod("play", int.class);
printMethod(m);
//方法有多个参数
m=cls.getMethod("sleep", String.class, Date.class);
printMethod(m);
System.out.println("3. 获取当前类的的Method(不包括父类)");
m= cls.getDeclaredMethod("getJob");
printMethod(m);
//私有方法也可以
m= cls.getDeclaredMethod("self");
//父类方法不可以,要报java.lang.NoSuchMethodException
m= cls.getDeclaredMethod("eat");
printMethod(m);
printMethod(m);
}
public void printMethod(Method addMethod){
System.out.println("---------方法名称: 【" + addMethod.getName()+"】---相关属性------------");
System.out.println("修饰符: " + Modifier.toString(addMethod.getModifiers())); //public private等
System.out.println("返回值: " + addMethod.getReturnType()); //返回值类型的class数组
Class[] paramsCls= addMethod.getParameterTypes(); //参数值类型对应的class数组
}
我们可以用Method.invoke(vo,参数)来调用方法
代码
@Test
public void t5() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Father father=new Father();
Class cls= father.getClass();
Method m=null;
//无参无返回方法
m=cls.getMethod("dd");
m.invoke(father);
//有参数,有返回值
m=cls.getMethod("sleep", String.class,Date.class);
String ret=(String)m.invoke(father, "1",new Date());
System.out.println("返回值:"+ret);
//private方法调用
m=cls.getDeclaredMethod("self");
//私有方法必须设置为m.setAccessible(true);
m.setAccessible(true);
m.invoke(father);
}
jdk内置对象的方法调用的另外一种写法
@Test
public void t6() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
String s="abcde";
String sub=s.substring(0,2);
System.out.println(sub);
//通过类加载
Class cls=String.class;
Method method=cls.getMethod("substring",int.class,int.class);
sub=(String) method.invoke(s, 0,2);
System.out.println(sub);
}
Method m =Father.class.getMethod(“dd”);
m.invoke(new Father());
其实就相当于
Father f=new Father();
f.dd();
实例化对象有3中方法
1.我们都知道的new 对象Father father=new Father();
2.通过类class:newInstance
3.构造方法:Constructor
准备类
public class Const {
private Integer id;
private String name;
public Const() {
System.out.println("无参数构造方法");
}
public Const(Integer id, String name) {
System.out.println("有参数构造方法");
this.id = id;
this.name = name;
}
}
在很多框架里,我们为了足够的扩展,都通过配置来实现类的实例化
Class.newInstance()
@Test
public void t7() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class cls =Class.forName("com.jsoft.reflection.Const");
Const aConst = (Const) cls.newInstance();
}
Class.newInstance()最大的问题是只能实例化无参的构造方法,如果我们把无参构造方法屏蔽掉,代码会出错,所以我们需要使用Constructor类来实现有参的构造方法
这里是引用一共有4种方法,全部都在Class类中:
- getConstructors():获取类中的公共方法
- getConstructor(Class[] params): 获取类的特定构造方法,params参数指定构造方法的参数类型
- getDeclaredConstructors(): 获取类中所有的构造方法(public、protected、default、private)
- getDeclaredConstructor(Class[] params): 获取类的特定构造方法,params参数指定构造方法的参数类型
代码示例
@Test
public void t8() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class cls =Class.forName("com.jsoft.reflection.Const");
//得到所有构造函数
Constructor[] constructors= cls.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("修饰符: " + Modifier.toString(constructor.getModifiers()));
System.out.println("构造函数名: " + constructor.getName());
System.out.println("参数列表: " );
Class[] cs= constructor.getParameterTypes();
for (Class c : cs) {
System.out.println(c.getName());
}
}
//获得具体一个(无参)
Constructor cst=cls.getConstructor();
//newInstance实例化
Const const1=(Const) cst.newInstance();
//获得具体一个(有参)
cst=cls.getConstructor(Integer.class,String.class);//传入构造函数的参数的类型
//newInstance实例化
const1=(Const) cst.newInstance(1,"蒋增奎");
}
instanceof是判断一个实例对象是不是一个类的实例,是则返回true
对象 instanceof 类
class Person{
}
class Teacher extends Person{
}
class Student extends Person{
}
测试
public class test01 {
public static void main(String[] args) {
Object obj = new Student(); // 主要看这个对象是什么类型与实例化的类名
System.out.println(obj instanceof Student); // true
System.out.println(obj instanceof Person); // true
System.out.println(obj instanceof Object); // true
System.out.println(obj instanceof String); // false
System.out.println(obj instanceof Teacher); // false 无关系
System.out.println("========================");
Person person = new Student();
System.out.println(person instanceof Person); // true
System.out.println(person instanceof Object); // true
// System.out.println(person instanceof String); // 编译错误
System.out.println(person instanceof Teacher); // false 无关系
}
}
<bean id="employee" class="com.jsoft.po.Employee">
<property name="id" value="1" />
<property name="name" value="蒋增奎" />
<property name="deptId" value="001" />
</bean>
解析思路
1.用dom4j解析xml得到xml数据
2.创建类:Class cls=Class.forName(“Employee”);
3.创建对象:Employee obj=(Employee)cls.newInstance();
4.调用修改字段或者方法给字段注入值
代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。
举个例子:
boy向girl求婚
(1)现代:boy和girl直接发生通信,girl迫不及待的同意了
(2)古代:boy不能直接和girl发生关系,需要通过媒婆,boy向媒婆发起请求,媒婆转告girl,gilr同意,媒婆觉得女孩如果同意,婚前应该收彩礼10万,婚后不能家暴,媒婆就是代理,在原始的反馈上增加了自己的要求,提升了框架的健壮性。
代理分为静态代理和动态代理两种类型
public interface Love {
/***
* @Description: 结婚请求
*/
public void marray();
}
/**
接口实现类
**/
public class LoveImpl implements Love {
@Override
public void marray() {
System.out.println("我同意");
}
}
静态代理实现步骤:
1.定义一个接口及其实现类;
2.创建一个代理类同样实现这个接口
3.将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。
代码:
//代理类要实现接口
public class LoveProxy implements Love {
private Love target;//代理的接口
/***
* @Description: 构造方法注入代理的接口
*/
public LoveProxy(Love love){
target=love;
}
@Override
public void marray(){
System.out.println("要彩礼10万");
target.marray();
System.out.println("不能家暴");
}
public static void main(String[] args) {
//实际应用:代理的应用
Love love=new LoveImpl();
LoveProxy proxy=new LoveProxy(love);
proxy.marray();
}
}
效果:在以前的需求上扩展了功能
要彩礼10万
我同意
不能家暴
静态代理:通过手动编写代理类来实现,需要为每个目标对象编写一个代理类。
动态代理:通过Java提供的相关接口和类,在运行时动态生成代理类,无需手动编写代理类。
我们仍然先定义了接口,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。
动态代理三个重要的java类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;