反射时竟然NoSuchMethodException了!看这篇超详细的解决方案吧

Posted 马剑威老师

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了反射时竟然NoSuchMethodException了!看这篇超详细的解决方案吧相关的知识,希望对你有一定的参考价值。

前几天九哥在讲Servlet时,为了灵活地使用同一个Servlet来处理对同一张表的业务操作请求,我给学生讲解了BaseServlet工具类的封装,基本实现思路有如下几个步骤。

一. 反射封装BaseServlet工具类

使用反射封装BaseServlet工具类,无论是哪个Servlet接收到请求,都由该类完成请求分发。因此该类的主要作用就是通过反射机制,确定我们请求的到底是哪个Servlet的哪个方法。

/*
 * BaseServlet 获取客户端请求的是哪个servlet的哪个方法
 * */
public class BaseServlet extends HttpServlet 
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 
        //获取客户端发来请求的标识:即要执行的方法名
        String method = req.getParameter("method");
        //获取方法属于哪个Servlet类
        Class<? extends BaseServlet> clazz = this.getClass();
        //通过类字节码对象获取要执行方法的对象
        try 
            Method mh = clazz.getMethod(method, HttpServletRequest.class, HttpServletResponse.class);
            //执行方法
            mh.invoke(this,req,resp); // this.insert(req,resp);
         catch (Exception e) 
            e.printStackTrace();
        
    

二. 继承BaseServlet父类

以后再创建Servlet时,我们要使任意一个Servlet类,不再直接继承HttpServlet,而是要继承统一的BaseServlet,完成请求的分发管理,例如:

@WebServlet("/stuinfo")
public class StuinfoServlet extends BaseServlet 
    //删除方法
    public void delById(HttpServletRequest req, HttpServletResponse resp) 

    

    //修改学员信息
    public void update(HttpServletRequest req, HttpServletResponse resp)

    

    //根据学号查询方法
    public void findById(HttpServletRequest req, HttpServletResponse resp)  
    

    //查询全部方法
    public void findAll(HttpServletRequest req, HttpServletResponse resp)  
    

    //添加方法
    public void insert(HttpServletRequest req, HttpServletResponse resp)  
    

三. 异常展现

然而有个别同学在按照上述思路自己编写代码时,却遇到了下面的NoSuchMethodException异常。他排查许久未果,于是就来找九哥帮他解决。

四. 异常原因

起初,九哥以为是学生从客户端发出请求时,未携带执行方法的标识或携带的方法标识与实际方法名不匹配,从而导致通过反射机制获取方法对象时报错。因为我们知道,在通过Methodmh=clazz.getMethod(method,HttpServletRequest.class,HttpServletResponse.class)获取Method对象时,必须保证方法名、参数匹配,才能找到指定的方法,否则就会出现此类异常。

但经过排查,发现并不是以上原因,该学生的代码如下:

