您的位置:首页 > 编程语言 > Java开发

java动态代理详解

2015-08-03 15:55 155 查看
Java动态代理详解
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

按照代理的创建时期,代理类可以分为两种。

静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。

动态代理:在程序运行时,运用反射机制动态创建而成。
首先了解静态代理:
1、Count.java

1. package net.battier.dao;
2.
3. /**
4. * 定义一个账户接口
5. *
6. * @author Administrator
7. *
8. */
9. public interface Count {
10. // 查看账户方法
11. public void queryCount();
12.
13. // 修改账户方法
14. public void updateCount();
15.
16.}

2、CountImpl.java

1. package net.battier.dao.impl;
2.
3. import net.battier.dao.Count;
4.
5. /**
6. * 委托类(包含业务逻辑)
7. *
8. * @author Administrator
9. *
10. */
11.public class CountImpl implements Count {
12.
13. @Override
14. public void queryCount() {
15. System.out.println("查看账户方法...");
16.
17. }
18.
19. @Override
20. public void updateCount() {
21. System.out.println("修改账户方法...");
22.
23. }
24.
25.}
26.
27.、CountProxy.java
28.package net.battier.dao.impl;
29.
30.import net.battier.dao.Count;
31.
32./**
33. * 这是一个代理类(增强CountImpl实现类)
34. *
35. * @author Administrator
36. *
37. */
38.public class CountProxy implements Count {
39. private CountImpl countImpl;
40.
41. /**
42. * 覆盖默认构造器
43. *
44. * @param countImpl
45. */
46. public CountProxy(CountImpl countImpl) {
47. this.countImpl = countImpl;
48. }
49.
50. @Override
51. public void queryCount() {
52. System.out.println("事务处理之前");
53. // 调用委托类的方法;
54. countImpl.queryCount();
55. System.out.println("事务处理之后");
56. }
57.
58. @Override
59. public void updateCount() {
60. System.out.println("事务处理之前");
61. // 调用委托类的方法;
62. countImpl.updateCount();
63. System.out.println("事务处理之后");
64.
65. }
66.
67.}

3、TestCount.java

1. package net.battier.test;
2.
3. import net.battier.dao.impl.CountImpl;
4. import net.battier.dao.impl.CountProxy;
5.
6. /**
7. *测试Count类
8. *
9. * @author Administrator
10. *
11. */
12.public class TestCount {
13. public static void main(String[] args) {
14. CountImpl countImpl = new CountImpl();
15. CountProxy countProxy = new CountProxy(countImpl);
16. countProxy.updateCount();
17. countProxy.queryCount();
18.
19. }
20.}

观察代码可以发现每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,则此时肯定是重复代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理完成。

在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的我们的功能,我们更需要学习的是其底层是怎么样的一个原理,而AOP的原理就是java的动态代理机制,所以本篇随笔就是对java的动态机制进行一个回顾。
在java的动态代理机制中,有两个重要的类或接口,一个是InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。首先我们先来看看java的API帮助文档是怎么样对这两个类进行描述的:
InvocationHandler:
InvocationHandleris the interface implemented by the invocation handler of a proxy instance.

Eachproxy instance has an associated invocation handler. When a method is invokedon a proxy instance, the method invocation is encoded and dispatched to theinvoke method of its invocation handler.
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:
Objectinvoke(Object proxy, Method method, Object[] args)
throws Throwable
我们看到这个方法一共接受三个参数,那么这三个参数分别代表什么呢?
Objectinvoke(Object proxy, Method method, Object[] args)
throws Throwable

proxy:  指代我们所代理的那个真实对象
method:  指代的是我们所要调用真实对象的某个方法的Method对象
args:  指代的是调用真实对象某个方法时接受的参数
如果不是很明白,等下通过一个实例会对这几个参数进行更深的讲解。
接下来我们来看看Proxy这个类:
Proxyprovides static methods
for creating dynamic proxy classes and instances, and it is also thesuperclass of all dynamic proxy classes created by those methods.

Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:
public static Object
newProxyInstance(ClassLoader loader, Class<?>[]interfaces, InvocationHandler h)
throws IllegalArgumentException
Returnsan instance of a proxy class
for the specified interfaces that dispatchesmethod invocations to the specified invocation handler.
这个方法的作用就是得到一个指定接口的动态的代理对象,将方法调用指派到指定的处理程序,也就是invoke函数。
其接收三个参数,我们来看看这三个参数所代表的含义:

