代理模式
代理(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修饰
- 给目标对象中的所有的方法都加了增强
- jdk
- 目标类的所有方法都添加了性能监视横切逻辑,而有时,这并不是我们所期望的,我们可能只希望对业务类中的某些特定方法添加横切逻辑;
- 我们通过硬编码的方式指定了织入横切逻辑的织入点,即在目标类业务方法的开始和结束前织入代码;
- 我们手工编写代理实例的创建过程,为不同类创建代理时,需要分别编写相应的创建代码,无法做到通用。
其他
导入源码包之前
参数名字都是arg
导入源码包之后
参数不是arg了