当前位置: 代码迷 >> 综合 >> 动态代理与Scala反射/Java反射在Java、Scala、Kotlin中的使用
  详细解决方案

动态代理与Scala反射/Java反射在Java、Scala、Kotlin中的使用

热度:113   发布时间:2023-09-23 10:10:22.0

一、Java反射

需要注意的是:本文的代理不是为了给方法添加前置或者后缀逻辑,而是直接替换方法本身实现。

被代理对象

这个也是我使用graphql-java-codegen生成的一个resolver,这里我们需要了解这些,只知道我们有个接口,其中有个方法,需要被动态代理使用即可。

public interface QueryResolver {
    UserTO user(String login) throws Exception;
}

InvocationHandler实现

通常我们在Java中只需实现InvocationHandler接口,本文Scala和Kotlin同样使用该接口实现代理。如下:

final public class JavaResolverProxy implements InvocationHandler, JavaDeserializerAdapter {
    private GraphQLResponseProjection projection;private GraphQLOperationRequest request;private final ServerConfig config;public JavaResolverProxy(ServerConfig config, GraphQLResponseProjection projection, Class<? extends GraphQLOperationRequest> request) {
    this.config = config;this.projection = projection;try {
    this.request = request.newInstance();} catch (InstantiationException | IllegalAccessException e) {
    throw new ExecuteException("newInstance failed: ", e.getLocalizedMessage(), e);}}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) {
    if (Object.class.equals(method.getDeclaringClass())) {
    try {
    return method.invoke(this, args);} catch (Throwable t) {
    throw new ExecuteException("invoke failed: ", t.getLocalizedMessage(), t);}} else {
    return proxyInvoke(method, args);}}private Object proxyInvoke(Method method, Object[] args) {
    Field field = null;List<GraphQLResponseField> fields;Class<?> entityClass;Type type = method.getGenericReturnType();// 获取方法的返回类型,用于后续的json解析if (type instanceof ParameterizedType) {
    Type[] parameterizedType = ((ParameterizedType) type).getActualTypeArguments();entityClass = (Class<?>) parameterizedType[0];} else {
    entityClass = (Class<?>)type;}if (isPrimitive(entityClass)) {
    assert(projection == null);} else {
    assert(projection != null);}// 利用Java8的特性,获取参数名列表与参数值组成map,用于发送graphql请求,可以不管List<Parameter> parameters = Arrays.stream(method.getParameters()).collect(Collectors.toList());if (!parameters.isEmpty()) {
    List<String> parameterNames = parameters.stream().map(Parameter::getName).collect(Collectors.toList());List<Object> arguments = Arrays.stream(args).collect(Collectors.toList());request.getInput().putAll(CollectionUtils.listToMap(parameterNames, arguments));}// 使用反射获取父类的字段try {
    field = projection.getClass().getSuperclass().getDeclaredField("fields");field.setAccessible(true);fields = (List<GraphQLResponseField>) field.get(projection);} catch (NoSuchFieldException | IllegalAccessException e) {
    throw new ExecuteException("access fields failed: ", e.getLocalizedMessage(), e);} finally {
    if (field != null) {
    field.setAccessible(false);}}//if fields not null, use it directly, because user want to select fieldsif (projection != null && (fields == null || fields.isEmpty())) {
    throw new ExecuteException("projection verification failed: ", "fields of projection cannot be empty", null);}GraphQLRequest graphQLRequest = new GraphQLRequest(request, projection);Object ret;// 发送graphql请求获取json解析后的对象ret = OkHttp.syncRunQuery(config, graphQLRequest, entityClass, buildFunction3());return ret;}}

代理实现

有了处理器的实现类,我们还需构造代理对象,如下:

