0.前言
在教材的学习中我们都会用到数据的增删改查。现在的需求是:我们在添加一个对象的时候打印“增加了什么什么对象”,在删除的时候打印“删除了什么什么对象”。如果我们在每个方法中加这段打印语句,这个工作量就会很大,而且将会出现大量的重复代码。动态代理 将会很好地解决这个问题。在这之前我们先需要说一下代码设计模式——代理模式。
1.代理模式
代理模式是一种软件设计模式。当对象A不想直接访问对象B的时候,这时就需要一个代理,充当中介,这就是代理 。比如一个男生想给一个女孩送一束玫瑰花,但是这个男生不好意思,所有找了另外一个人----老王帮他送花,这个老王就是一个代理。先编写一个接口,让老王和男生都来实现。老王依赖于男生,男生对女生的操作转化为老王对女生的操作。
//先编写一个接口
public interface BoyInterface {
//送花
void sendFlowers(Girl girl);
}
//男生实现这个接口
public class Boy implements BoyInterface {
@Override
public void sendFlowers(Girl girl) {
System.out.println("送花给:"+ girl.getName());
}
}
//代理老王也实现这个接口
public class Proxy implements BoyInterface {
Boy boy;
public Proxy(Boy boy){
this.boy = boy;
}
@Override
public void sendFlowers(Girl girl) {
System.out.println("我是代理送花的老王");
boy.sendFlowers(girl);
}
}
//女生类
public class Girl {
String name;
public Girl(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
//主程序
public class TestMain {
public static void main(String[] args) {
Proxy proxy = new Proxy(new Boy());
Girl girl = new Girl("漂亮的女孩");
proxy.sendFlowers(girl);
}
}
很容易看懂这段代码,他的UML是这样的
在《 大话设计模式 》中给出的定义是:为对象提供一种代理以控制对这个对象的访问接下来分析一个这个图。
- 首先是接口BoyInterface ,它定义了Boy和Proxy的公用方法,这样在任何使用Boy的地方也可以使用Proxy。
- 接下来是Proxy,保留了一个Boy的引用使得代理类可以直接操作Boy。因为和Boy实现了相同的接口所以Proxy可以代理Boy的方法了。
- Boy:这个是真实的实体类。
所以要实现代理最基本的一点就是代理类的实体类要实现相同的接口,并且代理类中要保存一个实体类的引用。
2.动态代理
JDK提供了动态代理机制,只需要掌握Proxy类的使用就可以实现所有类的代理。
2.1先来看代码
public class TestMain {
public static void main(String[] args) {
final BoyInterface boy = new Boy();//被代理对象
BoyInterface proxy = (BoyInterface) Proxy.newProxyInstance(boy.getClass().getClassLoader(),
new Class[]{BoyInterface.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是代理阿璐4r");
method.invoke(boy,args);
return null;
}
});
proxy.sendFlowers(new Girl("诗宇4r"));
}
}
2.2详细解析
首先看一下Proxy.newProxyInstance()在jdk文档中的描述:
该方法主要有三的参数:ClassLoader(类加载器),Class,InvocationHandler。
类加载器:它是用来把类装进JVM的。这个参数用来指明由Proxy产生的代理对象是由哪个类加载器来加载的,这里我们需要写和被代理对象的加载相同。
Class[ ]:相当于需要受理的接口的数组。我们的案例是BoyInterface接口。(所以前面说到我们写动态代理的前提是需要写一个接口)。也必须写由动态代理产生的对象实现的接口的Class数组。当然也可以写成boy.getClss().getInterfaces()
InvocationHandler:他是一个接口需要使用匿名类的方式来实现。在这里面就可以写代理需要做的额外操作。需要实现invoke()方法。这里面也有三个参数。proxy指的是正在被代理的对象,一般不使用,在使用的他的方法是会继续调用proxy的方法,这样就出现了死循环。Method表示正在执行的方法,args是方法的参数。
我们在之前写被代理对象的时候写成了final:因为代理Proxy用到了被代理,如果不使用final,我们从一个方法中获取一个代理,然后方法结束,被代理对象就会被回收。
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是代理阿璐4r");
method.invoke(boy,args);
return null;
}
}
3.完成增删改查的日志功能
首尾呼应一下,在前言中我们提到的增删改查,在操作中我们打印相应的提示语句。
首先我们编写一个接口声明增加和删除的方法:
public interface Service {
void insert(Student student);
void delete(int id);
}
然后编写实现类,这里写一个HashMap来模拟一下数据库。默认添加一条数据。
public class ServiceImpl implements Service {
HashMap<Integer,Student> hashMap = new HashMap<>();
public ServiceImpl(){
hashMap.put(1,new Student(1,"阿璐4r",21));
}
@Override
public void insert(Student student) {
hashMap.put(student.id,student);
}
@Override
public void delete(int id) {
hashMap.remove(id);
}
@Override
public String toString() {
return "ServiceImpl{" +
"hashMap=" + hashMap +
'}';
}
}
最后是动态代理的使用
public class TestMain {
public static void main(String[] args) {
final Service service = new ServiceImpl();
Service proxy = (Service) Proxy.newProxyInstance(service.getClass().getClassLoader(),
service.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("insert")){
Student student = (Student) args[0];
System.out.println("增加了Student对象:"+student.getName());
}else if(method.getName().equals("delete")){
Integer integer = (Integer) args[0];
System.out.println("删除了id为:"+integer+"的对象");
}
method.invoke(service,args);
return null;
}
});
proxy.insert(new Student(2,"诗宇4r",21));
proxy.delete(1);
System.out.println("HashMap中剩余的数据");
System.out.println(service.toString());
}
}
最终结果
4.结束
动态代理是Spring的基石,把自己学习的笔记和总结用博客的方式记下来。
不对之处,望多指教!