JAVA动态代理 ps:模拟jdk

本文深入探讨了Java动态代理机制及其在Spring AOP框架中的应用,包括代理类生成过程、实现细节和实战案例。通过实现自定义动态代理类和Spring AOP配置,展示了如何在实际项目中灵活使用这些技术,实现业务逻辑的增强和拦截。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

刚接触Java的时候,学长学姐带着学。那个时候有个学长说,其实他很想学java反射机制的。


那个时候听他说,反射机制能自动帮用户生成文件等。那个时候我听了感觉特牛逼,图样图森破啊


这个学期,导师(工头)催的不紧,然后就自己去看一些动态代理的视频,代理模式等。


看了之后,自己也写了一些程序,模拟jdk的动态代理。


lz个人觉得会用动态代理,实现InvocationHandler接口,并重写其invoke方法,加入自己的业务逻辑


调用的时候,interface i = (interface) Proxy.newInstance(loadclass,interfaces,invocationhandler


jdk的这个方法是返回一个代理类,这个代理类同样的实现的interface这个接口


loadclass lz觉得传进去的参数,是供jdk生成该类的二进制class文件时,直接load进程序时用到的。


lz的猜想:这个loadclass生成的最终的二进制class文件,是已经修改了其里面的内容的。下面lz会解释为什么会这样想


interfaces  注意这个s,是传进去这个代理类需要实现的所有接口类class数组


invocationhandler 当每执行一个方法的时候都调用invocationhandler里我们想要他加入的业务逻辑,因为我们重写了InvocationHandler接口的invoke方法


下面是个简单的例子:



package Interface;//接口包

public interface TestInterface {
	public void print();
}

package BLL;//接口实现包

import Interface.TestInterface;

public class TestImpl implements TestInterface{

	@Override
	public void print() {
		System.out.println("你好呀");
	}
}

package Proxy;//加入自己的业务逻辑的InvocationHandler类

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

public class LoggerProxy implements InvocationHandler{

    private Object object = null;
    
    public LoggerProxy(Object object)
    {
        this.object = object;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object tmpObject = null;
        tmpObject = method.invoke(object, args);//object:调用这个方法的实现类,args:传入的参数,若无则null
        return tmpObject;
    }
}




package test;//测试类

import java.lang.reflect.Proxy;

import org.omg.CORBA.PUBLIC_MEMBER;

import BLL.TestImpl;
import Interface.TestInterface;
import Proxy.LoggerProxy;

public class Test{

	public static void main(String[] args) {
		TestInterface impl = new TestImpl();//声明TestImpl对象
		LoggerProxy proxy = new LoggerProxy(impl);//声明LoggerProxy对象并传入,方法执行实体,ps:invoke(obj,args)
		TestInterface api = (TestInterface) Proxy.newProxyInstance(impl.getClass().getClassLoader(), impl.getClass().getInterfaces(), proxy);
		api.print();
	}
}


这时候我修改实现InvocationHandler接口的类的invoke方法,加入我自己的业务逻辑


我要实现每执行一个方法,都写入日志,包括错误


贴上Porxy包里的代码:

package Proxy;

import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class LoggerProxy implements InvocationHandler{

	private Object object = null;
	private boolean sign = false;
	private BufferedWriter bw = null;
	
	public LoggerProxy(Object object)
	{
		this.object = object;
	}
	private void openStream()
	{
		if (!sign) {
			try {
				bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("./log.txt")));
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}
			sign = true;
		}else {
			try {
				bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(".\\log.txt",true)));
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}
		}
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		this.openStream();
		bw.newLine();
		bw.write(object.getClass().getName() + " has start method:" + method.getName());
		Object tmpObject = null;
		try {
			tmpObject = method.invoke(object, args);
		} catch (Exception e) {
			bw.write("\n\t"+e.toString());
			for(int i = 0 ; i < e.getStackTrace().length ; i ++)
			{
				bw.write("\n\t\t\tMethod : "+e.getStackTrace()[i].getMethodName()+
						"("+e.getStackTrace()[i].getFileName()+":" + e.getStackTrace()[i].getLineNumber()+")");		
			}
		} 
		bw.newLine();
		bw.write(object.getClass().getName() + " has end method:" + method.getName());
		bw.close();
		return tmpObject;
	}
}





理解了这个过程,就不难理解spring的aop的思想了。


spring已经封装的很好了,真的要会用的话,看一些视频不难学会,比较容易上手。但要在项目中活用的,还得见仁见智。


基本就是想在哪加入业务逻辑就在哪加入!