final public class GitHubJavaClient {
    private ServerConfig config;private Class<?> resolver;private GraphQLResponseProjection projection;private Class<? extends GraphQLOperationRequest> request;private GitHubJavaClient() {
    }private Object getResolver() {
    JavaResolverProxy invocationHandler = new JavaResolverProxy(config, projection, request);return Proxy.newProxyInstance(resolver.getClassLoader(), new Class[]{
    resolver}, invocationHandler);}private void setConfig(ServerConfig config) {
    this.config = config;}private void setResolver(Class<?> resolver) {
    this.resolver = resolver;}private void setRequest(Class<? extends GraphQLOperationRequest> request) {
    this.request = request;}private void setProjection(GraphQLResponseProjection projection) {
    this.projection = projection;}public static GitHubJavaClientBuilder newBuilder() {
    return new GitHubJavaClientBuilder();}public static class GitHubJavaClientBuilder {
    private GraphQLResponseProjection projection;private Class<? extends GraphQLOperationRequest> request;private ServerConfig config;private GitHubJavaClientBuilder() {
    }public GitHubJavaClientBuilder setRequest(Class<? extends GraphQLOperationRequest> request) {
    this.request = request;return this;}public GitHubJavaClientBuilder setConfig(ServerConfig config) {
    this.config = config;return this;}public GitHubJavaClientBuilder setProjection(GraphQLResponseProjection projection) {
    this.projection = projection;return this;}@SuppressWarnings(value = "unchecked")public <Resolver> Resolver build(Class<Resolver> resolver) {
    GitHubJavaClient invoke = new GitHubJavaClient();assert (resolver != null);assert (request != null);invoke.setProjection(projection);invoke.setResolver(resolver);invoke.setConfig(config);invoke.setRequest(request);return (Resolver) invoke.getResolver();}}}

使用

不需要new,直接调用接口的方法

public class JavaClientExample {
    public static void main(String[] args) throws Exception {
    // 1. Use projection to select the preset returned.UserResponseProjection userResponseProjection = new UserResponseProjection().id().avatarUrl().login().resourcePath();QueryResolver queryResolver = GitHubJavaClient.newBuilder()// 2. Set the service endpoint..setConfig(ServerConfig.apply("https://api.github.com/graphql", Collections.singletonMap("Authorization", "Bearer xx"))).setProjection(userResponseProjection)// 3. Set the request corresponding to the resolver..setRequest(UserQueryRequest.class)// 4. Set the resolver that needs a proxy..build(QueryResolver.class);// 5. Use resolver to create a call.UserTO userTO = queryResolver.user("jxnu-liguobin"); // projection and request must correspond to the return type of the user method.System.out.println(userTO.toString());}
}

二、Scala中使用Java反射

被代理对象

trait QueryResolver {
    def user(login: String): UserTO
}

Scala中也可直接使用Java反射操作Java/Scala类,但是代码不如Scala反射优美。同时有些Scala特有类型可能反射不到,此时必须使用Scala反射。

InvocationHandler实现

//这里的Manifest是Scala特有,用于获取运行时类
final class ScalaResolverProxyV1[Request <: GraphQLOperationRequest : Manifest] private
(val config: ServerConfig, val projection: GraphQLResponseProjection) extends InvocationHandlerwith JavaDeserializerAdapter {
    private lazy val request: GraphQLOperationRequest = manifest[Request].runtimeClass.getConstructor(classOf[String]).newInstance(null).asInstanceOf[GraphQLOperationRequest]override def invoke(proxy: AnyRef, method: Method, args: Array[AnyRef]): Any =if (classOf[AnyRef] == method.getDeclaringClass) {
    try {
    method.invoke(this, args)} catch {
    case t: Throwable =>throw ExecuteException("invoke failed: ", t.getLocalizedMessage, t)}} else {
    proxyInvoke(method, args)}private def proxyInvoke(method: Method, args: Array[AnyRef]): Any = {
    val `type` = method.getGenericReturnTypeval entityClass = `type` match {
    case parameterizedType1: ParameterizedType =>val parameterizedType = parameterizedType1.getActualTypeArgumentsparameterizedType(0).asInstanceOf[Class[_]]case _ => `type`.asInstanceOf[Class[_]]}if (isPrimitive(entityClass)) {
    assert(projection == null)} else {
    assert(projection != null)}val parameters = method.getParameters.toListif (parameters.nonEmpty) {
    val parameterNames = parameters.map(_.getName)val arguments = args.toListrequest.getInput.putAll(CollectionUtils.listToMap(parameterNames, arguments))}// TODO remove reflectvar field: Field = nullvar fields: util.List[GraphQLResponseField] = nulltry {
    field = projection.getClass.getSuperclass.getDeclaredField("fields")field.setAccessible(true)fields = field.get(projection).asInstanceOf[util.List[GraphQLResponseField]]} catch {
    case e@(_: NoSuchFieldException | _: IllegalAccessException) =>throw ExecuteException("access fields failed: ", e.getLocalizedMessage, e)} finally if (field != null) field.setAccessible(false)if (projection != null && (fields == null || fields.isEmpty)) {
    throw ExecuteException("projection verification failed: ", "fields of projection cannot be empty")}val graphQLRequest = new GraphQLRequest(request, projection)OkHttp.syncRunQuery(config, graphQLRequest, entityClass)(extractData)}}object ScalaResolverProxyV1 {
    def apply[Request <: GraphQLOperationRequest : Manifest](config: ServerConfig, projection: GraphQLResponseProjection):ScalaResolverProxyV1[Request] = new ScalaResolverProxyV1[Request](config, projection)
}