public static Object newProxyInstance(ClassLoaderloader, Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException

loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载

interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了

h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

好了,在介绍完这两个接口(类)以后,我们来通过一个实例来看看我们的动态代理模式是什么样的:
首先我们定义了一个Subject类型的接口,为其声明了两个方法:
public interface Subject
{
public
void rent();

public
void hello(String str);
}
接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,RealSubject类:

public class RealSubject
implements Subject
{
@Override
public
void rent()
{
System.out.println("I want to rentmy house");
}

@Override
public
void hello(String str)
{
System.out.println("hello: "+ str);
}
}

下一步,我们就要定义一个动态代理类了,前面说个,每一个动态代理类都必须要实现 InvocationHandler这个接口,因此我们这个动态代理类也不例外:

public class DynamicProxy
implements InvocationHandler
{
// 这个就是我们要代理的真实对象
private Object subject;

// 构造方法,给我们要代理的真实对象赋初值
public DynamicProxy(Object subject)
{
this.subject = subject;
}

@Override
public Object invoke(Object object, Methodmethod, Object[] args)
throws Throwable
{
//  在代理真实对象前我们可以添加一些自己的操作
System.out.println("before renthouse");

System.out.println("Method:"+ method);

// 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
method.invoke(subject, args);

//  在代理真实对象后我们也可以添加一些自己的操作
System.out.println("after renthouse");

return
null;
}

}

最后,来看看我们的Client类:

public class Client
{
public
static void main(String[] args)
{
// 我们要代理的真实对象
Subject realSubject = new RealSubject();

// 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler handler = new DynamicProxy(realSubject);

/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader()
,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler, 我们这里将这个代理对象关联到了上方的InvocationHandler 这个对象上
*/
Subject subject =(Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
.getClass().getInterfaces(),handler);

System.out.println(subject.getClass().getName());
subject.rent();
subject.hello("world");
}
}

我们先来看看控制台的输出:

$Proxy0


beforerent house
Method:public
abstract void com.xiaoluo.dynamicproxy.Subject.rent()

I wantto rent my house
afterrent house

beforerent house
Method:public
abstract voidcom.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)

hello:world
afterrent house

我们首先来看看 $Proxy0 这东西,我们看到,这个东西是由 System.out.println(subject.getClass().getName());这条语句打印出来的,那么为什么我们返回的这个代理对象的类名是这样的呢?
Subjectsubject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(),realSubject
.getClass().getInterfaces(),handler);
可能我以为返回的这个代理对象会是Subject类型的对象,或者是InvocationHandler的对象,结果却不是,首先我们解释一下为什么我们这里可以将其转化为Subject类型的对象?原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。
同时我们一定要记住,通过 Proxy.newProxyInstance创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号
接着我们来看看这两句
subject.rent();

subject.hello("world");
这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的invoke方法去执行,而我们的这个 handler 对象又接受了一个 RealSubject类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的invoke方法去执行:

public Object invoke(Object object, Methodmethod, Object[] args)
throws Throwable
{
//  在代理真实对象前我们可以添加一些自己的操作
System.out.println("before renthouse");

System.out.println("Method:"+ method);

// 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
method.invoke(subject, args);

//  在代理真实对象后我们也可以添加一些自己的操作
System.out.println("after renthouse");

return
null;
}

我们看到,在真正通过代理对象来调用真实对象的方法的时候,我们可以在该方法前后添加自己的一些操作,同时我们看到我们的这个 method 对象是这样的:
public abstract
void com.xiaoluo.dynamicproxy.Subject.rent()

public abstract
voidcom.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)
正好就是我们的Subject接口中的两个方法,这也就证明了当我通过代理对象来调用方法的时候,起实际就是委托由其关联到的 handler 对象的invoke方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的。
这就是我们的java动态代理机制

另一种
动态代理示例:

1、BookFacade.java

1. package net.battier.dao;
2.
3. public interface BookFacade {
4. public void addBook();
5. }

2、BookFacadeImpl.java

