Java JDK动态代理

到处都是用的动态代理,这次是认真的

静态代理:事先已经知道了要代理什么,而且编译时代理类就已经生成

动态代理:事先不知道要代理什么,运行的时候才能确定,实现InvocationHandler接口,调用Proxy的newProxyInstance()方法通过反射动态地创建代理类

下图就是创建代理类的流程

NewImage

图来自:https://upload-images.jianshu.io/upload_images/2109481-5bc36d36f5997da1.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp

先还是一段简单的InvocationHandler的动态代理例子,然后再根据执行流程来分析

定义一个interface,也就是真实被代理的类实现的接口

package com.lihuia.invoke.service;

/**
* Copyright (C), 2018-2019
* FileName: HelloService
* Author: lihui
* Date: 2019/11/20
*/

public interface HelloService {

void sayHello();
}

实现类,被代理的真实对象就这里来的

package com.lihuia.invoke.impl;

import com.lihuia.invoke.service.HelloService;
import lombok.extern.slf4j.Slf4j;

/**
* Copyright (C), 2018-2019
* FileName: HelloImpl
* Author: lihui
* Date: 2019/11/20
*/

@Slf4j
public class HelloImpl implements HelloService {

@Override
public void sayHello() {
log.info("Hello World!");
}
}

注入一堆Bean

package com.lihuia.invoke.config;

import com.lihuia.invoke.handler.HelloHandler;
import com.lihuia.invoke.impl.HelloImpl;
import com.lihuia.invoke.service.HelloService;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Copyright (C), 2018-2019
* FileName: HelloConfig
* Author: lihui
* Date: 2019/11/20
*/

@Configuration
public class HelloConfig {

@Bean
public HelloService helloService() {
return new HelloImpl();
}

@Bean
public HelloHandler helloHandler() {
return new HelloHandler(helloService());
}
}

测试类,通过代理类调用真实类的sayHello()方法

package com.lihuia.invoke;

import java.lang.reflect.Proxy;

import javax.annotation.Resource;

import com.lihuia.invoke.config.HelloConfig;
import com.lihuia.invoke.handler.HelloHandler;
import com.lihuia.invoke.service.HelloService;
import lombok.extern.slf4j.Slf4j;
import org.testng.annotations.Test;

import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;

/**
* Copyright (C), 2018-2019
* FileName: HelloTest
* Author: lihui
* Date: 2019/11/20
*/

@ContextConfiguration(classes = {HelloConfig.class})
@Slf4j
public class HelloTest extends AbstractTestNGSpringContextTests {

@Resource
private HelloService helloService;

@Resource
private HelloHandler helloHandler;

@Test
public void helloProxyTest() {
HelloService helloProxy = (HelloService) Proxy.newProxyInstance(
helloHandler.getClass().getClassLoader(),
helloService.getClass().getInterfaces(),
helloHandler
);
helloProxy.sayHello();
}
}

Proxy.newProxyInstance(),这个静态方法就是通过反射生成一个代理对象,关键是三个参数

public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException

源码里的注释如下:

* @param   loader the class loader to define the proxy class
* @param interfaces the list of interfaces for the proxy class
* to implement
* @param h the invocation handler to dispatch method invocations to
* @return a proxy instance with the specified invocation handler of a
* proxy class that is defined by the specified class loader
* and that implements the specified interfaces

详细说明如下:

ClassLoader loader:代理类的加载器,也就是HelloHandler的类加载器

Class<?>[] interfaces:代理类实现的接口,可以有多个,也就是上面的HelloService

InvocationHandler h:当动态代理对象调用了方法的时候,就会关联上一个InvocationHandler对象,并最终又它来调用

这里可以打个断点调试一下,的确是在代理对象调用方法的时候,会调用对应的Handler的invoke方法

NewImage

Handler类如下

package com.lihuia.invoke.handler;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import lombok.extern.slf4j.Slf4j;

/**
* Copyright (C), 2018-2019
* FileName: HelloHandler
* Author: lihui
* Date: 2019/11/20
*/

@Slf4j
public class HelloHandler implements InvocationHandler {

private Object target;

public HelloHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(target, args);
return null;
}
}

通过构造方法传入一个真实的被代理对象,然后通过反射来调用方法,invoke方法的三个参数

Object proxy:代理对象

Method method:代理对象调用的方法

Object[] args:方法的参数

这里可以修改一下调用方法,加一个参数,可以得到参数的传递如下

NewImage

invoke()方法return的就是调用方法的返回值

但是可以发现一点,invoke的第一个参数proxy没用到,而且它的类型很奇怪com.sun.proxy.$Proxy0,那是因为在创建代理对象的时候是在运行的时候动态生成的对象,至于说proxy会不会用到,Stack Overflow上有一个例子可以参考下:

https://stackoverflow.com/questions/22930195/understanding-proxy-arguments-of-the-invoke-method-of-java-lang-reflect-invoca

所以JDK动态代理的流程就是调用Proxy类的newProxyInstance方法通过反射创建一个代理类对象,调用代理类目标接口方法时,会自动转发到对应的Handler处理器里的invoke()方法,来完成method的执行

 

最后还是想再写下Java的反射,因为Java本身是一个编译性语言,而反射这种动态运行过程中做一些动作号称逼格很高,但是理解起来并不很GET得到,下面通过一个小例子来看看反射的优势

接口

public interface UserService {

void Working();
}

实现类Teacher

public class Teacher implements UserService {

@Override
public void Working() {
System.out.println("Teacher Working");
}
}

实现类Doctor

public class Doctor implements UserService {

@Override
public void Working() {
System.out.println("Doctor Working");
}
}

工厂类

public class UserFactory {

public static UserService getInstance(String userType) {
UserService userService = null;

if ("Teacher".equals(userType)) {
userService = new Teacher();
} else if ("Doctor".equals(userType)) {
userService = new Doctor();
} else {
System.out.println(userType);
}

return userService;
}
}

Main方法

public class UserTest {
public static void main(String[] args) {
UserService userService = UserFactory.getInstance("Teacher");
userService.Working();
}
}

假如我们要持续新增各种职业的人,因为各行各业的人都要辛苦Working来赚钱,那么正常做法就要在UserFactory里getInstance方法新增新的职业分支,就会越来越难维护,代码还很冗长

下面来看看反射的做法

工厂类

public class UserFactory {

public static UserService getInstance(String userType) {
UserService userService = null;
try {
userService = (UserService)Class.forName(userType).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return userService;
}
}

Main方法

public class UserTest {
public static void main(String[] args) {
UserService userService = UserFactory.getInstance("com.lihuia.reflect.Teacher");
userService.Working();
}
}

这样,你如果要新增一个职业类,那么只要getInstance方法里传入该类的路径,都可以通过反射在运行中动态地创建对象,不需要修改代码

OVER

发表评论