通过名字(类名)获得字节码信息,获得对象,获得方法,甚至调用方法,什么玄学
通过全路径限定获得类对象,通过类对象获得字节码信息,字节码信息里存储的属性,方法都可以通过类对象获取到,这一切的动作都发生在程序运行时,所以反射使java语言拥有了准动态的特性
案例:美团外卖
美团外卖提供接口,支付合作商家实现接口
public interface Mtwm {
void payonline();
}
合作商家支付宝
public class AliPay implements Mtwm{
@Override
public void payonline() {
System.out.println("正在使用支付宝支付");
}
}
合作商家微信
public class WechatPay implements Mtwm {
@Override
public void payonline() {
System.out.println("正在使用微信支付");
}
}
测试
public class Test02 {
public static void main(String[] args) throws Exception {
String path = "com.pb.test01.AliPay"; //通过名字掉方法
Class cla = Class.forName(path);
Object o = cla.newInstance();//通过类对象创建类
//获得类中的方法(需要知道方法名(重写应用))
Method m = cla.getMethod("payonline");
//调用方法
m.invoke(o);
}
private static void pay(Mtwm m) {
m.payonline();
}
通过反射不需要多次判断前端传进来的支付商家是哪位,直接根据名字调用对应的方法(反射),提高了代码的拓展性
获取字节码信息(类对象)的四种方式
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
//1.通过对象调getclass()方法
Person p = new Person();
Class c1 = p.getClass();
System.out.println(c1);
//2.通过类名调用class()静态方法
Class c2 = Person.class;
System.out.println(c2);
//3.(常用)调用class类提供的静态方法forname()
Class c3 = Class.forName("com.pb.test02.Person");
System.out.println(c3);
//4.(了解)利用类的加载器
//获取当前类对象,然后通过类对象调用当前类加载器
ClassLoader loder = Test.class.getClassLoader();
Class c4 = loder.loadClass("com.pb.test02.Person");
System.out.println(c4);
}
}
不管用什么方式获得类对象,都是同一个对象,因为字节码信息只加载一次
作用
构造器
getconstructor() 获得public修饰的构造器
getdeclarestructor 获得所有构造器
如果构造器带参,那么方法里也需要传相同数量的参数,通过这种方式可以获得指定构造器
对象
类对象.getconstructor().newInstance()
属性
getFields():获取运行时类和父类中被public修饰的属性
getDeclaredFields():获取运行时类中的所有属性
如果想要获取指定属性,需要向方法中传入属性名
属性的具体结构
//获取修饰符
/*int modifiers = sno.getModifiers();
System.out.println(modifiers);
System.out.println(Modifier.toString(modifiers));*/
System.out.println(Modifier.toString(sno.getModifiers()));
//获取属性的数据类型:
Class clazz = sno.getType();
System.out.println(clazz.getName());
//获取属性的名字:
String name = sno.getName();
System.out.println(name);
System.out.println("-------------------------------");
//给属性赋值:(给属性设置值,必须要有对象)
Field sco = cls.getField("score");
Object obj = cls.newInstance();
sco.set(obj,98);//给obj这个对象的score属性设置具体的值,这个值为98
System.out.println(obj);
}
}
方法
getMethods:获取运行时类的方法还有所有父类中的方法(被public修饰)
getDeclaredMethods:获取运行时类中的所有方法
获取指定方法:把方法名当作参数传进去
//获取指定的方法:
Method showInfo1 = cls.getMethod(“showInfo”);
反射给基类写查询方法
//通过反射获取类名,需要先获得类对象,当前的类对象是<T>是一个泛型,
// 但是BaseDaoImpl的子类会有具体的类型,所以创建类对象的任务交给子类
// ,在这里通过有参构造器接受泛型类对象
public class BaseDaoImpl<T> implements BaseDao<T> {
private Class<T> tClass;
private String className; //类名
public BaseDaoImpl(Class<T> tclass) {
this.tClass = tclass; //获得类对象
className = tClass.getSimpleName();
}
public BaseDaoImpl() {
}
@Override
public T selectOnePeron(int id) {
Connection conn = Connjdbc.conn();
String sql = "select * from "+className+" where id = ?";
T t = null;
try {
PreparedStatement ps = conn.prepareStatement(sql);
//设置字段对应数据
ps.setInt(1,id);
ResultSet rs = ps.executeQuery();
if(rs.next())
{
//通过反射创建对象,将结果集的内容一一赋值
t = tClass.getConstructor().newInstance(); //不能直接调get/set方法
//由于此时不知道rs结果集里存的字段名(靠字段名取数据),也不知道有多少个,所以用另一个方法获得个数和数据
//获得结果集中关于字段的属性(数量,名字,数据类型,数据)
ResultSetMetaData rsm = rs.getMetaData();
int columnCount = rsm.getColumnCount();//字段数量
for (int i = 1; i <= columnCount; i++) {
String columnName = rsm.getColumnName(i);//列名
Object value = rs.getObject(columnName);//列中对应的数据
//obj.set方法(value)——>method.invoke(obj, value);
//要知道方法名和对象才能执行方法,但泛型不能直接调,所以要拼接方法名
StringBuilder s = new StringBuilder("set");
String[] nameArr = columnName.split("_");
for (String name : nameArr) {
String s1 = String.valueOf(name.charAt(0)); //获得字符串数组中每个元素的第一个字符
//将每个元素的第一个字符替换为大写
String s2 = name.replaceFirst(s1, s1.toUpperCase());
//将变形后的每个字符串拼接起来
s.append(s2); //方法名
}
String type = rsm.getColumnClassName(i); //列在表里面的类型 映射在 程序里面的数据类型
System.out.println(type);
//知道方法名后用反射获取方法
Method method = tClass.getMethod(s.toString(), Class.forName(type));
method.invoke(t,value);
}
}
} catch (SQLException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException | ClassNotFoundException throwables) {
throwables.printStackTrace();
}
return t;
}
}
通过反射优化servlet代码
通过当前输入的路径调用相应方法,这样把不同的servlet都封装成方法放进一个继承了BaseServlet的类中,减少了代码的冗余
//1.获取当次请求的路径名
String requestURI = req.getRequestURI();
//2.通过路径名获得方法名(要求路径名与方法名一致)
String path = requestURI.substring(requestURI.lastIndexOf("/")+1);
//开始反射
//1.获取当前类对象,通过类对象和方法名获得方法
try {
Method method = this.getClass().getMethod(path); //(有参传参)
//2.调用方法(传入对象和参数)
method.invoke(this);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}