从回调认识动态代理 (Java)


介绍

之前的动态代理篇幅写的不好,所以重写了一下(Java实现),作为个人的复习3。同时这一章承上启下,承接反射篇章,引出AOP思想和Spring AOP

了解AOP的前置知识(本人目前只了解Java AOP)

  • 回调函数
  • 静态代理
  • 反射
  • jdk动态代理

回调方法 CallBack

将核心代码交给使用者编写

A想使用B设计的代码,其中的大部分功能符合A需求,但是A仍不满足,如果代码是写死的话,A还要重构一下并自己封装,冗余而不方便。

假设B的代码将A想重写的部分抽离出去,其余部分做一个框架,A就只需要写核心的代码即可,非常方便。所以被抽离的部分就是回调函数!

想要实现这个思想,各个语言各有不同的方法(noob的猜测);Java要实现回调函数,就要利用其重要的面向接口(Interface)思想

1.回调接口

public interface CallBack {
	public void callBackMethod();
}

2.调用类

public class Caller {
    public void call(CallBack callBack) {
        System.out.println("写死的代码");
        callBack.callBackMethod();
        System.out.println("写死的代码");
    }
}

3.重写回调方法,为了方便使用匿名内部类

public static void main(){
	Caller caller = new Caller();
    caller.call(new CallBack(){
        @Override
        public void callBackMethod(){
            System.out.println("Hello I'm Oddpalmer");
        }
    });
}
/*
写死的代码
回调方法: Hello I'm Oddpalmer
写死的代码
*/

只需要编写一个继承callback的类,并重写方法即可传入Caller实现回调,it’s simple

静态代理 Static Proxy

代理就是将非核心代码剥离出去,只关注对象本身的核心
非核心代码在AOP中称为通知(Advice)

以明星和经纪人为例:对接、签约….,这些工作经纪人做就行,明星只需要唱歌、演戏….即可,这里经纪人就作了代理的工作,并且经纪人可以去为多个明星服务。

Java实现代理的步骤

  • 代理和被代理对象类继承同一个接口
  • 代理的方法调用对象的同名方法

依旧是面向接口编程

1.接口

public interface UserService {
    void addUser(String name);
}

2.实现类

public class UserServiceImpl implements UserService{
    @Override
    public void addUser(String name) {
        System.out.println("添加用户 " + name + " 成功");
    }
}

3.实现类代理

public class UserServiceProxy implements UserService{
    private UserService target;
    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    @Override
    public void addUser(String name) {
        System.out.println("代理:[权限检查]");// 前置额外逻辑

        target.addUser(name); //UserService真实对象的核心业务逻辑

        System.out.println("代理:[日志上传]");// 后置额外逻辑
    }
}

4.Main

// 调用
public class ProxyTest {
    public static void main(String[] args) {
        UserService proxy = new UserServiceProxy(new UserServiceImpl());
        proxy.addUser("张三");
    }
}
/*  结果
  代理:[权限检查]
  添加用户 张三 成功
  代理:[日志上传]
*/

虽然静态代理能在一定程度上帮我们减少代码冗余,但是不难发现只有继承了UserService接口的类才可以被代理。如果我还有OrderService、ProductService也需要权限检查或者日志上传,就需要多个静态代理才可以实现,局促而不优雅,这就引出了我们的动态代理。

动态代理 Dynamic Proxy

我们已经知道静态代理与被代理对象的类会继承同一个接口。那么Java实现动态代理也要知道创造的代理类要继承什么接口,从而和被代理对象在接口上保持一致!所以就要依赖Java反射机制,以获取运行时类的接口信息和方法,然后由JVM在内存中动态创造多个静态代理类,实现代理!(也称作JDK动态代理,用接口实现。cglib本文不作讨论)

不了解反射的可以看博主的这篇文档 待重构todo

jvm生成的一个动态代理类大致长这样,发现没有,和静态代理一样的,区别只是动态生成的(里面涉及了一个回调,我们稍后再讲),DeepSeek给出的代码:

public final class $Proxy0 extends Proxy implements UserService {
    private static Method m_addUser;
    static {
        try {
            m_addUser = UserService.class
                    .getMethod("addUser", String.class);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }
    // 父类 Proxy 已经持有 InvocationHandler h
    public $Proxy0(InvocationHandler h) {
        super(h);
    }

    @Override
    public void addUser(String name) {
        try {
            // invoactionHandler.invoke(), 所有接口方法 → 回调调用者
            h.invoke(this, m_addUser, new Object[]{name});
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
}

如何生成一个动态代理类?Java给我们设计了java.lang.reflect.Proxy类,其中的newProxyInstance()的三个参数 就是关键…

public static Object newProxyInstance(
        ClassLoader loader,     // 用target类加载器
        Class<?>[] interfaces,  // 用target的接口:硬性规定只能代理接口
        InvocationHandler h    // 传入回调实现类: 要求重写回调方法,用于承载“方法调用时的统一处理逻辑”。
)

参数1涉及的底层我还理解不了;参数2很明显是通过反射获取某个类的接口;参数3就是我们上文提到的回调接口,参数如下:

Interface InvotationHandler{
	public Object invoke(
		Object proxy, 
		Method method, 
		Object[] args) throws Throwable;
}

创造代理对象的步骤:

  • 调用newProxyInstance()
  • 将被代理对象的实例作为参数传入
  • 重写invocationHandler回调接口的invoke()

实际开发中,动态代理可以再封装成一个工具类,不用每次代理都去newProxyInstance(),简化代码

  public class ProxyUtil {
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(T target) {
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    // InvokeHandler接口定义的回调方法,控制权在程序员手里
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                       System.out.println("[权限检测]");// 前置 额外逻辑
                       Object o = method.invoke(target, args);// 反射包下invoke 调用真实对象方法
                       System.out.println("[日志记录]");// 后置 额外逻辑
                       return o;
                    }
                }
        );
    }
}

当你调用了ProxyUtil后

	UserService userServiceProxy = new ProxyUtil().createProxy(new UserServiceImple());
	userServiceProxy.addUser("oddpalmer");
	/*
		[权限检测]
		添加 oddpalmer 成功
		[日志记录]
	*/

程序执行顺序:

  • jvm生成了动态代理类$ProxyXXX
  • main调用代理类UserServiceProxy的addUser(),会通过重写的回调方法调用被代理对象本身的addUser()

引出Spring AOP

面向切面AOP的思想很简单,将一个对象的核心代码抽离出来,使得开发者更注重核心代码而非其他的冗余代码(通知)。动态代理就是实现这一思想的利器

Spring AOP就很好的帮我们省去了写ProxyUtil类的过程….

文章摘自:https://www.cnblogs.com/oddpalmer/p/19931115