我现在自己写的,有个前提,基于接口编程


看这个方法传入的参数  Proxy.newInstance(classloader,interfaces,invocationhandler)


没错,一定是要实现至少一个接口的。继承这种是不行的。


但是!在spring的项目里,你的类不一定要有接口!


如果有,则会调用jdk的Proxy和InvocationHandler帮你实现代理


如果没有,spring则调用cglib直接生成二进制码的方式实现代理



俗话说:知其然更要知其所以然。


jdk里具体是怎么实现的呢?不多说,贴上代码


package cn.Interface;//接口包

public interface FTD {
	public void show_Dream(Integer a);
}

package cn.Impl;//实现包

import cn.Interface.FTD;

public class FTDImpl implements FTD{

	@Override
	public void show_Dream(Integer a) {
		System.out.println("This is my Dream!");
	}
}

package cn.Invocation;//Invocation包  包括接口跟实现类

import java.lang.reflect.Method;

public interface MyInvocationHandlerInterface {//接口
	public Object invoke(Object object , Method m , Object[] args);
}

package cn.Invocation;

import java.lang.reflect.Method;

public class MyInvocationHandler implements MyInvocationHandlerInterface{//实现类

	private Object target = null;
	
	public MyInvocationHandler(Object obj){
		target = obj;
	}
	