@WebServlet("/stuinfo")
public class StuinfoServlet extends BaseServlet 
    //创建serivce层对象
    private StuinfoService ss = new StuinfoSerivceImpl();
    
    //查询方法
    private void findAll(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 
        //调用service层查询方法
        List<Stuinfo> list = ss.findAll();
        //将list集合数据保存到域对象中
        req.setAttribute("stuList",list);
        //跳转到主页面
        req.getRequestDispatcher("index.jsp").forward(req,resp);
    

五. 异常解决

可能你也一眼就看到了,上述代码中,查询方法使用的是private修饰符,而私有成员在该类的外部是不能被访问的!因此我们在利用反射,通过getMethod()方法获取Method对象时就会出现NoSuchMethodException异常。所以现在的解决办法,你是不是立刻就明朗了,我们直接将private改成public就可以了

六. 暴力反射

上面的问题是解决了,但大家还要知道,反射机制中还有一种叫暴力反射,听起来是不是很厉害!!利用暴力反射,即使被private修饰也可以进行正常的操作。

九哥在这里给大家再补上一刀,反射里的Constructor、Field、Method三个类都有getDeclaredXxx方法(这里的Xxx表示Constructor、Field、Method),该方法可以不受权限控制,就能够获取到类中的这些成员信息。如果我们想要使用私有的构造函数、字段、方法,则会自动访问类的isAccessable,其默认值是false,表示在访问成员时需要安全检查,如果发现是私有的则不允许访问。所以,如果我们想要访问类中的私有成员时,需要调用setAccessible(boolean flag)方法,将其改为true。这样,我们就可以对类中的私有成员进行操作了。

 *威哥Java学习交流Q群:691533824
加群备注:CSDN推荐
      

吃惊,反射竟然是

同学们,上课了,小黄老师今天来教教大家什么是反射,男孩子必须学,女娃子也不用害羞好好学,学会了还可以教教咱班的男孩纸

反射射射,首先万物皆对象,咳咳,单身的男同胞们,肯定知道类是用来描述对象的,那么内个类的类是什么呢?

是Class,咳咳,老师这几天熬夜帮大家改作业的,嗓子不舒服,接下来,让班长小山来给大家念一下ppt

班长小山:大家看手机,ppt文件都发到群里了,自习。。。


1、什么是反射:

问题1:

1、对象有编译类型和运行类型

Object obj = new java.util.Date();

  • 编译类型:是声明时的类型,Object 类型
  • 运行类型:是new 的类型,实例化类型 java.util.Date

需求:通过obj对象,调用java.util.Date类中的toLocaleString方法。

直接 obj.toLocaleString(); 此时编译报错,编译时会检查编译类型中是否存在toLocaleString方法。

  • 解决方案:因为咱知道obj的真实类型是java.util.Date类,可以进行类型的强制转化

    java.util.Date d = (java.util.Date)obj; d.toLocaleString();//此时编译成功,成功调用方法

    但是,若是不知道obj的真实类型,就不能强转(底层有一个方法-返回类型是Object类型的java.util.Date对象)

  • 底层代码:
    public static Object getObj()
    ​     return new java.util.Date();

//使用反射:
Object obj = new java.util.Date();
Method m = obj.getClass().getMethod("toLocaleString");
m.invoke();


还有问题呀。。。。不好意思,人家问题有点多麻麻。。。


问题2:

  • 面向对象思维:万物皆对象,那么:类这种事物是什么对象呢?又需要用什么类来描述这种对象呢?

当类一旦被加载进内存,就会变成Class对象【字节码对象】

  • 类-用来描述对象,Class-用来描述类这种对象的类
  • 好比是元数据:描述数据的描述数据。

------- 反射:就是得到类(这种对象的)类【java.lang.Class】,得到类的元数据的过程。

  • 细致描述:在运行时期,动态地区获取某个类中的成员的信息(构造器、方法、字段、内部类、接口、父类等等)。

■ 还是根据万物皆对象的思维,我们把类中的每一种成员,都描述成一个新的类:

  • Class:表示所有的类
  • Constructor:表示所有的构造器
  • Method:表示所有的方法
  • Field:表示所有的字段

☀ 太棒了,终于将心里话说出来啦,反射就是---得到类的类



2、Class 类和 Class实例:

2-1、Class 类和 Class实例概念:

(1)Class类:用来描述类或接口的类型,描述类的类

(2)Class类的实例:在jvm中的一份份字节码,Class 实例表示在jvm中的类或者接口,枚举是一种特殊的类,注解是一种特殊的接口。

  • Class 对象:是字节码对象

□ 当程序第一次使用某个类(例如:java.util.Date类)的时候,就会把该类的字节码(编译生成的字节码),加载进行虚拟机,并创建一个Class对象【字节码对象】,此时的Class对象可以表示java.util.Date类的字节码,
但是Class 类可以表示N个类的字节码对象问题:要怎么区分Class类此时表示的是哪一个类的字节码呢?

解决方案:Class类的设计者使用了泛型 Class

  • 例如: java.lang.String 类的字节码类型是 Class<java.lang.String>, 而 java.util.Date类的字节码类型是 Class<java.util.Date>

2-2、获取Class实例的三种方式:

  • 使用最多的是第三种方式,通过Class类中的静态方法forName("类的全限定名称")获取Class对象,在框架中大量使用。
//需求:获取java.util.Date类的字节码对象
//方式1:通过class属性
Class<java.util.Date> clazz1 = java.util.Date.class;
		
//方式2:通过对象的getClass方法,getClass方法是Object类的方法
java.util.Date date = new java.util.Date();
Class<?> clazz2 = date.getClass();
		
//方式3:通过Class类中的静态方法forName(String className)
Class clazz3 = Class.forName("java.util.Date");

■ 为什么创建Class对象不直接new呢?

■ 为什么获取Class实例的三种方式的结果是相同的呢?

  • 同一个类在jvm/内存中只有一份字节码对象。



2-3、九大内置的Class实例:

■ 问题:上述的三种方式获取Class对象,对于基本类型:不能表示为对象,不能使用getClass方式,基本类型也没有类名的概念,不能使用Class.forName()方式,如何表示基本类型的字节码对象呢?---通过第一种方式

  • 所有的数据类型都有class属性 Class clazz = 数据类型.class;

  • 九大内置Class实例:jvm中预先提供好的Class实例,分别是byte、short、int、long、float、double、char、void

    ​ 表示:byte.class,short.class,int.class......void.class


2-4、数组的Class实例:

  • 数组时引用类型,其实就是对象

  • 如何表示数组的Class实例呢? 方式1:数组类型.class; 方式2: 数组对象.getClass();

  • 注意:所有的具有相同的维数、相同的元素类型的数组共享同一份字节码对象,和元素没有关系。


2-5、Class和Object的区别:

  • Class:描述所有的型,所以Class类中应该具有所有类型的相同方法
  • Object:描述所有的对象,所以Object类中应该具有所有对象的共同方法



3、操作构造器(获取类中的构造器、使用反射创建对象)

✿ 需求:通过反射来获取一个类的构造器

① 获取该类的字节码(要获取一个类的构造器,需要将该类先加载进虚拟机)

​ ② 从该字节码对象中去寻找需要获取的构造器

(1)Class 类获取构造器方法

  • Constructor 类:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器

  • 获取所有构造器: getConstructors() 【所有,public修饰的】 getDeclaredConstructors()【所有,和访问权限无关】

    /* 获取所有的构造器 */
    
    //1、获取构造器所在类的字节码对象
    Class clazz = User.class;
    //2、获取字节码对象中所有的构造器
    // getConstructors() 只能获取当前Class对象所表示类的【public修饰的构造器】
    Constructor[] cs = clazz.getConstructors();
    //getDeclaredConstructors() 获取当前Class所表示的所有的构造器【和访问权限无关】
    cs = clazz.getDeclaredConstructors();
    
  • 获取指定的一个构造器

    • 公共无参构造器 getConstructor()
    • 公共有参构造器 getConstructor(Class<?>...parameterTypes) 参数parameterTypes表示构造器参数的Class类型
    • 任何类型的有参构造器 getDeclaredConstructor(Class<?>...parameterTypes) 【和访问权限无关】
    /* 获取指定的一个构造器 */
    
    //1、获取构造器所在类的字节码对象
    Class clazz = User.class;
    //2、获取字节码对象中获取指定的一个构造器
    
    //获取public User() 无参构造器
    Constructor constructor = clazz.getConstructor();
    //获取public User(String name) 参数是String 的有参构造器
    constructor = clazz.getConstructor(String.class);
    //获取private User(String name, int age) 的私有有参构造器
    constructor = clazz.getDeclaredConstructor(String.class, int.class);
    

(2)使用反射创建对象--调用Constructor的方法来创建对象

✿ 构造器最大作用:创建对象


★ 为什么需要使用反射来创建对象,不选择直接new?

① 不知道obj的真实类型
② 在框架中,提供给我们的都是字符串(例如 spring框架的xml,对xml解析,得到元素的属性值是字符串,需要通过Class.forName方法创建对象)
③ 解除硬编码、消除耦合

■ 使用反射创建对象的步骤:

1)找到构造器所在类的字节码对象