代理实现


class GithubScalaClient {
    private var config: ServerConfig = _private var resolver: Class[_] = _private var projection: GraphQLResponseProjection = _private def getResolverObjectV1[Request <: GraphQLOperationRequest : Manifest]: AnyRef = {
    val invocationHandler: ScalaResolverProxyV1[Request] = ScalaResolverProxyV1[Request](config, projection)Proxy.newProxyInstance(resolver.getClassLoader, Array[Class[_]](resolver), invocationHandler)}
}object GithubScalaClient {
    def newBuilder: GitHubScalaClientBuilder = new GitHubScalaClientBuilder()class GitHubScalaClientBuilder() {
    private var projection: GraphQLResponseProjection = _private var config: ServerConfig = _def setConfig(config: ServerConfig): GitHubScalaClientBuilder = {
    this.config = configthis}def setProjection(projection: GraphQLResponseProjection): GitHubScalaClientBuilder = {
    this.projection = projectionthis}def buildV1[Resolver: Manifest, Request <: GraphQLOperationRequest : Manifest]: Resolver = {
    assert(this.config != null)val invoke = new GithubScalaClientinvoke.config = this.configinvoke.projection = this.projectioninvoke.resolver = manifest[Resolver].runtimeClassinvoke.getResolverObjectV1[Request].asInstanceOf[Resolver]}}}

使用

object ScalaClientExample extends App {
    val userResponseProjection = new UserResponseProjection().id().avatarUrl().login().resourcePath()val config = ServerConfig("https://api.github.com/graphql", Map("Authorization" -> "Bearer xx"))val queryResolver = GithubScalaClient.newBuilder.setConfig(config).setProjection(userResponseProjection).buildV1[QueryResolver, UserQueryRequest]val userTO = queryResolver.user("jxnu-liguobin")println(userTO.id) //tostring failed, because jackson use java Deserializer
}

三、Scala反射

被代理对象

trait QueryResolver {
    def user(login: String): UserTO
}

InvocationHandler实现

// Scala反射的环境
import scala.reflect.runtime.{
     universe => ru }
import scala.reflect.runtime.universe._final class ScalaResolverProxyV2[Request <: GraphQLOperationRequest : Manifest, Out: Manifest] private //使用Manifest传递返回类型,不再需要通过返回获取方法的返回类型,json解析时,需要使用ScalaObjectMapper和CaseClassObjectMapper
(val config: ServerConfig, val projection: GraphQLResponseProjection) extends InvocationHandlerwith ScalaDeserializer {
    // 使用Scala反射获取构造函数 private[this] lazy val constructor = getRuntimeMirror.reflectClass(getRequestScalaType.typeSymbol.asClass).reflectConstructor(getRequestScalaType.members.find(_.isConstructor).get.asMethod)private val request: GraphQLOperationRequest = constructor.apply(null).asInstanceOf[GraphQLOperationRequest]//当前类的运行时环境private[this] def getRuntimeMirror: ru.Mirror = runtimeMirror(getClass.getClassLoader)//获取Request泛型的运行时类型,通过类型反射出构造函数 private[this] def getRequestScalaType: ru.Type = typeOf[Request]override def invoke(proxy: AnyRef, method: Method, args: Array[AnyRef]): Any =if (classOf[AnyRef] == method.getDeclaringClass) {
    try {
    method.invoke(this, args)} catch {
    case t: Throwable =>throw ExecuteException("invoke failed: ", t.getLocalizedMessage, t)}} else {
    proxyInvoke(method, args)}private def proxyInvoke(method: Method, args: Array[AnyRef]): Any = {
    val `type` = method.getGenericReturnTypeval isCollection = `type` match {
    case _: ParameterizedType => truecase _ => false}if (isPrimitive(manifest[Out].runtimeClass)) {
    assert(projection == null)} else {
    assert(projection != null)}val parameters = method.getParameters.toListif (parameters.nonEmpty) {
    val parameterNames = parameters.map(_.getName)val arguments = args.toListrequest.getInput.putAll(CollectionUtils.listToMap(parameterNames, arguments))}// use shapeless LabelledGeneric// Scala反射获取父类的私有字段def fieldValue(name: String): Any = {
    //获取到当前类加载的运行时环境,反射出projection对象父类中的名为“fields”的字段//members返回projection类的所有成员,包含继承过来的,所以这里可以拿到父类的“fields”字段val im = ru.runtimeMirror(projection.getClass.getClassLoader)getRuntimeMirror.classSymbol(projection.getClass).toType.members.filter(!_.isMethod).filter(_.name.decodedName.toString.trim.equals(name)).map(s => {
    im.reflect(projection).reflectField(s.asTerm).get}).head}val fields: java.util.List[GraphQLResponseField] = fieldValue("fields").asInstanceOf[java.util.List[GraphQLResponseField]]if (projection != null && (fields == null || fields.isEmpty)) {
    throw ExecuteException("projection verification failed: ", "fields of projection cannot be empty")}val graphQLRequest = new GraphQLRequest(request, projection)// extractData是一个解析函数,Out传递过去用于解析json。注意,这里没有entityClassOkHttp.syncRunQuery(config, isCollection, graphQLRequest)(extractData[Out])}}object ScalaResolverProxyV2 {
    def apply[Request <: GraphQLOperationRequest : Manifest, Out: Manifest](config: ServerConfig, projection: GraphQLResponseProjection):ScalaResolverProxyV2[Request, Out] = new ScalaResolverProxyV2[Request, Out](config, projection)
}

