Unable to find a constructor that takes a String param or a valueOf() or fromString() method
阅读原文时间:2023年07月10日阅读:1

Unable to find a constructor that takes a String param or a valueOf() or fromString() method

最近在做服务的dubbo-rest改造,在启动服务的时候遇到这个错。

2020-02-21 14:15:51,433 [main] ERROR org.springframework.boot.SpringApplication - Application startup failed
java.lang.RuntimeException: RESTEASY003875: Unable to find a constructor that takes a String param or a valueOf() or fromString() method for javax.ws.rs.QueryParam("roleList") on java.util.List com.xxx.uic.entity.req.UserRoleReq.roleList for basetype: com.xxx.uic.entity.req.RoleReq
    at org.jboss.resteasy.core.StringParameterInjector.initialize(StringParameterInjector.java:220)
    at org.jboss.resteasy.core.StringParameterInjector.<init>(StringParameterInjector.java:64)
    at org.jboss.resteasy.core.QueryParamInjector.<init>(QueryParamInjector.java:30)
    at org.jboss.resteasy.core.InjectorFactoryImpl.createParameterExtractor(InjectorFactoryImpl.java:165)
    at org.jboss.resteasy.core.PropertyInjectorImpl.getParameterExtractor(PropertyInjectorImpl.java:118)
    at org.jboss.resteasy.core.PropertyInjectorImpl.populateMap(PropertyInjectorImpl.java:66)
    at org.jboss.resteasy.core.PropertyInjectorImpl.<init>(PropertyInjectorImpl.java:54)
    at org.jboss.resteasy.core.InjectorFactoryImpl.createPropertyInjector(InjectorFactoryImpl.java:65)
    at org.jboss.resteasy.core.FormInjector.<init>(FormInjector.java:37)
    at org.jboss.resteasy.core.InjectorFactoryImpl.createParameterExtractor(InjectorFactoryImpl.java:119)
    at org.jboss.resteasy.core.MethodInjectorImpl.<init>(MethodInjectorImpl.java:44)
    at org.jboss.resteasy.core.InjectorFactoryImpl.createMethodInjector(InjectorFactoryImpl.java:77)
    at org.jboss.resteasy.core.ResourceMethodInvoker.<init>(ResourceMethodInvoker.java:99)
    at org.jboss.resteasy.core.ResourceMethodRegistry.processMethod(ResourceMethodRegistry.java:281)
    at org.jboss.resteasy.core.ResourceMethodRegistry.register(ResourceMethodRegistry.java:252)
    at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:222)
    at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:194)
    at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:180)
    at com.alibaba.dubbo.rpc.protocol.rest.BaseRestServer.deploy(BaseRestServer.java:46)
    ....

大意就是我的@QueryParam注解下的参数没有使用String参数的构造方法,也没有对应的valueOf()和fromString(),所以这里是没法反序列化的。

    @QueryParam("roleList")
    List<RoleReq> roleList;

如果深究这里的原因,需要查看resteasy里面的部分源码。