1. package net.battier.dao.impl;
2.
3. import net.battier.dao.BookFacade;
4.
5. public class BookFacadeImpl implements BookFacade {
6.
7. @Override
8. public void addBook() {
9. System.out.println("增加图书方法。。。");
10. }
11.
12.}
13.
14.、BookFacadeProxy.java
15.
16.package net.battier.proxy;
17.
18.import java.lang.reflect.InvocationHandler;
19.import java.lang.reflect.Method;
20.import java.lang.reflect.Proxy;
21.
22./**
23. * JDK动态代理代理类
24. *
25. * @author student
26. *
27. */
28.public class BookFacadeProxy implements InvocationHandler {
29. private Object target;
30. /**
31. * 绑定委托对象并返回一个代理类
32. * @param target
33. * @return
34. */
35. public Object bind(Object target) {
36. this.target = target;
37. //取得代理对象
38. return Proxy.newProxyInstance(target.getClass().getClassLoader(),
39. target.getClass().getInterfaces(), this); //要绑定接口(这是一个缺陷,cglib弥补了这一缺陷)
40. }
41.
42. @Override
43. /**
44. * 调用方法
45. */
46. public Object invoke(Object proxy, Method method, Object[] args)
47. throws Throwable {
48. Object result=null;
49. System.out.println("事物开始");
50. //执行方法
51. result=method.invoke(target, args);
52. System.out.println("事物结束");
53. return result;
54. }
55.
56.}

3、TestProxy.java

1. package net.battier.test;
2.
3. import net.battier.dao.BookFacade;
4. import net.battier.dao.impl.BookFacadeImpl;
5. import net.battier.proxy.BookFacadeProxy;
6.
7. public class TestProxy {
8.
9. public static void main(String[] args) {
10. BookFacadeProxy proxy = new BookFacadeProxy();
11. BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl());
12. bookProxy.addBook();
13. }
14.
15.}

但是,JDK的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用JDK代理,这就要使用cglib动态代理了。
Cglib动态代理

JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

示例

1、BookFacadeCglib.java

1. package net.battier.dao;
2.
3. public interface BookFacade {
4. public void addBook();
5. }

2、BookCadeImpl1.java

1. package net.battier.dao.impl;
2.
3. /**
4. * 这个是没有实现接口的实现类
5. *
6. * @author student
7. *
8. */
9. public class BookFacadeImpl1 {
10. public void addBook() {
11. System.out.println("增加图书的普通方法...");
12. }
13.}

3、BookFacadeProxy.java

1. package net.battier.proxy;
2.
3. import java.lang.reflect.Method;
4.
5. import net.sf.cglib.proxy.Enhancer;
6. import net.sf.cglib.proxy.MethodInterceptor;
7. import net.sf.cglib.proxy.MethodProxy;
8.
9. /**
10. * 使用cglib动态代理
11. *
12. * @author student
13. *
14. */
15.public class BookFacadeCglib implements MethodInterceptor {
16. private Object target;
17.
18. /**
19. * 创建代理对象
20. *
21. * @param target
22. * @return
23. */
24. public Object getInstance(Object target) {
25. this.target = target;
26. Enhancer enhancer = new Enhancer();
27. enhancer.setSuperclass(this.target.getClass());
28. // 回调方法
29. enhancer.setCallback(this);
30. // 创建代理对象
31. return enhancer.create();
32. }
33.
34. @Override
35. // 回调方法
36. public Object intercept(Object obj, Method method, Object[] args,
37. MethodProxy proxy) throws Throwable {
38. System.out.println("事物开始");
39. proxy.invokeSuper(obj, args);
40. System.out.println("事物结束");
41. return null;
42.
43.
44. }
45.
46.}

4、TestCglib.java

1. package net.battier.test;
2.
3. import net.battier.dao.impl.BookFacadeImpl1;
4. import net.battier.proxy.BookFacadeCglib;
5.
6. public class TestCglib {
7.
8. public static void main(String[] args) {
9. BookFacadeCglib cglib=new BookFacadeCglib();
10. BookFacadeImpl1 bookCglib=(BookFacadeImpl1)cglib.getInstance(new BookFacadeImpl1());
11. bookCglib.addBook();
12. }
13.}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: