平时自己会在写代码的过程中尝试使用一些常用的设计模式,对于代码使用设计模式来说,其实并无强制性的要求,如果强中注入一些设计模式,可能会使得代码变了味道,所以在使用的涉及模式的时候,
最好还是能用到最适合的场景中,下面结合项目中的东西,总结一下这些设计模式的使用原理以及场景:
设计模式一:单例模式:
作为对象的创建模式,单例模式确保其某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类。单例模式有以下特点:
1、单例类只能有一个实例;
2、单例类必须自己创建自己的唯一实例,也就是说构造函数是私有的;
3、单例类必须给其他所有对象提供这一实例,通过一个统一的结构提供;
下面看一下单例模式的三种写法,除了这三种写法,静态内部类的方式、静态代码块的方式、enum枚举的方式也都可以,不过异曲同工,这三种方式就不写了。
首先声明就是 在我们项目工程中 我们完全不用使用懒汉式 因为有锁使用的地方就有效率低的存在;
饿汉式
顾名思义,饿汉式,就是使用类的时候不管用的是不是类中的单例部分,都直接创建出单例类,看一下饿汉式的写法:
public class SingleEager { public static SingleEager se = new SingleEager(); public static SingleEager getInstance() { return se; }
private SingleRager()
{}
}
这就是饿汉式单例模式的写法,也是一种比较常见的写法。这种写法会不会造成竞争,引发线程安全问题呢?答案是不会。
可能有人会觉得奇怪:第3行,CPU执行线程A,实例化一个EagerSingleton,没有实例化完,CPU就从线程A切换到线程B了,线程B此时也实例化这个EagerSingleton,然后EagerSingleton被实例化出来了两次,有两份内存地址,不就有线程安全问题了吗?
没关系,我们完全不需要担心这个问题,JDK已经帮我们想到了。Java虚拟机2:Java内存区域及对象,文中可以看一下对象创建这一部分,没有写得很详细,其实就是"虚拟机采用了CAS配上失败重试的方式保证更新操作的原子性和TLAB两种方式来解决这个问题"。
懒汉式
同样,顾名思义,这个人比较懒,只有当单例类用到的时候才会去创建这个单例类,看一下懒汉式的写法:
public class LazySingleton { private static LazySingleton instance = null; private LazySingleton() { } public static LazySingleton getInstance() { if (instance == null) instance = new LazySingleton(); return instance; } }
这种写法基本不用,因为这是一种线程非安全的写法。试想,线程A初次调用getInstance()方法,代码走到第12行,线程此时切换到线程B,线程B走到12行,看到instance是null,就new了一个LazySingleton出来,这时切换回线程A,线程A继续走,也new了一个LazySingleton出来。这样,单例类LazySingleton在内存中就有两份引用了,这就违背了单例模式的本意了。
可能有人会想,CPU分的时间片再短也不至于getInstance()方法只执行一个判断就切换线程了吧?问题是,万一线程A调用LazySingleton.getInstance()之前已经执行过别的代码了呢,走到12行的时候刚好时间片到了,也是很正常的。
双检锁【其实这个地方叫做 带锁的双检懒汉式单利模式】
既然懒汉式是非线程安全的,那就要改进它。最直接的想法是,给getInstance方法加锁不就好了,但是我们不需要给方法全部加锁啊,只需要给方法的一部分加锁就好了。
双检的目的是为了提高效率,当第一次线程创建了实例对象后,后边进入的线程通过判断第一个是否为null,可以直接不用走入加锁的代码区;
基于这个考虑,引入了双检锁(Double Check Lock,简称DCL)的写法:
public class DoubleCheckLockSingleton
{
private static DoubleCheckLockSingleton instance = null;
private DoubleCheckLockSingleton()
{
}
public static DoubleCheckLockSingleton getInstance()
{
if (instance == null)
{
synchronized (DoubleCheckLockSingleton.class)
{
if (instance == null)
instance = new DoubleCheckLockSingleton();
}
}
return instance;
}
}
双检锁的写法是不是线程安全的呢?是的,至于为什么,不妨以分析懒汉式写法的方式分析一下双检锁的写法。
线程A初次调用DoubleCheckLockSingleton.getInstance()方法,走12行,判断instance为null,进入同步代码块,此时线程切换到线程B,线程B调用DoubleCheckLockSingleton.getInstance()方法,由于同步代码块外面的代码还是异步执行的,所以线程B走12行,判断instance为null,等待锁。结果就是线程A实例化出了一个DoubleCheckLockSingleton,释放锁,线程B获得锁进入同步代码块,判断此时instance不为null了,并不实例化DoubleCheckLockSingleton。这样,单例类就保证了在内存中只存在一份。
单例模式在Java中的应用及解读
Runtime是一个典型的例子,看下JDK API对于这个类的解释"每个Java应用程序都有一个Runtime类实例,使应用程序能够与其运行的环境相连接,可以通过getRuntime方法获取当前运行时。应用程序不能创建自己的Runtime类实例。",这段话,有两点很重要:
1、每个应用程序都有一个Runtime类实例
2、应用程序不能创建自己的Runtime类实例
只有一个、不能自己创建,是不是典型的单例模式?看一下,Runtime类的写法:
public class Runtime { private static Runtime currentRuntime = new Runtime(); //使用饿汉式 /** * Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object. * * @return the <code>Runtime</code> object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {} ... }
后面的就不黏贴了,到这里已经足够了,看到Runtime使用getRuntime()方法并让构造方法私有保证程序中只有一个Runtime实例且Runtime实例不可以被用户创建。
单例模式的好处
作为一种重要的设计模式,单例模式的好处有:
1、控制资源的使用,通过线程同步来控制资源的并发访问
2、控制实例的产生,以达到节约资源的目的
3、控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信
设计模式二:工厂模式:
一、简单工厂模式
public abstract class INoodles { /** * 描述每种面条啥样的 */ public abstract void desc(); }
先来一份兰州拉面(具体的产品类):
public class LzNoodles extends INoodles { @Override public void desc() { System.out.println("兰州拉面 上海的好贵 家里才5 6块钱一碗"); } }
程序员加班必备也要吃泡面(具体的产品类):
public class PaoNoodles extends INoodles {
@Override
public void desc() {
System.out.println("泡面好吃 可不要贪杯");
}
}
还有我最爱吃的家乡的干扣面(具体的产品类):
public class GankouNoodles extends INoodles {
@Override
public void desc() {
System.out.println("还是家里的干扣面好吃 6块一碗");
}
}
准备工作做完了,我们来到一家“简单面馆”(简单工厂类),菜单如下:
public class SimpleNoodlesFactory { public static final int TYPE_LZ = 1;//兰州拉面 public static final int TYPE_PM = 2;//泡面 public static final int TYPE_GK = 3;//干扣面 public static INoodles createNoodles(int type) { switch (type) { case TYPE_LZ: return new LzNoodles(); case TYPE_PM: return new PaoNoodles(); case TYPE_GK: default: return new GankouNoodles(); } } }
优点:
1 它是一个具体的类,非接口 抽象类。有一个重要的create()方法,利用if或者 switch创建产品并返回。
2 create()方法通常是静态的,所以也称之为静态工厂。
缺点:
1 扩展性差(我想增加一种面条,除了新增一个面条产品类,还需要修改工厂类方法)
2 不同的产品需要不同额外参数的时候 不支持。
工厂方法模式:
package com.demoFound.factoryMethod.factory; import com.demoFound.factoryMethod.message.IMyMessage; /** * 工厂方法模式_工厂接口 */ public interface IMyMessageFactory { public IMyMessage createMessage(String messageType); }
/**
* 工厂方法模式_工厂实现
*/
public class MyMessageFactory implements IMyMessageFactory {
@Override
public IMyMessage createMessage(String messageType) {
// 这里的方式是:消费者知道自己想要什么产品;若生产何种产品完全由工厂决定,则这里不应该传入控制生产的参数。
IMyMessage myMessage;
Map<String, Object> messageParam = new HashMap<String, Object>();
// 根据某些条件去选择究竟创建哪一个具体的实现对象,条件可以传入的,也可以从其它途径获取。
// sms
if ("SMS".equals(messageType)) {
myMessage = new MyMessageSms();
messageParam.put("PHONENUM", "123456789");
} else
// OA待办
if ("OA".equals(messageType)) {
myMessage = new MyMessageOaTodo();
messageParam.put("OAUSERNAME", "testUser");
} else
// email
if ("EMAIL".equals(messageType)) {
myMessage = new MyMessageEmail();
messageParam.put("EMAIL", "test@test.com");
} else
// 默认生产email这个产品
{
myMessage = new MyMessageEmail();
messageParam.put("EMAIL", "test@test.com");
}
myMessage.setMessageParam(messageParam);
return myMessage;
}
}
产品:
package com.demoFound.factoryMethod.message; import java.util.Map; /** * 工厂方法模式_产品接口 * @author popkidorc */ public interface IMyMessage { public Map<String, Object> getMessageParam(); public void setMessageParam(Map<String, Object> messageParam); public void sendMesage() throws Exception;// 发送通知/消息 }
package com.demoFound.factoryMethod.message; import java.util.Map; /** * 工厂方法模式_虚拟产品类 * @author popkidorc */ public abstract class MyAbstractMessage implements IMyMessage { private Map<String, Object> messageParam;// 这里可以理解为生产产品所需要的原材料库。最好是个自定义的对象,这里为了不引起误解使用Map。 @Override public Map<String, Object> getMessageParam() { return messageParam; } @Override public void setMessageParam(Map<String, Object> messageParam) { this.messageParam = messageParam; } }
package com.demoFound.factoryMethod.message; /** * 工厂方法模式_email产品 * @author popkidorc */ public class MyMessageEmail extends MyAbstractMessage { @Override public void sendMesage() throws Exception { // TODO Auto-generated method stub if (null == getMessageParam() || null == getMessageParam().get("EMAIL") || "".equals(getMessageParam().get("EMAIL"))) { throw new Exception("发送短信,需要传入EMAIL参数");// 为了简单起见异常也不自定义了 }// 另外邮件内容,以及其他各种协议参数等等都要处理 System.out.println("我是邮件,发送通知给" + getMessageParam().get("EMAIL")); } }
package com.demoFound.factoryMethod.message; /** * 工厂方法模式_oa待办产品 */ public class MyMessageOaTodo extends MyAbstractMessage { @Override public void sendMesage() throws Exception { // TODO Auto-generated method stub if (null == getMessageParam() || null == getMessageParam().get("OAUSERNAME") || "".equals(getMessageParam().get("OAUSERNAME"))) { throw new Exception("发送OA待办,需要传入OAUSERNAME参数");// 为了简单起见异常也不自定义了 }// 这里的参数需求就比较多了不一一处理了 System.out .println("我是OA待办,发送通知给" + getMessageParam().get("OAUSERNAME")); } }
package com.demoFound.factoryMethod.message; /** * 工厂方法模式_sms产品 */ public class MyMessageSms extends MyAbstractMessage { @Override public void sendMesage() throws Exception { // TODO Auto-generated method stub if (null == getMessageParam() || null == getMessageParam().get("PHONENUM") || "".equals(getMessageParam().get("PHONENUM"))) { throw new Exception("发送短信,需要传入PHONENUM参数");// 为了简单起见异常也不自定义了 }// 另外短信信息,以及其他各种协议参数等等都要处理 System.out.println("我是短信,发送通知给" + getMessageParam().get("PHONENUM")); } }
消费者:
package com.demoFound.factoryMethod; import com.demoFound.factoryMethod.factory.IMyMessageFactory; import com.demoFound.factoryMethod.factory.MyMessageFactory; import com.demoFound.factoryMethod.message.IMyMessage; /** * 工厂方法模式_消费者类 * @author popkidorc */ public class MyFactoryMethodMain { public static void main(String[] args) { IMyMessageFactory myMessageFactory = new MyMessageFactory(); IMyMessage myMessage; // 对于这个消费者来说,不用知道如何生产message这个产品,耦合度降低 try { // 先来一个短信通知 myMessage = myMessageFactory.createMessage("SMS"); myMessage.sendMesage(); // 来一个oa待办 myMessage = myMessageFactory.createMessage("OA"); myMessage.sendMesage(); // 来一个邮件通知 myMessage = myMessageFactory.createMessage("EMAIL"); myMessage.sendMesage(); } catch (Exception e) { e.printStackTrace(); } } }
抽象工厂方法:
抽象工厂模式代码
interface IProduct1 { public void show(); } interface IProduct2 { public void show(); } class Product1 implements IProduct1 { public void show() { System.out.println("这是1型产品"); } } class Product2 implements IProduct2 { public void show() { System.out.println("这是2型产品"); } } interface IFactory { public IProduct1 createProduct1(); public IProduct2 createProduct2(); } class Factory implements IFactory{ public IProduct1 createProduct1() { return new Product1(); } public IProduct2 createProduct2() { return new Product2(); } } public class Client { public static void main(String[] args){ IFactory factory = new Factory(); factory.createProduct1().show(); factory.createProduct2().show(); } }
抽象工厂模式的优点
抽象工厂模式除了具有工厂方法模式的优点外,最主要的优点就是可以在类的内部对产品族进行约束。所谓的产品族,一般或多或少的都存在一定的关联,抽象工厂模式就可以在类内部对产品族的关联关系进行定义和描述,而不必专门引入一个新的类来进行管理。
抽象工厂模式的缺点
产品族的扩展将是一件十分费力的事情,假如产品族中需要增加一个新的产品,则几乎所有的工厂类都需要进行修改。所以使用抽象工厂模式时,对产品等级结构的划分是非常重要的。
设计模式三:观察者模式:
1、定义一个抽象被观察者接口
/*** * 被观察者接口 * 声明了添加、删除、通知观察者方法 * @author gxy */ public interface ICompile{ public void registerObserver(Observer o); public void removeObserver(Observer o); public void notifyObserver(); }
2、定义一个抽象观察者接口
/*** * 抽象观察者 * 定义了一个update()方法,当被观察者调用notifyObservers()方法时,观察者的update()方法会被回调。 */ public interface ITaskRun{ public void run(String message); }
3、定义被观察者,实现了Observerable接口,对Observerable接口的三个方法进行了具体实现,同时有一个List集合,用以保存注册的观察者,等需要通知观察者时,遍历该集合即可。
/**
* 被观察者,也就是编译类
* 实现了ICompile接口,对ICompile接口的三个方法进行了具体实现
* @author gxy
*/
public class CompileServer implements ICompile{
//注意到这个List集合的泛型参数为Observer接口,设计原则:面向接口编程而不是面向实现编程
private List<ICompile> list;
private String message;
public CompileServer() {
list = new ArrayList<ICompile>();
}
@Override
public void registerObserver(ICompile o) {
list.add(o);
}
@Override
public void removeObserver(ICompile o) {
if(!list.isEmpty())
list.remove(o);
}
//遍历通知观察者
@Override
public void notifyObserver() {
for(int i = 0; i < list.size(); i++) {
ICompile com = list.get(i);
com.update(this.message);
}
}
public void setInfomation(String s) {
this.message = s;
//消息更新,通知所有观察者
notifyObserver();
}
}
4、定义具体观察者,微信公众号的具体观察者为用户User
/**
* 观察者
* 实现了update方法
*
*/
public class Smoking implements ITaskRun{
private String name;
private String message;
public Smoking(String name) {
this.name = name;
}
@Override
public void update(String message) {
this.message = message;
smoke();
}
public void smoke() {
System.out.println(name + " 收到推送消息: " + message);
}
}
设计模式四:模板模式
博客有一篇文章专门讲了模板方法的使用;详见:https://www.cnblogs.com/gxyandwmm/p/9375843.html
设计模式五:策略模式:
使用场景实例:
假设某个网站销售各种书籍,对初级会员没有提供折扣,对中级会员提供每本10%的促销折扣,对高级会员提供每本20%的促销折扣。
折扣是根据以下的3个算法中的1个进行的:
算法1:对初级会员没有提供折扣。
算法2:对中级会员提供10%的促销折扣。
算法3:对高级会员提供20%的促销折扣。
该实例的UML图:
折扣接口
1 public interface MemberStrategy { 2 /** 3 * 计算图书的价格 4 * @param booksPrice 图书的原价 5 * @return 计算出打折后的价格 6 */ 7 public double calcPrice(double booksPrice); 8 }
初级会员折扣实现类
1 public class PrimaryMemberStrategy implements MemberStrategy {
2
3 @Override
4 public double calcPrice(double booksPrice) {
5
6 System.out.println("对于初级会员的没有折扣");
7 return booksPrice;
8 }
9
10 }
中级会员折扣实现类
1 public class IntermediateMemberStrategy implements MemberStrategy {
2
3 @Override
4 public double calcPrice(double booksPrice) {
5
6 System.out.println("对于中级会员的折扣为10%");
7 return booksPrice * 0.9;
8 }
9
10 }
高级会员折扣实现类
1 public class AdvancedMemberStrategy implements MemberStrategy {
2
3 @Override
4 public double calcPrice(double booksPrice) {
5
6 System.out.println("对于高级会员的折扣为20%");
7 return booksPrice * 0.8;
8 }
9 }
价格类【相当于中间处理类 拿到策略接口 进行区分不同的子类策略】
1 public class Price {
2 // 持有一个具体的策略对象
3 private MemberStrategy strategy;
4 /**
5 * 构造函数,传入一个具体的策略对象
6 * @param strategy 具体的策略对象
7 */
8 public Price(MemberStrategy strategy){
9 this.strategy = strategy;
10 }
11
12 /**
13 * 计算图书的价格
14 * @param booksPrice 图书的原价
15 * @return 计算出打折后的价格
16 */
17 public double quote(double booksPrice){
18 return this.strategy.calcPrice(booksPrice);
19 }
20 }
客户端【客户端需要知道所有的策略子类才可以进行决定使用哪一个】
1 public class Client { 2 3 public static void main(String[] args) { 4 // 选择并创建需要使用的策略对象 5 MemberStrategy strategy = new AdvancedMemberStrategy(); 6 // 创建环境 7 Price price = new Price(strategy); 8 // 计算价格 9 double quote = price.quote(300); 10 System.out.println("图书的最终价格为:" + quote); 11 } 13 }
策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。策略算法是相同行为的不同实现。在运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象。把所有的具体策略实现类的共同公有方法封装到抽象类里面,将代码向继承等级结构的上方集中。
策略模式优点:
1 通过策略类的等级结构来管理算法族。
2 避免使用将采用哪个算法的选择与算法本身的实现混合在一起的多重条件(if-else if-else)语句。
策略模式缺点:
1 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
2 由于策略模式把每个算法的具体实现都单独封装成类,针对不同的情况生成的对象就会变得很多。
从上边看 策略模式和模板模式很想,下面看一下他们的具体区别:
策略模式,是指将某个类运行过程中,把核心算法提取出来、封装,使得对象在针对不同的情境可以方便地更换执行策略(算法),使得算法独立变化而不影响客户端的使用。
模板模式从一个例子引入:
泡茶的流程是:烧水,拿茶壶,放入茶叶,冲泡;
泡咖啡的流程:烧水,拿咖啡壶,放入可可,冲泡。
二者的流程是不是非常接近?,难道我们要写两个类去区分他们?当然不是,模板模式就是为了解决这个问题而存在的。 所以,模板方法是指:定义一个算法流程的骨架,把一些可变节点延迟到具体的子类中去执行,
- 执行流程:模板模式一定是按照一定次序执行程序的,任何一个节点的重载不会影响到这个次序;策略模式则不一定,它只提供了某个情景下的执行策略,是为了优化这个情景而制定的,为了达到需求,只要入口相同,执行顺序不做需求;
- 可变节点:模板模式可变节点大于等于一;策略模式被重载的节点一般唯一;
- 重载侧重点:模板模式要求算法流程中的某几个节点会被替换,但顺序不变;策略模式中整个算法都是可以被替换的。
设计模式六:动态代理模式:
JDK动态代理的实现
JDK动态代理的思维模式与之前的一般模式是一样的,也是面向接口进行编码,创建代理类将具体类隐藏解耦,不同之处在于代理类的创建时机不同,动态代理需要在运行时因需实时创建。
第一步:定义总接口Iuser.java
1 package ceshi1; 2 public interface Iuser { 3 void eat(String s); 4 }
第二步:创建具体实现类UserImpl.java
1 package ceshi1; 2 public class UserImpl implements Iuser { 3 @Override 4 public void eat(String s) { 5 System.out.println("我要吃"+s); 6 } 7 }
第三步:创建实现InvocationHandler接口的代理类
1 package ceshi1;
2 import java.lang.reflect.InvocationHandler;
3 import java.lang.reflect.Method;
4 public class DynamicProxy implements InvocationHandler {
5 private Object object;//用于接收具体实现类的实例对象
6 //使用带参数的构造器来传递具体实现类的对象
7 public DynamicProxy(Object obj){
8 this.object = obj;
9 }
10 @Override
11 public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
12 System.out.println("前置内容");
13 method.invoke(object, args);
14 System.out.println("后置内容");
15 return null;
16 }
17 }
第四步:创建测试类ProxyTest.java
1 package ceshi1; 2 import java.lang.reflect.InvocationHandler; 3 import java.lang.reflect.Proxy; 4 public class ProxyTest { 5 public static void main(String[] args) { 6 Iuser user = new UserImpl(); 7 InvocationHandler h = new DynamicProxy(user); 8 Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h); 9 proxy.eat("苹果"); 10 } 11 }
运行结果为:
动态代理前置内容 我要吃苹果 动态代理后置内容
4、通过上面的动态代理实例我们来仔细分析研究一下动态代理的实现过程
(1)首先我要说的就是接口,为什么JDK的动态代理是基本接口实现的呢?
因为通过使用接口指向实现类的实例的多态实现方式,可以有效的将具体的实现与调用之间解耦,便于后期修改与维护。
再具体的说就是我们在代理类中创建一个私有成员变量(private修饰),使用接口来指向实现类的对象(纯种的多态体现,向上转型的体现),然后在该代理类中的方法中使用这个创建的实例来调用实现类中的相应方法来完成业务逻辑功能。
这么说起来,我之前说的“将具体实现类完全隐藏”就不怎么正确了,可以改成,将具体实现类的细节向调用方完全隐藏(调用方调用的是代理类中的方法,而不是实现类中的方法)。
这就是面向接口编程,利用java的多态特性,实现程序代码的解耦。
(2)创建代理类的过程
如果你了解静态代理,那么你会发现动态代理的实现其实与静态代理类似,都需要创建代理类,但是不同之处也很明显,创建方式不同!
不同之处体现在静态代理我们知根知底,我们知道要对哪个接口、哪个实现类来创建代理类,所以我们在编译前就直接实现与实现类相同的接口,直接在实现的方法中调用实现类中的相应(同名)方法即可;而动态代理不同,我们不知道它什么时候创建,也不知道要创建针对哪个接口、实现类的代理类(因为它是在运行时因需实时创建的)。
虽然二者创建时机不同,创建方式也不相同,但是原理是相同的,不同之处仅仅是:静态代理可以直接编码创建,而动态代理是利用反射机制来抽象出代理类的创建过程。
让我们来分析一下之前的代码来验证一下上面的说辞:
第一点:静态代理需要实现与实现类相同的接口,而动态代理需要实现的是固定的Java提供的内置接口(一种专门提供来创建动态代理的接口)InvocationHandler接口,因为java在接口中提供了一个可以被自动调用的方法invoke,这个之后再说。
第二点:private Object object;
public UserProxy(Object obj){this.object = obj;}
这几行代码与静态代理之中在代理类中定义的接口指向具体实现类的实例的代码异曲同工,通过这个构造器可以创建代理类的实例,创建的同时还能将具体实现类的实例与之绑定(object指的就是实现类的实例,这个实例需要在测试类中创建并作为参数来创建代理类的实例),实现了静态代理类中private Iuser user = new UserImpl();一行代码的作用相近,这里为什么不是相同,而是相近呢,主要就是因为静态代理的那句代码中包含的实现类的实例的创建,而动态代理中实现类的创建需要在测试类中完成,所以此处是相近。
第三点:invoke(Object proxy, Method method, Object[] args)方法,该方法是InvocationHandler接口中定义的唯一方法,该方法在调用指定的具体方法时会自动调用。其参数为:代理实例、调用的方法、方法的参数列表
在这个方法中我们定义了几乎和静态代理相同的内容,仅仅是在方法的调用上不同,不同的原因与之前分析的一样(创建时机的不同,创建的方式的不同,即反射),Method类是反射机制中一个重要的类,用于封装方法,该类中有一个方法那就是invoke(Object object,Object...args)方法,其参数分别表示:所调用方法所属的类的对象和方法的参数列表,这里的参数列表正是从测试类中传递到代理类中的invoke方法三个参数中最后一个参数(调用方法的参数列表)中,在传递到method的invoke方法中的第二个参数中的(此处有点啰嗦)。
第四点:测试类中的异同
静态代理中我们测试类中直接创建代理类的对象,使用代理类的对象来调用其方法即可,若是别的接口(这里指的是别的调用方)要调用Iuser的方法,也可以使用此法
动态代理中要复杂的多,首先我们要将之前提到的实现类的实例创建(补充完整),然后利用这个实例作为参数,调用代理来的带参构造器来创建“代理类实例对象”,这里加引号的原因是因为它并不是真正的代理类的实例对象,而是创建真正代理类实例的一个参数,这个实现了InvocationHandler接口的类严格意义上来说并不是代理类,我们可以将其看作是创建代理类的必备中间环节,这是一个调用处理器,也就是处理方法调用的一个类,不是真正意义上的代理类,可以这么说:创建一个方法调用处理器实例。
下面才是真正的代理类实例的创建,之前创建的”代理类实例对象“仅仅是一个参数
Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);
这里使用了动态代理所依赖的第二个重要类Proxy,此处使用了其静态方法来创建一个代理实例,其参数分别是:类加载器(可为父类的类加载器)、接口数组、方法调用处理器实例
这里同样使用了多态,使用接口指向代理类的实例,最后会用该实例来进行具体方法的调用即可。
(3)InvocationHandler
InvocationHandler是JDK中提供的专门用于实现基于接口的动态代理的接口,主要用于进行方法调用模块,而代理类和实例的生成需要借助Proxy类完成。
每个代理类的实例的调用处理器都是实现该接口实现的,而且是必备的,即每个动态代理实例的实现都必须拥有实现该接口的调用处理器,也可以这么说,每个动态代理实例都对应一个调用处理器。
这里要区分两个概念,代理类和代理实例,调用处理器是在创建代理实例的时候才与其关联起来的,所以它与代理实例是一一对应的,而不是代理类。
(4)Proxy
Proxy类是JDK提供的用于生成动态代理类和其实例的类。
我们可以通过Proxy中的静态方法getProxyClass来生成代理类,需要的参数为类加载器和接口列表(数组),然后再通过反射调用代理类的构造器来生成代理实例,需要以一个InvocationHandler作为参数(体现出方法调用是与实例相关的,而非类)。
1 InvocationHandler handler = new MyInvocationHandler(...); 2 Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class); 3 Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).newInstance(handler);
我们也可以直接通过Proxy中的静态方法newProxyInstance方法来直接生产代理实例,需要提供参数为上面的三个参数,即类加载器,接口数组,InvocationHandler。
1 Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class<?>[] { Foo.class },handler);
(5)、总结
我们总结下JDK动态代理的实现步骤:
第一步:创建接口,JDK动态代理基于接口实现,所以接口必不可少(准备工作)
第二步:实现InvocationHandler接口,重写invoke方法(准备工作)
第三步:调用Proxy的静态方法newProxyInstance方法生成代理实例(生成实例时需要提供类加载器,我们可以使用接口类的加载器即可)
第四步:使用新生成的代理实例调用某个方法实现功能。
我们的动态代理实现过程中根本没有涉及到真实类实例。