//String参数注入器 初始化方法
protected void initialize(Class type, Type genericType, String paramName, Class paramType, String defaultValue, AccessibleObject target, Annotation[] annotations, ResteasyProviderFactory factory)
   {
      this.type = type;
      this.paramName = paramName;
      this.paramType = paramType;
      this.defaultValue = defaultValue;
      this.target = target;
      baseType = type;
      baseGenericType = genericType;
          //对集合类型进行判断
      if (type.isArray()) baseType = type.getComponentType();
      if (List.class.isAssignableFrom(type))
      {
         isCollection = true;
         collectionType = ArrayList.class;
      }
      else if (SortedSet.class.isAssignableFrom(type))
      {
         isCollection = true;
         collectionType = TreeSet.class;
      }
      else if (Set.class.isAssignableFrom(type))
      {
         isCollection = true;
         collectionType = HashSet.class;
      }
      if (isCollection)
      {
         //如果是集合类型,取集合内成员的类型
         if (genericType != null && genericType instanceof ParameterizedType)
         {
            ParameterizedType zType = (ParameterizedType) genericType;
            baseType = Types.getRawType(zType.getActualTypeArguments()[0]);
            baseGenericType = zType.getActualTypeArguments()[0];
         }
         else
         {
            baseType = String.class;
            baseGenericType = null;
         }
      }
      if (!baseType.isPrimitive())
      {
         //如果注入对象类型为非基础类型,尝试拿到参数的转换器
         paramConverter = factory.getParamConverter(baseType, baseGenericType, annotations);
         if (paramConverter != null) return;
                 //尝试获取解析器
         unmarshaller = factory.createStringParameterUnmarshaller(baseType);
         if (unmarshaller != null)
         {
            unmarshaller.setAnnotations(annotations);
            return;
         }

         for (Annotation annotation : annotations)
         {
            StringParameterUnmarshallerBinder binder = annotation.annotationType().getAnnotation(StringParameterUnmarshallerBinder.class);
            if (binder != null)
            {
               try
               {
                  unmarshaller = binder.value().newInstance();
               }
               catch (InstantiationException e)
               {
                  throw new RuntimeException(e.getCause());
               }
               catch (IllegalAccessException e)
               {
                  throw new RuntimeException(e);
               }
               factory.injectProperties(unmarshaller);
               unmarshaller.setAnnotations(annotations);
               return;
            }
         }
                 //尝试获取String的转换器
         converter = factory.getStringConverter(baseType);
         if (converter != null) return;

         if (paramType.equals(HeaderParam.class))
         {
            delegate = factory.getHeaderDelegate(baseType);
            if (delegate != null) return;
         }

         try
         {
            constructor = baseType.getConstructor(String.class);
            if (!Modifier.isPublic(constructor.getModifiers())) constructor = null;
         }
         catch (NoSuchMethodException ignored)
         {

         }
         if (constructor == null)
         {
            try
            {
               // this is for JAXB generated enums.
               Method fromValue = baseType.getDeclaredMethod("fromValue", String.class);
               if (Modifier.isPublic(fromValue.getModifiers()))
               {
                  for (Annotation ann : baseType.getAnnotations())
                  {
                     if (ann.annotationType().getName().equals("javax.xml.bind.annotation.XmlEnum"))
                     {
                        valueOf = fromValue;
                     }
                  }
               }
            }
            catch (NoSuchMethodException e)
            {
            }
           //以上转换方式都没有,尝试使用方法名匹配,使用fromString和valueOf去匹配方法
            if (valueOf == null)
            {
               Method fromString = null;

               try
               {
                  fromString = baseType.getDeclaredMethod("fromString", String.class);
                  if (Modifier.isStatic(fromString.getModifiers()) == false) fromString = null;
               }
               catch (NoSuchMethodException ignored)
               {
               }
               try
               {
                  valueOf = baseType.getDeclaredMethod("valueOf", String.class);
                  if (Modifier.isStatic(valueOf.getModifiers()) == false) valueOf = null;
               }
               catch (NoSuchMethodException ignored)
               {
               }
               // If enum use fromString if it exists: as defined in JAX-RS spec
               if (baseType.isEnum())
               {
                  if (fromString != null)
                  {
                     valueOf = fromString;
                  }
               }
               else if (valueOf == null)
               {
                  valueOf = fromString;
               }
               if (valueOf == null)
               {
                 //如果还是没有则抛出上面的异常
                  throw new
                 RuntimeException(Messages.MESSAGES.unableToFindConstructor(getParamSignature(), target, baseType.getName()));
               }
            }

         }
      }
   }

为了解决这个问题,我对RoleReq类增加了valueof(String)的方法来实现String反序列化成我需要的bean。

    public static RoleReq valueOf(String string){
        return JSONObject.parseObject(string,RoleReq.class);
    }

由于时间仓促这一块没有去仔细思考这种改法有没有问题或者有没有更好的改法,后续有时间会对本文进行更新。