	@Override
	public Object invoke(Object object, Method m, Object[] args) {
		try {
			m.invoke(target, args);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}


重头戏来了!


package cn.Test;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;

import cn.Interface.FTD;
import cn.Invocation.MyInvocationHandler;
import cn.Invocation.MyInvocationHandlerInterface;

public class Proxy {
	public static Object newProxyIntance(Class clz , MyInvocationHandlerInterface h) throws Exception{
		
		String rt = "\r\n";//windows下/r/n是换行回车,一般\n也可以。ps:windows读取文件的时候会自动加上\r
		
		String methodStr = "";//方法存储字符串
		
		Method[] methods = clz.getMethods();
		for(Method method : methods)
		{
			Class<?>[] types = method.getParameterTypes();
			String typestr = "";//方法的形参和形参类字符串 例如:java.lang.Integer arg0
			String paraStr = "";//方法的形参名  例如:arg0
			String paraclassStr = "";//方法的形参类名 例如:Integer.class
			int i = 0;
			for(Class c : types)
			{
				if(types.length != (i + 1)){
					typestr += c.getName() + " arg"+i+", ";
					paraStr += "arg"+i+",";
					paraclassStr += ","+c.getName()+".class";
				}
				else{
					typestr += c.getName()+ " arg"+i;
					paraStr += "arg"+i;
					paraclassStr += ","+c.getName()+".class";
				}
				i++;
			}
			System.out.println(paraStr);
			try {
				System.out.println(method.getReturnType());
			} catch (Exception e) {
				e.printStackTrace();
			}
			methodStr += "@Override" +rt+
						 "		public "+ method.getReturnType() + " "+method.getName() +"(" + typestr + "){"+rt+
						 "			try{" +rt+
						 "				Method md = "+ clz.getName()+".class.getMethod(\""+ method.getName() +"\""+paraclassStr+");"+rt+
						 "				Object[] objs = {"+paraStr+"};"+rt+
						 "				h.invoke(this,md,objs);" +rt+
						 "			}catch (NoSuchMethodException e){" +rt+
						 "					e.printStackTrace();" +rt+
						 "			}"+rt+
						 "		}";
		}
		
		String classStr = 
			"import java.lang.reflect.Method;"+rt+
			"public class TmpTest implements " + clz.getName() + "{"+rt+
			"		cn.Invocation.MyInvocationHandler h;"+rt+
			"		public TmpTest(cn.Invocation.MyInvocationHandler h){"+rt+
			"				this.h = h;"+rt+
			"		}"+rt+
			"		"+methodStr+rt+
			"}";
		
		String fileName = "f:/TmpFile/TmpTest.java";
		
		File file = new File(fileName);
		FileWriter fw = new FileWriter(file);
		
		fw.write(classStr);
		fw.flush();
		fw.close();
		
		//拿到java的编译器,我MyEclipse 10自带的jdk不能实现,需要导入外部的jdk。我用的是jdk7
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
		//第一个参数  监听器:编译错误的话由他收集;第二个:第三个:国际化相关的,用的什么语言,null就是默认的  ,默认GBK;
		//iterable 简单理解成数组
		Iterable units = fileMgr.getJavaFileObjects(fileName);
		CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
		t.call();
		fileMgr.close();
		
		//load into memory and create an instance
		//把硬盘里的弄到内存里 ,ps:可以从网上load类
		URL[] urls = new URL[]{new URL("file:/f:/TmpFile/")};
		URLClassLoader ul = new URLClassLoader(urls);
		Class c = ul.loadClass("TmpTest");//把生成的.class文件load进程序
		System.out.println(c);
		Constructor ctr = c.getConstructor(MyInvocationHandler.class);//返回该类的形参为MyInvocationHandler类对象的构造函数
		Object obj = ctr.newInstance(h);//实例化
		
		return obj;
	}
}

下面是测试类

package cn.Test;

import cn.Impl.FTDImpl;
import cn.Interface.FTD;
import cn.Invocation.MyInvocationHandler;

public class Test {
	public static void main(String[] args) throws Exception {
		FTDImpl ftdImpl = new FTDImpl();
		MyInvocationHandler h = new MyInvocationHandler(ftdImpl);
		FTD ftd = (FTD) Proxy.newProxyIntance(FTD.class, h);
		ftd.show_Dream(1);
	}
}

再把生成的代理文件,即 .java文件,贴上来

import java.lang.reflect.Method;
public class TmpTest implements cn.Interface.FTD{
		cn.Invocation.MyInvocationHandler h;
		public TmpTest(cn.Invocation.MyInvocationHandler h){
				this.h = h;
		}
		@Override
		public void show_Dream(java.lang.Integer arg0){
			try{
				Method md = cn.Interface.FTD.class.getMethod("show_Dream",java.lang.Integer.class);
				Object[] objs = {arg0};
				h.invoke(this,md,objs);
			}catch (NoSuchMethodException e){
					e.printStackTrace();
			}
		}
}


这里我来回答下之前的猜想


lz实现的Proxy.newInstance方法里只需要传入两个参数,就能够实现代理


但是jdk需要传入三个参数


jdk还需要传入classloader


在lz实现里,用URL把生成的.class文件地址传入,然后load进代理类。


如果他这么做,那就只要两个参数就行了,不需要再传入classloader(当然,可能jdk还有其他需要用到classloader的地方,请知道的大牛们点拨)


所以lz的猜想是:jdk在生成classloader的class文件的时候就已经把代理的方法也放进去了。


jdk的Proxy.newInstance方法返回的其实就是classloader生成的class文件的对象,只不过是已经修改过的。





CH341A编程器是一款广泛应用的通用编程设备,尤其在电子工程和嵌入式系统开发领域中,它被用来烧录各种类型的微控制器、存储器和其他IC芯片。这款编程器的最新版本为1.3,它的一个显著特点是增加了对25Q256等32M芯片的支持。 25Q256是一种串行EEPROM(电可擦可编程只读存储器)芯片,通常用于存储程序代码、配置数据或其他非易失性信息。32M在这里指的是存储容量,即该芯片可以存储32兆位(Mbit)的数据,换算成字节数就是4MB。这种大容量的存储器在许多嵌入式系统中都有应用,例如汽车电子、工业控制、消费电子设备等。 CH341A编程器的1.3版更新,意味着它可以与更多的芯片型号兼容,特别是针对32M容量的芯片进行了优化,提高了编程效率和稳定性。26系列芯片通常指的是Microchip公司的25系列SPI(串行外围接口)EEPROM产品线,这些芯片广泛应用于各种需要小体积、低功耗和非易失性存储的应用场景。 全功能版的CH341A编程器不仅支持25Q256,还支持其他大容量芯片,这意味着它具有广泛的兼容性,能够满足不同项目的需求。这包括但不限于微控制器、EPROM、EEPROM、闪存、逻辑门电路等多种类型芯片的编程。 使用CH341A编程器进行编程操作时,首先需要将设备通过USB连接到计算机,然后安装相应的驱动程序和编程软件。在本例中,压缩包中的"CH341A_1.30"很可能是编程软件的安装程序。安装后,用户可以通过软件界面选择需要编程的芯片类型,加载待烧录的固件或数据,然后执行编程操作。编程过程中需要注意的是,确保正确设置芯片的电压、时钟频率等参数,以防止损坏芯片。 CH341A编程器1.3版是面向电子爱好者和专业工程师的一款实用工具,其强大的兼容性和易用性使其在众多编程器中脱颖而出。对于需要处理25Q256等32M芯片的项目,或者26系列芯片的编程工作,CH341A编程器是理想的选择。通过持续的软件更新和升级,它保持了与现代电子技术同步,确保用户能方便地对各种芯片进行编程和调试。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值