Java动态代理之实践

动态代理实现方式

Java中的动态代理给我们提供一种动态生成类的方式,有很好的灵活性,这种技术一般会出现在一些第三方框架中,来降低接入方的使用成本。以下为常用的实现动态代理的几种方式:

1.JDK自带的Proxy方式
优点:JDK亲儿子,无依赖,使用简单。
缺点:代理类必须继承至少一个接口,无法继承已有父类。

2.asm方式,基于class字节码的操作
优点:很底层到操作,性能高,对性能要求苛刻的建议使用。
缺点:使用成本高,要熟悉JVM汇编指令。

3.javassist方式,基于class字节码的操作
优点:Api简单,通熟易懂,使用成本低。
缺点:性能相对偏低。

4.cglib方式,这个是基于ASM的
优点:Api简单,高性能,高灵活性,支持继承父类,可以不用实现接口。
缺点:这个真的很强大,个人感觉比JDK自带的要强大很多,一定要说的话只能说使用这个需要加jar包依赖。

本文就介绍最常用的JDK自带的Proxy方式

非动态代理模式处理业务问题

User,一个表示用户信息的类:

public class User {
    public String userName;
    public String sex;
    public String address;

    @Override
    public String toString() {
        return "User{" +
                "userName='" + userName + '\'' +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

NetWorkHelper,一个处理网络请求(这里只是模拟)的类:

private static final Gson sGson = new Gson();

    public static String request(String url, Map<String, Object> params) {
        if (params != null) {
            if ("ting说你跳".equals(params.get("username"))
                    && "123456".equals(params.get("password"))) {

                User user = new User();
                user.address = "上海";
                user.sex = "男";
                user.userName = "ting说你跳";
                return sGson.toJson(user);
            }
        }
        return null;
    }

这里我们模拟一个简单的网络请求。
当我们业务场景需要调用网络请求执行登录操作的时候,会这样写:

private static final String API_LOGIN = "http://tsnt.dynamicproxy.com";

    private static final Gson   sGson     = new Gson();

    public static User login(String username, String password) {
        Map<String, Object> params = new HashMap<>();
        params.put("username", username);
        params.put("password", password);
        String response = NetWorkHelper.request(API_LOGIN, params);
        //注,这里只是为了举例说明一下,就假设此时的数据结构就是跟User一致的
        return sGson.fromJson(response, User.class);
    }

那么,有什么问题呢。其实如果只是一个单单的login方法很难直观的反馈出来问题所在,很多时候我们为了去验证一个事物的合理性我们不妨去开始极端遐想一下这种情况:现在UserApi中又多了register方法,query方法,getToken方法,validate方法……甚至接下来一个UserApi已经满足不了我们了,我们开始有GoodsApi,OrderApi,MessageApi等等等等,试想一下N个Api的类,每个Api类都有N个类似于上面login的这种方法,而实际情况下我们request的入参还远远不止username,password两个这么简单。当我们业务场景扩大的时候,这些都是我们势必要面对的。这是一个问题,那能不能去以一种更优雅的方式去解决从而简化业务方的代码量并且降低使用成本呢。这里便引入了我们的动态代理。

动态代理模式处理业务问题

我们的目标是状态是这样的:让业务方写写接口,加注解配置,就可以直接使用。代码量少,简洁明了,方便使用。
因为下面内容会涉及到到注解和反射,如果不清楚注解和反射的同学建议先看一下这两篇文章:
1.Java反射
2.Java注解

定义两个支持配置的注解

@Inherited
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface URL {
    String value();
}
@Inherited
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@interface Param {
    String value();
}

这里不过多说明,就是两个可配字符串的注解。

定义一套接口和实现类:

网络请求的接口

public interface IRequest {

    String url();

    Map<String, Object> params();

    Class<?> responseCls();
}

网络请求的实现类

public class Request implements IRequest {
    String              url;
    Map<String, Object> params;
    Class<?>            responseCls;

    public Request(String url, Map<String, Object> params, Class<?> responseCls) {
        this.url = url;
        this.params = params;
        this.responseCls = responseCls;
    }

    @Override
    public String url() {
        return url;
    }

    @Override
    public Map<String, Object> params() {
        return params;
    }

    @Override
    public Class<?> responseCls() {
        return responseCls;
    }
}

执行网络请求的接口:

public interface INetExecutor {

    <T> T execute(IRequest request);
}

执行网络请求的实现类:

public class DefaultNetExecutor implements INetExecutor {

    private static final Gson sGson = new Gson();

    @Override
    public <T> T execute(IRequest request) {
        String response = NetWorkHelper.request(request.url(), request.params());
        return (T) sGson.fromJson(response, request.responseCls());
    }
}

以上的执行网络请求的实现类已经有默认的实现方式(使用上面的NetWorkHelper),主要是抽象出接口可以让业务方自主定制真正的执行操作。

动态代理器

public class ApiGenerator {

    private static final Map<Class, Object> sApiCache = new HashMap<>();

    private static INetExecutor sNetExecutor;

    private static class Handler<T> implements InvocationHandler {

        private Class<T> apiInterface;

        public Handler(Class<T> apiInterface) {
            this.apiInterface = apiInterface;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            IRequest request = resolveRequest(method, args, apiInterface);
            if (sNetExecutor == null) {
                sNetExecutor = defaultNetExecutor();
            }
            return sNetExecutor.execute(request);
        }
    }

    private static INetExecutor defaultNetExecutor() {
        return new DefaultNetExecutor();
    }

    private static <T> IRequest resolveRequest(Method method, Object[] args, Class<T> apiInterface) {
        StringBuilder urlBuilder = new StringBuilder();
        Map<String, Object> params = null;
        if (apiInterface.isAnnotationPresent(URL.class)) {
            String baseUrl = apiInterface.getAnnotation(URL.class).value();
            if (!TextUtils.isEmpty(baseUrl)) {
                urlBuilder.append(baseUrl);
            }
        }
        if (method.isAnnotationPresent(URL.class)) {
            String subUrl = method.getAnnotation(URL.class).value();
            if (!TextUtils.isEmpty(subUrl)) {
                urlBuilder.append(subUrl);
            }
        }
        int index = 0;
        for (Annotation[] annotations : method.getParameterAnnotations()) {
            for (Annotation annotation : annotations) {
                if (annotation instanceof Param) {
                    String key = ((Param) annotation).value();
                    if (!TextUtils.isEmpty(key)) {
                        if (params == null) {
                            params = new HashMap<>();
                        }
                        params.put(key, args[index]);
                    }
                    break;
                }
            }
            index++;
        }
        return new Request(urlBuilder.toString(), params, method.getReturnType());
    }

    public static <T> T generateApi(Class<T> apiInterface) {
        if (apiInterface == null || !apiInterface.isInterface()) {
            throw new RuntimeException("the apiInterface is null or isn't interface.");
        }
        synchronized (ApiGenerator.class) {
            Object api = sApiCache.get(apiInterface);
            if (api == null) {
                api = Proxy.newProxyInstance(apiInterface.getClassLoader(),
                        new Class[]{apiInterface},
                        new Handler(apiInterface));
                sApiCache.put(apiInterface, api);
            }
            return (T) api;
        }
    }

    /**
     * 外部提供自定义执行器
     *
     * @param netExecutor 网络执行器
     */
    public static void setNetExecutor(INetExecutor netExecutor) {
        sNetExecutor = netExecutor;
    }
}

到此,我们的动态代理的编码部分就结束了。我们可以看一下ApiGenerator这个类有个sApiCache的静态变量,他缓存了动态代理生成的对象,这里这样做还是很有必要的,防止重复创建Api的代理类造成额外的性能消耗。

使用

业务方只需要按照我们约束的一套标准来写一个interface即可:

@URL("http://tsnt.dynamicproxy.com")
public interface LoginApi {
    User login(@Param("username") String username,
               @Param("password") String password);
}

这个是业务方要写的接口,以及对应的一些注解配置。接下来就可以直接使用LoginApi生成的具体实例了:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        findViewById(R.id.login).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                login();
            }
        });
    }

    private void login() {
        LoginApi loginApi = ApiGenerator.generateApi(LoginApi.class);
        User user = loginApi.login("123", "456");
        Toast.makeText(MainActivity.this, user.toString(), Toast.LENGTH_SHORT).show();
    }
}

由于此处模拟的网络请求,就不考虑主线程的进行这个操作了。

使用对比

这里我再贴一下两种使用前后的代码对比。
传统的方式:

public class UserApi {
    private static final String API_LOGIN = "http://tsnt.dynamicproxy.com";

    private static final Gson   sGson     = new Gson();

    public static User login(String username, String password) {
        Map<String, Object> params = new HashMap<>();
        params.put("username", username);
        params.put("password", password);
        String response = NetWorkHelper.request(API_LOGIN, params);
        //注,这里只是为了举例说明一下,就假设此时的数据结构就是跟User一致的
        return sGson.fromJson(response, User.class);
    }
}

使用动态代理:

@URL("http://tsnt.dynamicproxy.com")
public interface LoginApi {
    User login(@Param("username") String username,
               @Param("password") String password);
}

对比一下,明显能感觉到代码比之前精简了一大圈,看上去清晰明了。当业务逐渐扩大的时候,这两种模式下,无论是开发效率上还是代码精简度上根本不具有可比性。

以上就是Java动态代理的实践。

代码地址:https://github.com/tingshuonitiao/DynamicProxy_practice

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值