设计模式之动态代理与spring的aop编程

本文介绍面向切面编程(AOP)的概念及其通过动态代理技术实现的功能扩展方法。通过具体示例展示如何使用Java反射机制创建动态代理类,以在不修改源代码的情况下为程序添加额外功能。

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

        AOP为Aspect Oriented Programming的缩写,意为:面向切面编程(也叫面向方面),可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。

动态代理是实现aop的一种主要方式。

 

动态代理的实现有JDK Compile API,CGLIB,ASM

如下是动态代理的原理

如下实例1:

接口movable

 

package com.old;
public interface Moveable {
	void move();
}

 

 实现movable接口的类Car

 

package com.old;

import java.util.Random;

public class Tank implements Moveable{

	@Override
	public void move() {
		// TODO Auto-generated method stub
		System.out.println("tank moving");
		try {
			Thread.sleep(new Random().nextInt(5000));
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}

 

此时如果要对Tank类进行扩展,比如前后记录日志,加上事务或者权限等,只有如下两种方法。

方法1:继承的方式,如Tank2.java

 

package com.old;

public class Tank2 extends Tank {
	@Override
	public void move() {
		// TODO Auto-generated method stub

		long start  = System.currentTimeMillis();
		super.move();
		long end =  System.currentTimeMillis();
		System.out.println("time: " + (end-start));
	}
}

 

 

但是如果被代理的对象较多会产生类爆炸,而且耦合性太强。

方法二:采用组合的方式,如Tank3.java

 

package com.old;

public class Tank3 implements Moveable{
	Tank tank;

	public Tank3(Tank tank) {
		super();
		this.tank = tank;
	}
	
	@Override
	public void move(){
		System.out.println("log start");
		tank.move();
		System.out.println("log end");
	}
}

 

组合的方法虽然不会产生类爆炸,但是不方便实现更多的扩展。

解决方法是采用动态代理

核心类Proxy.java

 

package com.proxy;

import java.io.File;
import java.io.FileWriter;
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;

public class Proxy {
	public static Object newProxyInstance(Class intf,InvocationHandler h) throws Exception{ //JDK Compile API,CGLIB,ASM
		String rt = "\r\n";
		String methodStr = "";

		Method[] methods = intf.getMethods();
		for(Method m:methods){
			methodStr += "@Override" + rt +
						 "public void " + m.getName() + "() {" + rt +
						 " try{" + rt +
						 " Method md = " + intf.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt +
						 "h.invoke(md);" + rt +
						 "}catch(Exception e){e.printStackTrace();}" + rt +
						 "}";
			
						
		}
		
		String src = "package com.proxy;" + r
				+ "import java.lang.reflect.Method;"
				+ "import com.proxy.InvocationHandler;"
				+ "public class AbProxy implements "+ intf.getName() + "{" + rt
				+ "com.proxy.InvocationHandler h;" + rt 
				+ "public AbProxy(InvocationHandler h) {" + rt
				+ "super();"  + rt
				+ "this.h = h;" + "}" + rt

				+ methodStr + rt
				
				+ "}" ;

//		String fileName = System.getProperty("user.dir")+"/src/com/proxy/TankTimeProxy.java";

		String fileName = "E:/leanspace/temp/src/com/proxy/AbProxy.java";
		File f = new File(fileName);
		FileWriter fw = new FileWriter(f);
		fw.write(src);
		fw.close();
		//compiler
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
		Iterable units =  fileMgr.getJavaFileObjects(f);
		CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
		t.call();
		
		//load into memory and create an instance
		URL[] url = new URL[]{new URL("file:/"+"E:/leanspace/temp/src/")};
		URLClassLoader ul = new URLClassLoader(url);
		Class c = ul.loadClass("com.proxy.AbProxy");
		
		Constructor ctr = c.getConstructor(InvocationHandler.class);
		Object m =  ctr.newInstance(h);

	
		return m;
	}
}

 

 通过java反射的机制动态的编译代理类的代码,只需要将被代理类实现的接口及对应的扩展功能处理逻辑的handler作为参数传入即可。

处理类接口:InvocationHandler.java

 

package com.proxy;

import java.lang.reflect.Method;

public interface InvocationHandler {
	public void invoke(Method m);
}	

记录时间的处理类TimeHandler.java

 

package com.proxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TimeHandler implements InvocationHandler{
	private Object target;

	public TimeHandler(Object target) {
		super();
		this.target = target;
	}

	@Override
	public void invoke(Method m) {
		// TODO Auto-generated method stub

		long start  = System.currentTimeMillis();
		System.out.println("startTime:" + start);
		try {
			m.invoke(target, new Object[]{});
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		long end =  System.currentTimeMillis();
		System.out.println("time: " + (end-start));
		
	}

	public Object getTarget() {
		return target;
	}

	public void setTarget(Object target) {
		this.target = target;
	}

}

 

 Client.java

 

package com.proxy;

public class Client {
	public static void main(String args[]) throws Exception{
		Tank t = new Tank();
		InvocationHandler h = new TimeHandler(t);
		
		Moveable m = (Moveable)Proxy.newProxyInstance(Moveable.class,h);
	
		m.move();
	}
}

通过Proxy.newProxyInstance(Moveable.class,h)方法动态编译生成代理类,开发人员不需要关注内部实现逻辑就可以对任意实现接口的生成代理,如果要对多个功能扩展只需要这样写。

 

Tank t = new Tank();
InvocationHandler th = new TimeHandler(t);
InvocationHandler lh = new LogHandler(th);

即可很方便的扩展。当然在spring中对一些没有实现接口的类也可以产生代理类,通过二进制码的方式(cglib包)。、

 

       当每个新的软件设计师都被要求掌握如何将需求功能转化成一个个类,并且定义它们的数据成员行为,以及它们之间复杂的关系的时候,面向切面编程(Aspect-Oriented Programming,AOP)为我们带来了新的想法、新的思想、新的模式。

 

 ---

理解补充:代理类所做的事就是将被代理类。如上面的例子tank对象.move(),重写成了,move方法.invoke(tank对象)的方式,通过handle类进行一层的invoke方法的包装,即如下代码所示,去记录时间等在不改写源代码的同时进行扩展。

       @Override
	public void invoke(Method m) {
		// TODO Auto-generated method stub

		long start  = System.currentTimeMillis();
		System.out.println("startTime:" + start);
		try {
			m.invoke(target, new Object[]{});
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		long end =  System.currentTimeMillis();
		System.out.println("time: " + (end-start));
		
	}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值