介绍
代理模式
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
按照代理的创建时期,代理类可以分为两种:
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成。
在JDK 1.3以后提供了动态代理的技术,允许开发者在运行期创建接口的代理实例。 JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,在并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。
一)静态代理:
1.Count.java
- <span style="font-size:16px;">package net.battier.dao;
- /**
- * 定义一个账户接口
- *
- * @author Administrator
- *
- */
- public interface Count {
- // 查看账户方法
- public void queryCount();
- // 修改账户方法
- public void updateCount();
- } </span>
- package net.battier.dao.impl;
- import net.battier.dao.Count;
- /**
- * 委托类(包含业务逻辑)
- *
- * @author Administrator
- *
- */
- public class CountImpl implements Count {
- @Override
- public void queryCount() {
- System.out.println("查看账户方法...");
- }
- @Override
- public void updateCount() {
- System.out.println("修改账户方法...");
- }
- }
- package net.battier.dao.impl;
- import net.battier.dao.Count;
- /**
- * 这是一个代理类(增强CountImpl实现类)
- *
- * @author Administrator
- *
- */
- public class CountProxy implements Count {
- private CountImpl countImpl;
- /**
- * 覆盖默认构造器
- *
- * @param countImpl
- */
- public CountProxy(CountImpl countImpl) {
- this.countImpl = countImpl;
- }
- @Override
- public void queryCount() {
- System.out.println("事务处理之前");
- // 调用委托类的方法;
- countImpl.queryCount();
- System.out.println("事务处理之后");
- }
- @Override
- public void updateCount() {
- System.out.println("事务处理之前");
- // 调用委托类的方法;
- countImpl.updateCount();
- System.out.println("事务处理之后");
- }
- }
4.TestCount.java
- Java代码
- package net.battier.test;
- import net.battier.dao.impl.CountImpl;
- import net.battier.dao.impl.CountProxy;
- /**
- *测试Count类
- *
- * @author Administrator
- *
- */
- public class TestCount {
- public static void main(String[] args) {
- CountImpl countImpl = new CountImpl();
- CountProxy countProxy = new CountProxy(countImpl);
- countProxy.updateCount();
- countProxy.queryCount();
- }
- }
二)动态代理
1.定义一个接口和实现类:
- <span style="font-weight: normal;"><span style="font-size:16px;">package com.tech.service;
- public interface PersonService {
- public String getPersonName(Integer personId);
- public void save(String name);
- public void update(Integer personId, String name);
- }
- package com.tech.service.impl;
- import com.tech.service.PersonService;
- public class PersonServiceBean implements PersonService {
- public String user = null;
- public PersonServiceBean(){};
- public PersonServiceBean(String user){
- this.user = user;
- }
- @Override
- public String getPersonName(Integer personId) {
- // TODO Auto-generated method stub
- System.out.println("这是find方法");
- return this.user;
- }
- @Override
- public void save(String name) {
- // TODO Auto-generated method stub
- System.out.println("这是save方法");
- }
- @Override
- public void update(Integer personId, String name) {
- // TODO Auto-generated method stub
- System.out.println("这是update方法");
- }
- public String getUser() {
- return user;
- }
- public void setUser(String user) {
- this.user = user;
- }
- }</span></span>
- package com.tech.jdkproxy;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- import com.tech.service.impl.PersonServiceBean;
- /**
- *
- * 切面
- * @author ch
- *
- */
- public class JDKProxyFactory implements InvocationHandler{
- private Object proxyObject; //目标对象
- /**
- * 绑定委托对象并返回一个代理类
- * @param proxyObject
- * @return
- */
- public Object createProxyInstance(Object proxyObject) {
- this.proxyObject = proxyObject;
- //生成代理类的字节码加载器
- ClassLoader classLoader = proxyObject.getClass().getClassLoader();
- //需要代理的接口,被代理类实现的多个接口都必须在这里定义 (这是一个缺陷,cglib弥补了这一缺陷)
- Class<?>[] proxyInterface = proxyObject.getClass().getInterfaces();//new Class[]{};
- //织入器,织入代码并生成代理类
- return Proxy.newProxyInstance(classLoader,
- proxyInterface, this);
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- PersonServiceBean bean = (PersonServiceBean)this.proxyObject;
- Object result = null;
- //控制哪些用户执行切入逻辑
- if(bean.getUser() != null) {
- //执行原有逻辑
- result = method.invoke(this.proxyObject, args);
- }
- return result;
- }
- }
- package com.tech.junit;
- import org.junit.BeforeClass;
- import org.junit.Test;
- import com.tech.jdkproxy.JDKProxyFactory;
- import com.tech.service.PersonService;
- import com.tech.service.impl.PersonServiceBean;
- public class PersonTest {
- @BeforeClass
- public static void setUpBeforeClass() throws Exception {
- }
- @Test
- public void Test() {
- JDKProxyFactory factory = new JDKProxyFactory();
- PersonService bean = (PersonService) factory
- .createProxyInstance(new PersonServiceBean("lucy"));
- //用户为lucy,有权限
- bean.save("abc");
- PersonService bean2 = (PersonService) factory
- .createProxyInstance(new PersonServiceBean());
- //用户为null,没有权限,不输出
- bean2.save("abc");
- }
- }
一、原理
代理为控制要访问的目标对象提供了一种途径。当访问对象时,它引入了一个间接的层。JDK自从1.3版本开始,就引入了动态代理,并且经常被用来动态地创建代理。JDK的动态代理用起来非常简单,当它有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的继承的类,该怎么办?现在我们可以使用CGLIB包。
二、什么是cglib
CGLIB是一个强大的高性能的代码生成包。
1>它广泛的被许多AOP的框架使用,例如:Spring AOP和dynaop,为他们提供方法的interception(拦截);
2>hibernate使用CGLIB来代理单端single-ended(多对一和一对一)关联(对集合的延迟抓取,是采用其他机制实现的);
3>EasyMock和jMock是通过使用模仿(moke)对象来测试java代码的包。
它们都通过使用CGLIB来为那些没有接口的类创建模仿(moke)对象。
三、底层
CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM(Java字节码操控框架),来转换字节码并生成新的类。除了CGLIB包,脚本语言例如 Groovy和BeanShell,也是使用ASM来生成java的字节码。当不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。所以cglib包要依赖于asm包,需要一起导入。下图为cglib与一些框架和语言的关系(CGLIB Library and ASM Bytecode Framework)
Spring AOP和Hibernate同时使用JDK的动态代理和CGLIB包。Spring AOP,如果不强制使用CGLIB包,默认情况是使用JDK的动态代理来代理接口。
四、实例场景模拟
1. 我们创建一个对Table操作的DAO类,提供了CRUD方法。
BookServiceBean.java
- package com.tech.cglibx;
- public class BookServiceBean {
- public void create(){
- System.out.println("create() is running !");
- }
- public void query(){
- System.out.println("query() is running !");
- }
- public void update(){
- System.out.println("update() is running !");
- }
- public void delete(){
- System.out.println("delete() is running !");
- }
- }
OK,它就是一个javaBean,提供了CRUD方法的javaBean。
下面我们创建一个DAO工厂,用来生成DAO实例。
- package com.tech.cglibx;
- public class BookServiceFactory {
- private static BookServiceBean service = new BookServiceBean();
- private BookServiceFactory() {
- }
- public static BookServiceBean getInstance() {
- return service;
- }
- }
接下来我们创建客户端,用来调用CRUD方法。
- public class Client {
- public static void main(String[] args) {
- BookServiceBean service = BookServiceFactory.getInstance();
- doMethod(service);
- }
- public static void doMethod(BookServiceBean service){
- service.create();
- service.update();
- service.query();
- service.delete();
- }
- }
OK,完成了,CRUD方法完全被调用了。
当然这里并没有CGlib的任何内容。问题不会这么简单的就结束,新的需求来临了。
2. one day,Boss告诉我们这些方法不能开放给用户,只有“boss”才有权使用。怎么办,难道我们要在每个方法上面进行判断吗?好像这么做也太那啥了吧?对了,Proxy可能是最好的解决办法。jdk的代理就可以解决了。 好了我们来动手改造吧。等等jdk的代理需要实现接口,这样, 我们的dao类需要改变了。既然不想改动dao又要使用代理,我们这就请出CGlib。
我们只需新增一个权限验证的方法拦截器。
- package com.tech.cglibx;
- import java.lang.reflect.Method;
- import net.sf.cglib.proxy.Enhancer;
- import net.sf.cglib.proxy.MethodInterceptor;
- import net.sf.cglib.proxy.MethodProxy;
- import org.apache.log4j.Logger;
- public class MyCglibProxy implements MethodInterceptor{
- private Logger log=Logger.getLogger(MyCglibProxy.class);
- public Enhancer enhancer = new Enhancer();
- private String name;
- public MyCglibProxy(String name) {
- this.name = name ;
- }
- /**
- * 根据class对象创建该对象的代理对象
- * 1、设置父类;2、设置回调
- * 本质:动态创建了一个class对象的子类
- *
- * @param cls
- * @return
- */
- public Object getDaoBean(Class cls) {
- enhancer.setSuperclass(cls);
- enhancer.setCallback(this);
- return enhancer.create();
- }
- @Override
- public Object intercept(Object object, Method method, Object[] args,
- MethodProxy methodProxy) throws Throwable {
- log.info("调用的方法是:" + method.getName());
- //用户进行判断
- if(!"张三".equals(name)){
- System.out.println("你没有权限!");
- return null;
- }
- Object result = methodProxy.invokeSuper(object, args);
- return result;
- }
- }
当然不能忘了对我们的dao工厂进行修改,我们提供一个使用代理的实例生成方法。上面的类中已经提供了一个通用的获取代理实例的方法,没有特殊需求(如下3)的方式可以使用上面的方式获取代理对象。
- public static BookServiceBean getProxyInstance(MyCglibProxy myProxy){
- Enhancer en = new Enhancer();
- //进行代理
- en.setSuperclass(BookServiceBean.class);
- en.setCallback(myProxy);
- //生成代理实例
- return (BookServiceBean)en.create();
- } <span style="font-family: Arial, Verdana, sans-serif; white-space: normal; "> </span>
我们这就可以看看客户端的实现了。添加了两个方法用来验证不同用户的权限
- BookServiceBean service = BookServiceFactory.getProxyInstance(new MyCglibProxy("boss"));
- service.create();
- BookServiceBean service2 = BookServiceFactory.getProxyInstance(new MyCglibProxy("john"));
- service2.create();
OK,"boss"的正常执行,"john"的没有执行。
看到了吗?简单的aop就这样实现了
难道就这样结束了么?
3.grd Boss又来训话了,不行不行,现在除了"boss"其他人都用不了了,现在不可以这样。必须使用开放查询功能。
哈哈,现在可难不倒我们了,因为我们使用了CGlib。当然最简单的方式是去修改我们的方法拦截器,不过这样会使逻辑变得复杂,且不利于维护。
还好CGlib给我们提供了方法过滤器(CallbackFilter),CallbackFilte可以明确表明,被代理的类中不同的方法,被哪个拦截器所拦截。
下面我们就来做个过滤器用来过滤query方法。
- package com.tech.cglibx;
- import java.lang.reflect.Method;
- import net.sf.cglib.proxy.CallbackFilter;
- public class MyProxyFilter implements CallbackFilter {
- @Override
- public int accept(Method arg0) {
- if(!"query".equalsIgnoreCase(arg0.getName()))
- return 0;
- return 1;
- }
- }
我们在工场中新增一个使用了过滤器的实例生成方法。
- public static BookServiceBean getAuthInstanceByFilter(MyCglibProxy myProxy){
- Enhancer en = new Enhancer();
- en.setSuperclass(BookServiceBean.class);
- en.setCallbacks(new Callback[]{myProxy,NoOp.INSTANCE});
- en.setCallbackFilter(new MyProxyFilter());
- return (BookServiceBean)en.create();
- }
setCallbacks中定义了所使用的拦截器,其中NoOp.INSTANCE是CGlib所提供的实际是一个没有任何操作的拦截器,
他们是有序的,一定要和CallbackFilter里面的顺序一致。上面return返回(0/1)的就是返回的顺序。也就是说如果调用query方法就使用NoOp.INSTANCE进行拦截。
现在看一下客户端代码。
- BookServiceBean service = BookServiceFactory.getProxyInstanceByFilter(new MyCglibProxy("jhon"));
- service.create();
- BookServiceBean service2 = BookServiceFactory.getProxyInstanceByFilter(new MyCglibProxy("jhon"));
- service2.query();
ok,现在"李四"也可以使用query方法了,其他方法仍然没有权限。
当然这个代理的实现没有任何侵入性,无需强制让dao去实现接口。
jdk的动态代理是基于接口的,必须实现了某一个或多个任意接口才可以被代理,并且只有这些接口中的方法会被代理。看了一下jdk带的动态代理api,发现没有例子实在是很容易走弯路,所以这里写一个加法器的简单示例。
1
2
3
4
5
6
7
|
// Adder.java
package
test;
public
interface
Adder {
int
add(
int
a,
int
b);
}
|
1
2
3
4
5
6
7
8
9
10
|
// AdderImpl.java
package
test;
public
class
AdderImpl
implements
Adder {
@Override
public
int
add(
int
a,
int
b) {
return
a + b;
}
}
|
1
|
现在我们有一个接口Adder以及一个实现了这个接口的类AdderImpl,写一个Test测试一下。
|
1
2
3
4
5
6
7
8
9
10
11
|
// Test.java
package
test;
public
class
Test {
public
static
void
main(String[] args)
throws
Exception {
Adder calc =
new
AdderImpl();
int
result = calc.add(
1
,
2
);
System.out.println(
"The result is "
+ result);
}
}
|
很显然,控制台会输出:
1
|
The result is 3
|
然而现在我们需要在加法器使用之后记录一些信息以便测试,但AdderImpl的源代码不能更改,就像这样:
1
2
|
Proxy: invoke add() at 2009-12-16 17:18:06
The result is 3
|
动态代理可以很轻易地解决这个问题。我们只需要写一个自定义的调用处理器(实现接口java.lang.reflect.InvokationHandler),然后使用类java.lang.reflect.Proxy中的静态方法生成Adder的代理类,并把这个代理类当做原先的Adder使用就可以。
第一步:实现InvokationHandler,定义调用方法时应该执行的动作。
自定义一个类MyHandler实现接口java.lang.reflect.InvokationHandler,需要重写的方法只有一个:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// AdderHandler.java
package
test;
import
java.lang.reflect.InvocationHandler;
import
java.lang.reflect.Method;
class
AdderHandler
implements
InvocationHandler {
/**
* @param proxy 接下来Proxy要为你生成的代理类的实例,注意,并不是我们new出来的AdderImpl
* @param method 调用的方法的Method实例。如果调用了add(),那么就是add()的Method实例
* @param args 调用方法时传入的参数。如果调用了add(),那么就是传入add()的参数
* @return 使用代理后将作为调用方法后的返回值。如果调用了add(),那么就是调用add()后的返回值
*/
@Override
public
Object invoke(Object proxy, Method method, Object[] args)
throws
Throwable {
// ...
}
}
|
使用代理后,这个方法将取代指定的所有接口中的所有方法的执行。在本例中,调用adder.add()方法时,实际执行的将是invoke()。所以为了有正确的结果,我们需要在invoke()方法中手动调用add()方法。再看看invoke()方法的参数,正好符合反射需要的所有条件,所以这时我们马上会想到这样做:
1
|
Object returnValue = method.invoke(proxy, args);
|
如果你真的这么做了,那么恭喜你,你掉入了jdk为你精心准备的圈套。proxy是jdk为你生成的代理类的实例,实际上就是使用代理之后adder引用所指向的对象。由于我们调用了adder.add(1, 2),才使得invoke()执行,如果在invoke()中使用method.invoke(proxy, args),那么又会使invoke()执行。没错,这是个死循环。然而,invoke()方法没有别的参数让我们使用了。最简单的解决方法就是,为MyHandler加入一个属性指向实际被代理的对象。所以,因为jdk的冷幽默,我们需要在自定义的Handler中加入以下这么一段:
1
2
3
4
5
6
|
// 被代理的对象
private
Object target;
public
AdderHandler(Object target) {
this
.target = target;
}
|
喜欢的话还可以加上getter/setter。接着,invoke()就可以这么用了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
// AdderHandler.java
package
test;
import
java.lang.reflect.InvocationHandler;
import
java.lang.reflect.Method;
import
java.util.Date;
class
AdderHandler
implements
InvocationHandler {
// 被代理的对象
private
Object target;
public
AdderHandler(Object target) {
this
.target = target;
}
@Override
public
Object invoke(Object proxy, Method method, Object[] args)
throws
Throwable {
// 调用被代理对象的方法并得到返回值
Object returnValue = method.invoke(target, args);
// 调用方法前后都可以加入一些其他的逻辑
System.out.println(
"Proxy: invoke "
+ method.getName() +
"() at "
+
new
Date().toLocaleString());
// 可以返回任何想要返回的值
return
returnValue;
}
}
|
第二步:使用jdk提供的java.lang.reflect.Proxy生成代理对象。
使用newProxyInstance()方法就可以生成一个代理对象。把这个方法的签名拿出来:
1
2
3
4
5
6
7
8
9
10
|
/**
* @param loader 类加载器,用于加载生成的代理类。
* @param interfaces 需要代理的接口。这些接口的所有方法都会被代理。
* @param h 第一步中我们建立的Handler类的实例。
* @return 代理对象,实现了所有要代理的接口。
*/
public
static
Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws
IllegalArgumentException
|
这个方法会做这样一件事情,他将把你要代理的全部接口用一个由代码动态生成的类类实现,所有的接口中的方法都重写为调用InvocationHandler.invoke()方法。这个类的代码类似于这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// 模拟Proxy生成的代理类,这个类是动态生成的,并没有对应的.java文件。
class
AdderProxy
extends
Proxy
implements
Adder {
protected
AdderProxy(InvocationHandler h) {
super
(h);
}
@Override
public
int
add(
int
a,
int
b) {
try
{
Method m = Adder.
class
.getMethod(
"add"
,
new
Class[] {
int
.
class
,
int
.
class
});
Object[] args = {a, b};
return
(Integer) h.invoke(
this
, m, args);
}
catch
(Throwable e) {
throw
new
RuntimeException(e);
}
}
}
|
据api说,所有生成的代理类都是Proxy的子类。当然,生成的这个类的代码你是看不到的,而且Proxy里面也是调用sun.XXX包的api生成;一般情况下应该是直接生成了字节码。然后,使用你提供的ClassLoader将这个类加载并实例化一个对象作为代理返回。
看明白这个方法后,我们来改造一下main()方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
// Test.java
package
test;
import
java.lang.reflect.InvocationHandler;
import
java.lang.reflect.Proxy;
public
class
Test {
public
static
void
main(String[] args)
throws
Exception {
Adder calc =
new
AdderImpl();
// 类加载器
ClassLoader loader = Test.
class
.getClassLoader();
// 需要代理的接口
Class[] interfaces = {Adder.
class
};
// 方法调用处理器,保存实际的AdderImpl的引用
InvocationHandler h =
new
AdderHandler(calc);
// 为calc加上代理
calc = (Adder) Proxy.newProxyInstance(loader, interfaces, h);
/* 什么?你说还有别的需求? */
// 另一个处理器,保存前处理器的引用
// InvocationHandler h2 = new XXOOHandler(h);
// 再加代理
// calc = (Adder) Proxy.newProxyInstance(loader, interfaces, h2);
int
result = calc.add(
1
,
2
);
System.out.println(
"The result is "
+ result);
}
}
|
输出结果会是什么呢?
1
2
|
Proxy: invoke add() at 2009-12-16 18:21:33
The result is 3
|