动态代理代理和静态代理

代理模式

代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法

什么是代理?

大道理上讲代理是一种软件设计模式,目的地希望能做到代码重用。具体上讲,代理这种设计模式是通过不直接访问被代理对象的方式,而访问被代理对象的方法。这个就好比 商户---->明星经纪人(代理)---->明星这种模式。我们可以不通过直接与明星对话的情况下,而通过明星经纪人(代理)与其产生间接对话。

什么情况下使用代理?

  • 设计模式中有一个设计原则是开闭原则,是说对修改关闭对扩展开放,我们在工作中有时会接手很多前人的代码,里面代码逻辑让人摸不着头脑(sometimes the code is really like shit),这时就很难去下手修改代码,那么这时我们就可以通过代理对类进行增强。
  • 我们在使用RPC框架的时候,框架本身并不能提前知道各个业务方要调用哪些接口的哪些方法 。那么这个时候,就可用通过动态代理的方式来建立一个中间人给客户端使用,也方便框架进行搭建逻辑,某种程度上也是客户端代码和框架松耦合的一种表现。
  • Spring的AOP机制就是采用动态代理的机制来实现切面编程。

静态代理和动态代理

我们根据加载被代理类的时机不同,将代理分为静态代理和动态代理。

如果我们在代码编译时就确定了被代理的类是哪一个,那么就可以直接使用静态代理;如果不能确定,那么可以使用类的动态加载机制,在代码运行期间加载被代理的类这就是动态代理,比如RPC框架和Spring AOP机制。

对于静态代理方式代理类也要实现和被代理类相同的接口;
对于动态代理代理类则不需要显示的实现被代理类所实现的接口

比如要:实现UserServiceImpl.add方法之前进行增强

增强的方法

package com.tamakiakoo.advices;

public class TimeManager {

	public void start() {
		System.out.println("方法调用之前记录时间");
	}

	public void end() {
		System.out.println("方法调用之后记录时间");
	}
}

package com.tamakiakoo.advices;

public class TransactionManager {

	public void begin(){
		System.out.println("开启事务");
	}
	public void commit(){
		System.out.println("提交事务");
	}
}

我们先创建一个接口,IUserService

public interface IUserService {

	public void add();
	public void update();
}

创建一个接口的实现类

public class UserServiceImpl implements IUserService {
	
	@Override
	public void add() {
		System.out.println("UserServiceImpl.add()");
	}

	@Override
	public void update() {
		System.out.println("UserServiceImpl.update()");
		
	}


}

静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类??

创建静态代理对象

public class UserServiceProxy implements IUserService {
	
	TimeManager tm;
	TransactionManager tr;
	
	IUserService userService;
	
	public UserServiceProxy(TimeManager tm, TransactionManager tr,IUserService userService) {
		this.tm = tm;
		this.tr = tr;
		this.userService=userService;
	}

	

	@Override
	public void add() {
		tm.start();
		tr.begin();
		
		userService.add();
		
		tr.commit();
		tm.end();
		
	}



	@Override
	public void update() {
		userService.update();
		
	}

}

方法调用之前记录时间
开启事务
UserServiceImpl.add()
提交事务
方法调用之后记录时间

UserServiceImpl.update()

静态代理总结:
  • 优点
    • 可以做到在不修改目标对象的功能前提下,对目标功能扩展.
  • 缺点:
    • 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.

动态代理

JDK动态代理:

利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

代理类需要实现InvocationHandler接口 现实invoke方法

Object invoke(Object proxy, Method method, Object[] args)  在代理实例上处理方法调用并返回结果。
…参数说明
proxy在其上调用方法的代理实例(也就是目标对象,不是代理对象)
method对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
args包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。

然后通过Proxy.newProxyInstance()获取代理对象

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

Proxy.newProxyInstance("目标对象的类加载器","目标对象的父类接口","halder");
参数说明
ClassLoader loader指定当前目标对象使用类加载器,获取加载器的方法是固定的
Class<?>[] interfaces目标对象实现的接口的类型,使用泛型方式确认类型
InvocationHandler h事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入

要注意的是申明对象的时候必须要用接口

JDK动态代理类