2)获取构造器对象

3)使用反射创建对象(调用构造器的方法创建对象)


✿ 调用构造器的方法创建对象:

  • 若一个类中的构造器可以被外界访问同时没有参数,那么直接使用Class类的newInstance方法创建对象
  • 要调用私有成员(例如调用私有构造器方法):需要先设置当前构造器为可以访问 Constructor对象.setAccessible(true)
//类中的构造器可以被外界访问同时没有参数,直接使用Class类的newInstance方法创建对象
Class<Person> clazz = Person.class;
Constructor<Person> con= clazz.getConstructor();
con.newInstance();
System.out.println("========================");

//反射:调用构造器的方法创建对象[无参构造器]
clazz = Person.class;
con= clazz.getConstructor(String.class);
con.newInstance("shan");
System.out.println("========================");

//反射:调用构造器的方法创建对象[无参构造器]
clazz = Person.class;
con= clazz.getDeclaredConstructor(String.class, int.class);
//设置当前的构造器【私有的】可以访问
con.setAccessible(true);
con.newInstance("shan", 10);		



4、操作方法(获取类中的方法、使用反射调用方法)

------跟操作构造器差不多啦。。。


4-1、获取类中的方法Method

■ 获取类中的所有方法:
  • public Method getMethods():获取包括自身和继承过来的所有public 方法

  • public Method getDeclaredMethods():获取自身所有方法(不包括继承、和访问权限无关

■ 获取类中指定的一个方法【方法签名:方法名+参数列表】:
  • public Method getMethod(String methodName,Class<?>...parameterTypes):获取指定名称的一个public方法(包括继承的)
  • public Method getDeclaredMethod(String methodName,Class<?>...parameterTypes):获取指定名称方法(不包括继承、和访问权限无关)
    • 参数类型parameterTypes为泛型的时候,自动提升为Object类型

4-2、使用反射调用方法(调用Method的方法来执行方法)

■ 步骤:

1)获取方法所在类的字节码对象

