介绍
代理模式
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事 后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类 的对象的相关方法,来提供特定的服务。
按照代理的创建时期,代理类可以分为两种:
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成。
在JDK 1.3以后提供了动态代理的技术,允许开发者在运行期创建接口的代理实例。 JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,在并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。
一)静态代理:
1.Count.java
[java] view plain copy
- <span style="font-size:16px;">package net.battier.dao;
- /**
- * 定义一个账户接口
- */
- public interface Count {
- // 查看账户方法
- public void queryCount();
- // 修改账户方法
- public void updateCount();
- } </span>
2.CountImpl.java
[java] view plain copy
- import net.battier.dao.Count;
- /**
- * 委托类(包含业务逻辑)
- */
- public class CountImpl implements Count {
- @Override
- public void queryCount() {
- System.out.println("查看账户方法...");
- }
- @Override
- public void updateCount() {
- System.out.println("修改账户方法...");
- }
- }
3.CountProxy.java
[java] view plain copy
- import net.battier.dao.Count;
- /**
- * 这是一个代理类(增强CountImpl实现类)
- */
- public class CountProxy implements Count {
- private CountImpl 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] view plain copy
- 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.定义一个接口和实现类:
package com.tech.service;
- public interface PersonService {
- public String getPersonName(Integer personId);
- public void save(String name);
- public void update(Integer personId, String name);
- }
- 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>
2.JDK动态代理代理类
[java] view plain copy
- 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;
- }
- }
3.测试类
[java] view plain copy
- 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");
- }
- }
解释:
1. Proxy即动态代理类;
2. Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用;
它有三个参数:
ClassLoader loader ----指定被代理对象的类加载器
Class[] Interfaces ----指定被代理对象所以事项的接口
InvocationHandler h ----指定需要调用的InvocationHandler对象
3. 实现InVocationHandler接口的LogHandler_old对象
这个对象的invoke()方法就是Proxy这个动态代理类所代理的接口类的抽象方法的真实实现;
它有三个参数:
Object proxy -----代理类对象
Method method -----被代理对象的方法(这里不是接口的抽象方法了,是具体的实现类中的方法)
Object[] args -----该方法的参数数组
JDK中具体的动态代理类是怎么产生的呢?
1.产生代理类$Proxy0类
执行了Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
将产生$Proxy0类,它继承Proxy对象,并根据第二个参数,实现了被代理类的所有接口,自然就可以生成接口要实现的所有方法了(这时候会重写hashcode,toString和equals三个方法),但是还没有具体的实现体;
2. 将代理类$Proxy0类加载到JVM中
这时候是根据Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)它的第一个参数----就是被代理类的类加载器,把当前的代理类加载到JVM中
3. 创建代理类$Proxy0类的对象
调用的$Proxy0类的$Proxy0(InvocationHandler)构造函数,生成$Proxy0类的对象
参数就是Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)它的第三个参数
这 个参数就是我们自己实现的InvocationHandler对象,我们知道InvocationHandler对象中组合加入了代理类代理的接口类的实 现类;所以,$Proxy0对象调用所有要实现的接口的方法,都会调用InvocationHandler对象的invoke()方法实现;
4. 生成代理类的class byte
动态代理生成的都是二进制class字节码
动态代理是很多框架和技术的基础, spring 的AOP实现就是基于动态代理实现的。了解动态代理的机制对于理解AOP的底层实现是很有帮助的。
Proxy 类的设计用到代理模式的设计思想,Proxy类对象实现了代理目标的所有接口,并代替目标对象进行实际的操作。但这种替代不是一种简单的替代,这样没有任 何意义,代理的目的是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标对象的方法进行拦截。所以,Proxy应该包括一个方法拦截器,来指示 当拦截到方法调用时作何种处理。InvocationHandler就是拦截器的接口。
InvocationHandler接口也是在java.lang.reflec
Object invoke(Object proxy, Method method, Object[] args)
这个接口有三个参数,其中第二和第三个参数都比较好理解,一个是被拦截的方法,一个是该方法的参数列表。关键是第一个参数。按照doc文档的解析,
proxy - the proxy instance that the method was invoked on
也就是说,proxy应该是一个代理实例,但为什么要传入这个参数呢?
带着这个问题,自己编了个小程序作了一点试验。
///////////////////////////////////////
public interface IAnimal {
void info();
}
////////////////////////////////////
public class Dog implements IAnimal
{
public void info() {
System.out.println("I am a dog!");
}
}
///////////////////////////////////////
import java.lang.reflect.*;
public class ProxyTest {
public static void main(String[] args) throws InterruptedException {
final IAnimal animal = new Dog();
Object proxyObj =Proxy.newProxyInstance(
animal.getClass().getClassLoader(),
animal.getClass().getInterfaces(),
new InvocationHandler()
{
public Object invoke(Object proxy, Method method, Object[] args)
{
try {
System.out.println("被拦截的方法:" + method.getName());
return method.invoke(animal, args);
}
catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
});
if(proxyObj instanceof IAnimal)
{
System.out.println("the proxyObj is an animal!");
}
else
{
System.out.println("the proxyObj isn't an animal!");
}
if(proxyObj instanceof Dog)
{
System.out.println("the proxyObj is a dog!");
}
else
{
System.out.println("the proxyObj isn't a dog!");
}
IAnimal animalProxy = (IAnimal)proxyObj;
animalProxy.info();
animalProxy.hashCode();
System.out.println(animalProxy.getClass().getName().toString());
}
}
程序执行的结果如下:
the proxyObj is an animal!
the proxyObj isn't a dog!
被拦截的方法:info
I am a dog!
被拦截的方法:hashCode
$Proxy0
从结果可以看出以下几点:
1. proxyObj 是一个实现了目标对象接口的对象,而不同于目标对象。也就是说,这种代理机制是面向接口,而不是面向类的。
2. info方法(在接口中)被成功拦截了,hashCode方法也成功被拦截了,但意外的是,getClass方法(继承自Object 类的方法)并没有被拦截!!
3. 应用调试还可以看出Invocation接口中invoke方法的传入的proxy参数确实就是代理对象实例proxyObj
为何getClass()没有被拦截?proxy参数又有何用呢?
先不管,做一个试验看看。既然这个proxy参数就是代理实例对象,它理所当然和proxyObj是一样的,可以调用info等方法。于是我们可以在invoke方法中加上如下一条语句:
((IAnimal)proxy).info();
结果是:
the proxyObj is an animal!
the proxyObj isn't a dog!
被拦截的方法:info
被拦截的方法:info
.......
被拦截的方法:info
被拦截的方法:info
然后就是栈溢出
结果是很明显的,在invoke方法中调用proxy中的方法会再一次引发invoke方法,这就陷入了死循环,最终结果当然是栈溢出的。
可以在invoke方法中调用proxy.getClass(), 程序可以正常运行。但如果调用hashCode()方法同样会导致栈溢出。
通过上面的试验,可以得出一些初步结论,invoke 接口中的proxy参数不能用于调用所实现接口的方法。奇怪的是hashCode()和getClass()方法都是从Object中继承下来的方法,为 什么一个可以另一个不可以呢?带首疑问到doc文档看一下Object中这两个方法,发现getClass()是定义为final的,而 hashCode()不是。难道是这个原因,于是找到一个非final方法,如equals试了一下,真的又会导致栈溢出;找另一个final方法如 wait(),试了一下,invoke又不拦截了。final 难道就是关键之处?
还有一个问题就是proxy有什么用?既然 proxy可以调用getClass()方法,我们就可以得到proxy的Class类象,从而可以获得关于proxy代理实例的所有类信息,如方法列 表,Annotation等,这就为我们提供的一个分析proxy的有力工具,如通过分析Annotation分析方法的声明式事务需求。我想传入 proxy参数应该是这样一个用意吧。
动态代理的主要作用就是:实现了日志和业务的分开,也就是某个类只是要提供了某些业务,比如银行取款业务。
这个类实现了取款业务的同时也需要实现日志功能,如果不用动态代理的话,那么由此一来该类代码里面已经额外地添加了自己不该添加的日志功能能代码。实现了业务和日志的分离也带来很好的独立性
参考 ——spring_21视屏