java反射是java中功能很强大的一个功能,很多的框架都使用到了反射的机制,所以学习反射是java进阶必不可少的步骤。
下面的内容就是反射中一些常见的问题和使用,能力属于初级,所以很多很深的东西根本写不了,只能带大家入门学习。
1、在学习java反射之前有必要了解一些概念:
类的初始化:类的初始化过程分为三步 系统会通过加载、连接、初始化三步来实现类的初始化
加载:将指定的class文件读取到内存中,并且为它创建一个Class对象 任何类被用时系统都会创建一个Class对象
连接: 验证 是否有正确的内部结构,并和其它类协调一致
准备 负责为类的静态成员变量分配内存,并且设置初始值
初始化:初始化成员变量等
类的加载的时机:
类的实例化
调用类的静态变量 或者为静态变量赋值
调用类的静态方法
初始化某个类的子类
使用反射原理强制创建某一类或接口对应的Class对象
java反射概念:自己理解的就是在程序运行的时候,可以动态的获取一个类中的属性和方法,而且可以调用以及修改类中方法和属性的一种机制我们成为java反射机制。
要想使用java反射机制,就必须有字节码文件。字节码文件获取的方法有三种:
1、Class.forName(“类路径”);
2、类名.class
3、对象.getClass();
2、使用反射
注意如果同时用三种方法创建的字节码对象,三个对象都是相同的
有了字节码文件就可以通过它来创建对象,通过对象就可以调用类中的方法和属性。
package com.yxc.domain;
/**
* 实体类 后面通过反射拿到这个类中的所有方法和所有属性
* 不管是私有的还是公有的,都可以获取,这就是反射强大之处。
*/
public class User {
//定义两个方法
public void print(){
System.out.println("我是公有打印方法");
}
private void print1(){
System.out.println("我是私有的打印方法");
}
}
package com.yxc.reflect;
import com.yxc.domain.User;
/**
* 第一个简单的反射类
*/
public class ReflectDemo {
public static void main(String[] args) {
//三种获取字节码文件的方法
/**
* 第一种方法,通过forName()方法获取字节码文件
*/
Class clazz1=null;
try {
clazz1=Class.forName("com.yxc.domain.User");
//通过字节码文件的newInstance可以创建对象,返回的Object类型,我们需要强转
User user = (User)clazz1.newInstance();
user.print();
//在这里私有方法是不可以直接调用的,但是等会我们可以通过反射获取所有的方法,包括私有的字段
//user.print1();
} catch (Exception e) {
e.printStackTrace();
}
/**
* 第二种,直接通过类名.class获取字节码文件;
*/
Class clazz2 = User.class;
try {
User user =(User)clazz2.newInstance();
user.print();
}catch (Exception e) {
e.printStackTrace();
}
/**
* 通过对象的getClass()方法获取字节码文件
*/
User user=new User();
Class clazz3 = user.getClass();
try {
User user1 = (User)clazz3.newInstance();
user1.print();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
//这里的对象都是相同的,对于同一个类而言,字节码类是相同的
System.out.println(clazz2==clazz3);
System.out.println(clazz1==clazz2);
}
}
上面的User类就是通过反射来执行它里面的方法和属性的,我在学的时候有一个疑问,既然都知道是User类,要调用它里面的方法和属性,干嘛不直接创建对象,通过对象调用呢?那是因为刚开始学,没有体会反射真正强大的地方,如果类中的私有属性和私有方法呢,我们在其它类中是无法调用的,这时候通过反射就可以解决问题。这些我们马上接着说。
3、下面我们通过一个案列来实现公有属性,私有属性,公有方法,公有有参数的方法,私有方法,私有有参数的方法的获取。
package com.yxc.domain;
/**
* 实体类 后面通过反射拿到这个类中的所有方法和所有属性
* 不管是私有的还是公有的,都可以获取,这就是反射强大之处。
*/
public class User {
/**
* 定义一个私有属性和公共属性
*/
private String name;
public int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void print(){
System.out.println("我是公有打印方法");
}
public void print(String name){
System.out.println("我是有参公有方法"+name);
}
private void print1(){
System.out.println("我是私有的打印方法");
}
private void print1(Integer i){
System.out.println("我是私有的有参数的打印方法,编号"+i);
}
}
package com.yxc.reflect;
import com.yxc.domain.User;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 第一个简单的反射类
*/
public class ReflectDemo {
public static void main(String[] args) {
/**
* 获取属性
* 获取公有属性和私有属性
*/
Class clazz=null;
try {
//1、首先获取字节码文件
clazz=Class.forName("com.yxc.domain.User");
//2、通过字节码文件创建对象
User user= (User)clazz.newInstance();
//3、获取公有属性的代码
Field f1 = clazz.getField("age");//获取字段名
f1.set(user,23); //设置字段新的值
int age = (int)f1.get(user); //获取字段
System.out.println(age);
//获取私有属性的代码
Field f2 = clazz.getDeclaredField("name"); //和公有属性的获取一样,只是方法不一样
f2.setAccessible(true); //这一步是核心,需要设置可访问为true
f2.set(user,"yxc"); //后面的操作和公有属性一致
String name = (String)f2.get(user);
System.out.println(name);
//获取公有无参数的方法
Method m1 = clazz.getMethod("print");
m1.invoke(user);
//获取有参数的公有方法
Method m2 = clazz.getMethod("print", String.class);//参数的类型
m2.invoke(user,"hhh");//那个对象执行这个方法,方法的参数
//获取私有方法
Method m3 = clazz.getDeclaredMethod("print1");
m3.setAccessible(true); //对于方法来说,同样需要设置可访问
m3.invoke(user);
//获取有参数的私有方法
Method m4 = clazz.getDeclaredMethod("print1", Integer.class);
m4.setAccessible(true);
m4.invoke(user,23);
} catch (Exception e) {
e.printStackTrace();
}
}
}
通过上面的简单案列,我们可以了解到反射的基本使用方法。反射的强大支持就在于此,不管你是私有还是公有,只要我想要的,没有得不到的,很霸道,很强势。
4、反射的应用
下面通过简单的几个反射的应用来巩固一下反射的知识,当然反射的应用是很广泛的,几乎所有的框架的开发都使用到了反射。
反射使用案例1:越过泛型检测
package com.yxc.reflect;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 反射的案例:越过泛型检测
* 我们都知道集合可以定义泛型,只要定义了,那么其它的类型是不可以添加进入的
* 但是学完反射以后我们就可以通过反射来实现,越过泛型检验
*/
public class ArraysCheck {
public static void main(String[] args) {
//定义一个list集合,指定类型为整型数据
ArrayList<Integer> list=new ArrayList<>();
list.add(23);
list.add(24);
list.add(6);
//此时如果试图添加字符串类型,那么肯定不能通过编译
//list.add("报错");
//下面通过反射来
//获取字节码文件
Class clazz=ArrayList.class;
try {
//获取集合中的add方法,而且将参数设置为Object类型
Method m1 = clazz.getMethod("add", Object.class);
m1.setAccessible(true); //其实这一步可以不用,因为add是公有的方法
//开始执行方法 使用list对象
m1.invoke(list,"越过泛型检验");
} catch (Exception e) {
e.printStackTrace();
}
//通过迭代器将集合中的数据输出
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
反射应用案例2:静态代理
这里又出现了一个新的专业名词,代理。如果我们自己先理解一下,什么是代理就好比是让一个人专门帮你做事,你不需要管,只要将代理权给他,那么他可以帮你完成一些任务,而你不需要关心这些,只要做好自己的事就好。 代理有很多种,常见的静态代理,jdk代理(动态代理)后面学习的第三方的cglib代理。
为什么要使用代理呢?我们都知道在一些操作的前面或者后面需要动态的加入一些功能,比如在查询数据前加一个权限校验,后面加一个日志生成的方法,当然我们可以使用继承的方式去完成这样的功能,继承势必会增加类的数量,这样会导致类的膨胀,当然也可以直接在原方法中直接添加,那么这样的代码是不健全的,不易维护的。我们都知道软件的开发需求是不断变更的,如果没改动一次就去动源码的话,那基本是不理想的。所以我们需要使用代理,让那些增强的方法专门放在一个代理类中,这样就可以在不修改源码的情况下添加新的功能。下面我们通过代码来实现一个简单的静态代理。
package com.yxc.proxy;
/**
* 对于静态代理来说,必须要有接口
*/
public interface UserDao {
/**
* 定义一个抽象方法根据id查询一个用户
* @param id
*/
public void findUser(int id);
}
package com.yxc.proxy;
/**
* 这是真正工作的类
*/
public class ReadWorkUserDaoImpl implements UserDao {
//在实际开发中这里就可以直接从数据库查询到需要的数据
@Override
public void findUser(int id) {
System.out.println("从数据库查询id为"+id+"的用户");
}
}
package com.yxc.proxy;
/**
* 这是代理类
* 对于代理类来说也需要继承接口,而且要持有接口对象,就好比证明一样,这样才可以拿到代理权
*/
public class ProxyUser implements UserDao{
//代理类必须持有接口对象,就好比代理要有代理权
private UserDao userDao;
//通过构造方法将参数初始化
public ProxyUser(UserDao userDao){
this.userDao=userDao;
}
@Override
public void findUser(int id) {
System.out.println("用户权限检验,可以进行数据库查询");
userDao.findUser(id);
System.out.println("完成操作,记录日志");
}
}
package com.yxc.proxy;
/**
* 测试静态代理
*/
public class ProxyTest {
public static void main(String[] args) {
UserDao userDao=new ReadWorkUserDaoImpl();
ProxyUser proxyUser = new ProxyUser(userDao);
proxyUser.findUser(23);
}
}
说明一下:对于静态代理必须由接口,然后真正执行的类和代理类都要实现接口,对重要的就是在代理类中必须持有接口的对象,就好比代理的人肯定要有代理权,而接口就是权力。我看网上的很多举例说代理类好比就是经纪人,帮你处理一些事情,而你只要授权给他即可,你不用关心那些事情,只要做好自己的事情,其它的包装都有经济人帮你完成,这就好比代理的任务。
静态代理的优点:显然在不改变源码的情况下可以添加新的功能,这样增强代码的可维护性
静态代理的缺点:如果有很多类需要代理,就必须持有多个接口的变量或者写很多的代理类,那样的话代理类又会膨胀,而且毫无意义,所以有没有什么方法可以根据接口对象,动态的生成这一种的代理类,那么就引入的动态代理。
有了上面的说明以后,我们可以理解动态代理只需要在静态代理上加以修饰。代码如下:
这里只贴出不同于静态代理的类,相同的类参考上面所说的静态代理代码
package com.yxc.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 这是代理类
* 对于代理类来说也需要继承接口,而且要持有接口对象,就好比证明一样,这样才可以拿到代理权
* 动态代理其实就是实现的接口不一样,我们使用内部实现好的
*/
public class ProxyUser implements InvocationHandler {
//对于动态代理也需要持有接口的对象
private UserDao userDao;
//通过构造方法将参数初始化
public ProxyUser(UserDao userDao){
this.userDao=userDao;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("用户权限检验,可以进行数据库查询");
//invoke方法的前面就是在实际执行方法中的前面添加的功能
Object invoke = method.invoke(userDao,args);
//后面就是后面新增的方法
System.out.println("完成操作,记录日志");
//最后返回的是一个代理对象
return invoke;
}
}
package com.yxc.proxy;
import java.lang.reflect.Proxy;
/**
* 测试静态代理
*/
public class ProxyTest {
public static void main(String[] args) {
//创建一个真正的实现类
ReadWorkUserDaoImpl userDaoImpl=new ReadWorkUserDaoImpl();
/**
* 这里面的参数有点多我们一个个讲解
* Proxy.newProxyInstance()方法直接创建一个代理对象,这是内部提供好的类和方法,我们直接使用
* 参数说明:
* 1、需要一个接口的类加载器ClassLoader
* 2、需要一个接口
* 3、需要一个代理对象
*/
UserDao userDao=(UserDao) Proxy.newProxyInstance(userDaoImpl.getClass().getClassLoader(),
userDaoImpl.getClass().getInterfaces(),
new ProxyUser(userDaoImpl)
);
userDao.findUser(23);
}
}
静态代理和动态代理执行的效果图如下:
最后还有一种代理是使用第三方开发的cglib,这个我们在spring框架中会有所涉及,到时使用注解开发会更加高效。我们可以在一个类中写很多的代理方法,直接通过注解表名这个方法添加哪个类中的哪个方法的哪个位置。
总结:对于反射来说,其实应用还有很多,但是对于初学者来说,后面的东西可能有点深,我自己也还没有深层次的接触过,而且在一般的java开发中,很少接触到反射这一章,都是接触一些框架的源码学习时才会慢慢的看到反射,那个时候才需要更深一步的研究学习。今天的总结就到此结束。有很多不足的地方,欢迎大家指出,一起学习。菜鸟一枚,慢慢的成长。一步一个脚印,走稳咯。