2)获取方法对象

3)使用反射调用方法


■ 使用反射调用一个方法:

在Method类中有方法:public Object invoke(Object obj, Object...args): 表示调用当前Method所表示的方法

  • obj 表示被调用的方法底层所属的对象【Class对象.newInstance() 当无参外界可访问时】

  • 设置可访问私有的成员 Method对象.setAccessible(true)


□ 使用反射调用静态方法:
  • 静态方法不属于任何对象,静态方法属于类本身

    此时把invoke方法的第一个参数设置为null即可 public Object invoke(null, Object...args)


□ 使用反射调用数组参数(可变参数):
  • 王道:调用方法的时候把世界参数统统作为Object数组的元素即可 Method对象.invoke(Object obj, new Object[] 所有实参 )



5、操作字段

------跟操作构造器差不多啦。。。



6、反射机制---加载资源

加载资源文件路径: db.properties

  • 注意:加载properties文件,只能使用Propertis类的load方法

■ 方式1:使用绝对路径--new FileInputStream("绝对路径") [不推荐]

Properties p = new Properties();
InputStream in = new FileInputStream("db.properties文件的绝对路径");//绝对路径
p.load(in);//加载资源

✿ 方式2:使用相对路径--相对于classpath的根路径(字节码输出路径) [推荐]

  • 此时得使用类加载器ClassLoader,类加载器默认就是从classpath根路径去寻找文件的
Properties p = new Properties();
//ClassLoader loader = CreateObjDemo.class.getClassLoader();//可以通过Class类的getClassLoader()方法获取
ClassLoader loader = Thread.currentThread().getContextClassLoader();//一般选择通过线程获取类加载器【因为可以不用写类名啦】
InputStream in = loader.getResourceAsStream("db.properties");//相对路径-相对于当前项目的输出路径
p.load(in);//加载资源

■ 方式3:使用相对路径--相对于当前加载资源文件的字节码的根路径

Properties p = new Properties();
InputStream in = CreateObjDemo.class.getResourceAsStream("db.properties");//相对路径-相对于加载当前资源文件的字节码根路径
p.load(in);//加载资源

以上是关于反射时竟然NoSuchMethodException了!看这篇超详细的解决方案吧的主要内容,如果未能解决你的问题,请参考以下文章

Java反射调用与面向对象结合使用产生的惊艳

高并发优化加锁方式时竟然死锁了!!

Go语言:运行时反射,深度解析!

光的反射和折射的区别

JAVA中的反射

Java反射