代理实现

其实只要在上面的GithubScalaClient中添加2个方法即可。

  private def getResolverObjectV2[Request <: GraphQLOperationRequest : Manifest, Out: Manifest]: AnyRef = {
    val invocationHandler: ScalaResolverProxyV2[Request, Out] = ScalaResolverProxyV2[Request, Out](config, projection)Proxy.newProxyInstance(resolver.getClassLoader, Array[Class[_]](resolver), invocationHandler)}def buildV2[Resolver: Manifest, Request <: GraphQLOperationRequest : Manifest, Out: Manifest]: Resolver = {
    assert(this.config != null)val invoke = new GithubScalaClientinvoke.config = this.configinvoke.projection = this.projectioninvoke.resolver = manifest[Resolver].runtimeClassinvoke.getResolverObjectV2[Request, Out].asInstanceOf[Resolver]
}

使用

由于我们使用了ManifestScalaObjectMapper,我们可以直接在调用时传递返回类型,这样就不需要使用反射获取方法的返回类型,减少一步操作,也更符合Scala代码风格。不过由于Jackson-scala-module提示不再支持Scala3,所以如果不能使用Manifest,那么就得继续使用entityClass。使用Manifest如下:

  val userResponseProjection1 = new UserResponseProjection().id().avatarUrl().login().resourcePath()val queryResolver1 = GithubScalaClient.newBuilder.setConfig(config).setProjection(userResponseProjection).buildV2[QueryResolver, UserQueryRequest, UserTO]//指定返回类型val userTO1 = queryResolver1.user("jxnu-liguobin")println(userTO.toString())

当然,动态代理+JSON的最大问题之一是无法处理编译时的类型不匹配,只会在运行时暴露出来错误。本文不考虑性能问题,专注可行性,主要用于测试代码生成库graphql-java-codegen。

四、Kotlin中使用Java反射

被代理对象

interface QueryResolver {
    fun rateLimit(dryRun: Boolean?): RateLimitTO?
}

具体就不写了,坑也很多,三种语言的源码在github-graphql-client
使用如下:

object KotlinClientExample {
    @JvmStaticfun main(args: Array<String>) {
    // Since Kotlin has a mandatory non-null for fields, a field-less interface test is used hereval rateLimitResponseProjection = RateLimitResponseProjection().`all$`(1)val queryResolver = GithubKotlinClient.newBuilder().setConfig(ServerConfigAdapter("https://api.github.com/graphql",mapOf(Pair("Authorization", "Bearer xx")))).setProjection(rateLimitResponseProjection).build<QueryResolver, RateLimitQueryRequest>()val rateLimit = queryResolver.rateLimit(true)println(rateLimit.toString())}
}
  相关解决方案