public class UserServiceDynamicProxy implements InvocationHandler{

	//需要代理的目标对象
    //这里设计为可以为任意对象添加事务控制, 所以将目标对象声明为Object
    private Object target;
	
    TimeManager tm;
	TransactionManager tr;
	
	IUserService userService;
	
	public UserServiceDynamicProxy(TimeManager tm, TransactionManager tr,Object target) {
		this.tm = tm;
		this.tr = tr;
		this.target=target;
	}
    
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		tm.start();
		tr.begin();
		
		Object invoke = method.invoke(target, args);
		
		tr.commit();
		tm.end();
		return invoke;
	}

}

获取代理对象

package com.tamakiakoo.test.proxy.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

import org.junit.Test;

import com.tamakiakoo.advices.TimeManager;
import com.tamakiakoo.advices.TransactionManager;
import com.tamakiakoo.service.IUserService;
import com.tamakiakoo.service.impl.UserServiceDynamicProxy;
import com.tamakiakoo.service.impl.UserServiceImpl;

public class DynamicProxyTest {

	@Test
	public void test(){
		TimeManager tm = new TimeManager();
		TransactionManager tr = new TransactionManager();
		IUserService userServiceImpl = new UserServiceImpl();//声明对象必须是接口
		
		UserServiceDynamicProxy proxy = new UserServiceDynamicProxy(tm,tr,userServiceImpl);
		ClassLoader loader = userServiceImpl.getClass().getClassLoader();
		Class<?>[] interfaces=userServiceImpl.getClass().getInterfaces();
		
		//必须是转成接口
		IUserService newProxyInstance = (IUserService) Proxy.newProxyInstance(loader, interfaces, proxy);//声明对象必须是接口
		newProxyInstance.add();
		newProxyInstance.update();
	}
	

	@Test 
	public void test2(){
		TimeManager tm = new TimeManager();
		TransactionManager tr = new TransactionManager();
		IUserService userServiceImpl = new UserServiceImpl();//必须是接口
		
		UserServiceDynamicProxy proxy = new UserServiceDynamicProxy(tm,tr,userServiceImpl);
		
		/*Class[] clzz = new Class[]{IUserService.class};*/
		Class<?> proxyClass = Proxy.getProxyClass(IUserService.class.getClassLoader(), new Class[]{IUserService.class});
		try {
			IUserService userService = (IUserService) proxyClass.getConstructor(new Class[]{InvocationHandler.class}).newInstance(new Object[] {proxy});
			userService.add();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
}

控制台

方法调用之前记录时间
开启事务
UserServiceImpl.add()
提交事务
方法调用之后记录时间


方法调用之前记录时间
开启事务
UserServiceImpl.update()
提交事务
方法调用之后记录时间

如下:
会把目标表对象的所有方法都加强了(使用硬性编码可以解决问题)

package com.tamakiakoo.service.impl;

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

import com.tamakiakoo.advices.TimeManager;
import com.tamakiakoo.advices.TransactionManager;
import com.tamakiakoo.service.IUserService;

public class UserServiceDynamicProxy implements InvocationHandler {

	// 需要代理的目标对象
	// 这里设计为可以为任意对象添加事务控制, 所以将目标对象声明为Object
	private Object target;

	TimeManager tm;
	TransactionManager tr;

	IUserService userService;

	public UserServiceDynamicProxy(TimeManager tm, TransactionManager tr, Object target) {
		this.tm = tm;
		this.tr = tr;
		this.target = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object invoke = null;

		String name = method.getName();
		if ("update".equals(name)) {
			invoke = method.invoke(target, args);
		} else {

			tm.start();
			tr.begin();

			invoke = method.invoke(target, args);

			tr.commit();
			tm.end();
		}

		return invoke;
	}

}

控制台

方法调用之前记录时间
开启事务
UserServiceImpl.add()
提交事务
方法调用之后记录时间
UserServiceImpl.update()
总结:
  • 代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理
  • 会把目标表对象的所有方法都加强了(使用硬性编码可以解决问题)
  • 解决了代理类过多的问题
CGlib动态代理:

利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

  • CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法
  • 这种通过继承类的实现方式,不能代理final修饰的类。
  • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
  • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
  • CGlib采用非常底层的字节码技术,可以为一个类创建子类,并在子类方法拦截技术拦截所有父类方法的调用,并织入横切逻辑

使用步骤

  • 导包:
  • 创建代理类Enhancer enhancer = new Enhancer();
  • 设置父类enhancer.setSuperclass(userService.getClass());
  • 设置拦截器enhancer.setCallback(new ProxyMethodInterceptor(tx, tm));
  • 得到代理类IUserService userServiceProxy = (IUserService) enhancer.create();

代码如下

代理类

代码如下:

package com.tamakiakoo.service.impl;

import java.lang.reflect.Method;

import com.tamakiakoo.advices.TimeManager;
import com.tamakiakoo.advices.TransactionManager;
import com.tamakiakoo.service.IUserService;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class MyCglibProxyMethodInterceptor implements MethodInterceptor {

	TimeManager tm;
	TransactionManager tr;

	public MyCglibProxyMethodInterceptor(TimeManager tm, TransactionManager tr) {
		this.tm = tm;
		this.tr = tr;
	}

	/**
	 * obj:代理类 
	 * method:目标对象中的方法 
	 * args:方法参数 proxy:代理对象
	 */
	@Override
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

		Object invoke = null;

		if (method.getName().equals("update")) {
			invoke = proxy.invokeSuper(obj, args);
		} else {
			// 1.增强开始
			tm.start();
			tr.begin();
			// 2.调用目标方法
			invoke = proxy.invokeSuper(obj, args);
			
			// 3.增强结束
			tr.commit();
			tm.end();
		}
		// 4.返回
		return invoke;
	}
}

package com.tamakiakoo.test.proxy.cglibproxy;

import org.junit.Test;

import com.tamakiakoo.advices.TimeManager;
import com.tamakiakoo.advices.TransactionManager;
import com.tamakiakoo.service.IUserService;
import com.tamakiakoo.service.impl.MyCglibProxyMethodInterceptor;
import com.tamakiakoo.service.impl.UserServiceImpl;

import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;


public class CglibProxyTest {

	@Test
	public void test(){
		//创建目标对象
		IUserService userService = new UserServiceImpl();
		
		//创建代理类
		Enhancer enhancer = new Enhancer();
		//设置父类
		//enhancer.setSuperclass(IUserService.class);//报错
		//enhancer.setSuperclass(userService.getClass());//可以
		enhancer.setSuperclass(UserServiceImpl.class);
		//设置拦截器
		Callback callback = new MyCglibProxyMethodInterceptor(new TimeManager(), new TransactionManager());
		enhancer.setCallback(callback);
		//得到拦截对象
		IUserService userServiceProxy= (IUserService)enhancer.create();
		
		//调用方法
		userServiceProxy.add();
		System.out.println("================");
		userServiceProxy.update();
	}
}

控制台

方法调用之前记录时间
开启事务
UserServiceImpl.add()
提交事务
方法调用之后记录时间
================
UserServiceImpl.update()

总结
  • 目标对象可以没有接口,不能代理final修饰的类
  • 给目标对象中的所有方法都加了增强,只能通过硬编码的方式解决
  • cglib实现动态代理的原理
    • 动态给目标对象创建一个子类,子类中复写父类的方法,然后在子类中进行拦截

AOP实现的总结

  • 静态代理的实现
    • 代理类过多
  • 动态代理实现
    • jdk
      • 目标对象必须要有接口
      • 给目标对象中的所有的方法都加了增强
    • cligb
      • 目标对象不能用final修饰
      • 给目标对象中的所有的方法都加了增强
  • 目标类的所有方法都添加了性能监视横切逻辑,而有时,这并不是我们所期望的,我们可能只希望对业务类中的某些特定方法添加横切逻辑;
  • 我们通过硬编码的方式指定了织入横切逻辑的织入点,即在目标类业务方法的开始和结束前织入代码;
  • 我们手工编写代理实例的创建过程,为不同类创建代理时,需要分别编写相应的创建代码,无法做到通用。

其他

导入源码包之前
参数名字都是arg

导入源码包之后
参数不是arg了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值