Spring中常用的设计模式

工厂模式

在平时编程中,构建对象最常用的方式是 new 一个对象。乍一看这种做法没什么不好,而实际上这也属于一种硬编码。每 new 一个对象,相当于调用者多知道了一个类,增加了类与类之间的联系,不利于程序的松耦合。我们可以利用工厂模式封装对象的生产过程。

简单工厂模式

  • 指定一个工厂对象来创建产品实例,只需要传给工厂参数,不需要关系对象是怎样产生的
  • 好处:如果业务扩展客户端会依赖 “一坨”对象,耦合度高,通过简单工厂模式改造后只依赖工厂类,耦合度降低

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled.png

  • 缺点:不符合开闭原则,如果增加新的参数则需要修改创建对象的代码
  • JDK中简单工厂的例子 下面是Calendar类中的createCalendar方法:
    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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    private static Calendar createCalendar(TimeZone zone,
    Locale aLocale)
    {
    CalendarProvider provider =
    LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
    .getCalendarProvider();
    if (provider != null) {
    try {
    return provider.getInstance(zone, aLocale);
    } catch (IllegalArgumentException iae) {
    // fall back to the default instantiation
    }
    }

    Calendar cal = null;

    if (aLocale.hasExtensions()) {
    String caltype = aLocale.getUnicodeLocaleType("ca");
    if (caltype != null) {
    switch (caltype) {
    case "buddhist":
    cal = new BuddhistCalendar(zone, aLocale);
    break;
    case "japanese":
    cal = new JapaneseImperialCalendar(zone, aLocale);
    break;
    case "gregory":
    cal = new GregorianCalendar(zone, aLocale);
    break;
    }
    }
    }
    if (cal == null) {
    if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
    cal = new BuddhistCalendar(zone, aLocale);
    } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
    && aLocale.getCountry() == "JP") {
    cal = new JapaneseImperialCalendar(zone, aLocale);
    } else {
    cal = new GregorianCalendar(zone, aLocale);
    }
    }
    return cal;
    }

工厂方法模式

  • 指定一个创建对象的接口,让实现这个接口的类来决定实例化哪一个类,实例化推迟到子类中进行。
  • 好处:用户只需要关心生产产品对应的工厂,不需要关系生产产品的细节,符合开闭原则
  • 适用场景:
    1. 创建对象需要大量重复代码
    2. 客户端(应用层)不依赖产品实例如何被创建、如何被实现等细节
    3. 一个类通过其子类来指定创建哪个对象

抽象工厂模式

  • 指定一个接口生产创建一系列产品,或相互依赖的对象。
  • 缺点:1. 类的个数容易过多 2.增加了系统抽象性和理解难度
  • 不符合开闭原则,增加产品时从抽象工厂开始都要进行修改
  • 增加了系统的抽象性和理解难度

利用工厂模式重构的案例

JDBC CRUD操作每次都要获取数据库连接很消耗性能,可以通过创建数据库连接池,先创建好数据库链接放入容器中,在业务调用时先取现用

总结

使用工厂模式就是为了给客户端(应用层)解耦,如果不使用工厂模式,本来应用层会依赖一堆对象,通过工厂模式重构就可以只依赖相应的工厂,依赖关系变得简单。

数据库连接池就是工厂模式的体现,从连接池中取出连接对象,而不必关心连接对象到底是如何生成的。

单例模式

单例模式.png

单例模式是指确保在任何情况下都绝对只有一个实例,并提供一个全局访问点。ServletContext、ServletContextConfig、ApplicationContext、数据库连接池都是单例模式

饿汉单例模式

为什么叫饿汉单例模式,因为在类加载的时候就初始化,并创建单例对象。饿汉单例模式是绝对线程安全的,因为在线程还没有出现的时候就初始化了。

  • 优点: 没有加任何锁、执行效率比较高,用户体验更好
  • 缺点:不管用不用都占用空间,浪费内存,就是“占坑不拉屎”。

第一种写法

1
2
3
4
5
6
7
8
9
public class HungrySingleton {
private static final HungrySingleton hungrySingleton = new HungrySingleton();

private HungrySingleton() {};

public static HungrySingleton getInstance() {
return hungrySingleton;
}
}

第二种写法:

1
2
3
4
5
6
7
8
9
10
11
12
public class HungryStaticSingleton {
private static final HungryStaticSingleton hungrySingleton;
static {
hungrySingleton = new HungryStaticSingleton();
}

private HungryStaticSingleton() {};

public static HungryStaticSingleton getInstance() {
return hungrySingleton;
}
}

懒汉单例模式

为了解决饿汉模式占用内存的问题,懒汉单例模式不会马上对对象进行加载,而是在需要的时候进行加载,下面是线程不安全的写法

1
2
3
4
5
6
7
8
9
10
public class LazySimpleSingleton {
private static LazySimpleSingleton lazySimpleSingleton = null;
private LazySimpleSingleton() {}
public static LazySimpleSingleton getInstance() {
if (lazySimpleSingleton == null) {
lazySimpleSingleton = new LazySimpleSingleton();
}
return lazySimpleSingleton;
}
}

如果多个线程同时调用getInstance方法肯能两个线程会同时进入if语句导致创建出两个对象,后创建的对象会覆盖第一个对象。为了解决线程不安全问题需要进行加锁,改进后的代码如下:

1
2
3
4
5
6
7
8
9
10
public class LazySimpleSingleton {
private static LazySimpleSingleton lazySimpleSingleton = null;
private LazySimpleSingleton() {}
public synchronized static LazySimpleSingleton getInstance() {
if (lazySimpleSingleton == null) {
lazySimpleSingleton = new LazySimpleSingleton();
}
return lazySimpleSingleton;
}
}

给getInstance整个方法进行加锁,这样当一个线程进入该方法时另外一个线程会从RUNNING状态变成MONITOR状态,第一个线程执行完成后,第二个线程再进入getInstance方法后lazySimpleSingleton≠null,因此只会创建一个对象。这种写法虽然解决了线程安全问题,但是性能差,当有大量线程访问是会照成很多线程发生阻塞,因此需要对代码进行改进,下面是双检的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazy;
private LazyDoubleCheckSingleton() {}
public static LazyDoubleCheckSingleton getInstance() {
if (lazy == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (lazy == null) {
lazy = new LazyDoubleCheckSingleton();
}
}
}
return lazy;
}
}

通过改进,多个线程都可以进入getInstance方法,也可以通过第一层检查,然后就会竞争一个锁,第一个拿到锁的线程会对lazy进行实例化,后面拿到锁的线程就不会通过第二层检查。这里给静态成员变量加上volatile关键字是防止编译器对lazy = new LazyDoubleCheckSingleton()这段代码的指令进行优化,这段代码的指令分为三条:

  1. 分配内存空间
  2. 初始化对象
  3. 将变量lazy指向刚刚分配的内存空间

volatile可以防止第二条和第三条指令发生重排序,指令顺序变为1->3->2,也就是lazy不为null但是没有初始化。在 3 执行完毕、2 未执行之前,被另一个抢占了,这时 lazy已经是非 null 了(但却没有初始化),所以该线程会直接返回 lazy,然后使用,然后顺理成章地报空指针。

双检的写法毕竟是需要加锁的,所以还是会对性能产生影响。利用类的加载性质,可以使用静态内部类实现懒汉单例模式,首先我们知道一个类的初始化时机有以下6种:

    1. 创建类的实例—> new
  • (2) 访问某个类或接口的静态变量,或对静态变量赋值
  • (3) 调用类的静态方法
  • (4) 反射 (Class.forname(“全限定类名”))
  • (5) 初始化一个类的子类—>先初始化父类
  • (6) JVM启动时标明的启动类,就是类名和文件名相同的那个类 注意: 访问常量(static final)不会导致类的初始化; 使用Class.loader()方法加载类时也不会对类初始化

可以看到访问某个类的静态的静态变量这个类就会被初始化,类的加载过程不会马上加载内部类,而是在使用时进行加载,利用这个性质就能实现懒加载,而JVM已经保证了线程的安全性,静态内部类在方法调用前就会被初始化,代码如下:

1
2
3
4
5
6
7
8
9
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton() {}
public static LazyInnerClassSingleton getInstance() {
return LazyHolder.LAZY;
}
private static class LazyHolder {
private static LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}

反射破坏单例模式

上面代码的构造器除了加了private修饰符以外没有做任何限制,因此可以通过反射来破坏单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test04() {
try {
Class<LazyInnerClassSingleton> clazz = LazyInnerClassSingleton.class;
Constructor<LazyInnerClassSingleton> c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);// 强制访问
LazyInnerClassSingleton o1 = c.newInstance();
LazyInnerClassSingleton o2 = c.newInstance();
System.out.println(o1 == o2);
}catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}

输出:false, 因此需要对构造器进行处理,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LazyInnerClassSingleton2 {

private LazyInnerClassSingleton2() {
if (LazyHolder.LAZY != null) {
throw new RuntimeException("不允许创建多个实例");
}
}
public static LazyInnerClassSingleton2 getInstance() {
return LazyHolder.LAZY;
}
public static class LazyHolder {
public static LazyInnerClassSingleton2 LAZY = new LazyInnerClassSingleton2();
}
}

通过反射调用构造器创建对象时,执行LazyHolder.LAZY语句时,静态内部类会初始化,导致LazyHolder.LAZY != null,从而抛出异常,无法创建多个对象。

序列化破坏单例对象

序列化是指将一个对象序列化后写入磁盘,下次使用时再从磁盘种读取对象并进行反序列化,转化为内存对象。反序列化的对象会重新分配内存,因此如果反序列化的目标对象是单例对象就会破坏单例,下面来看代码:

首先写一个饿汉单例模式:

1
2
3
4
5
6
7
8
9
10
public class SeriableSingleton implements Serializable {

private static SeriableSingleton instance = new SeriableSingleton();

private SeriableSingleton() {}

public static SeriableSingleton getInstance() {
return instance;
}
}

下面我们测试反序列化是否能破坏单例模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void test06() {
try {
SeriableSingleton s1 = null;
SeriableSingleton s2 = SeriableSingleton.getInstance();
FileOutputStream fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();

FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ooi = new ObjectInputStream(fis);
s1 = (SeriableSingleton) ooi.readObject();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}

控制台输出如图所示:

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%201.png

可以看到反序列化后的对象和手动创建的对象是不一样的,实例化了两次,也就破坏了单例。那么如何解决这个问题呢?其实我们只需要增加readResolve方法即可,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SeriableSingleton implements Serializable {

private static SeriableSingleton instance = new SeriableSingleton();

private SeriableSingleton() {}

public static SeriableSingleton getInstance() {
return instance;
}

private Object readResolve() {
return instance;
}
}

再来测试一下,看看运行结果,如下图所示。

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%202.png

可以看到神奇的事情发生了, 加上了readResolve方法后反序列化并没有破坏单例模式,那这到底是什么原因呢,我们来看看ObjectInputStream类的readObject方法

1
2
3
4
5
6
7
8
9
10
11
private final Object readObject(Class<?> type)
throws IOException, ClassNotFoundException
{
....
try {
Object obj = readObject0(type, false);
.....
return obj;
}
.......
}

再进入readObject0方法

1
2
3
4
5
6
7
8
9
private Object readObject0(Class<?> type, boolean unshared) throws IOException {
...
switch (tc) {
case TC_OBJECT:
....
return checkResolve(readOrdinaryObject(unshared));
}
...
}

可以看到在TC_OBJECT中调用了readOrdinaryObject方法, 我们再进入该方法:

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
private Object readOrdinaryObject(boolean unshared)
throws IOException {
...
Object obj;
try {
**obj = desc.isInstantiable() ? desc.newInstance() : null;**
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
...
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
.....
if (rep != obj) {
.....
handles.setObject(passHandle, obj = rep);
}
}
...
return obj;
}

通过阅读源码,我们可以知道,isInstantiable()是判断构造器是否为空,如果不为空就返回true,因此就会创建一个新的对象。再往下看我们可以看到还调用了hasReadResolveMethod方法来判断反序列化的对象有没有readResolve方法,如果有则返回true,然后就会invokeReadResolve方法,看这方法名就知道是通过反射来调用readResolve方法,我们进入该方法看看源码:

1
2
3
4
5
6
7
Object invokeReadResolve(Object obj)
throws IOException, UnsupportedOperationException
{
....
return readResolveMethod.invoke(obj, (Object[]) null);
....
}

到这是不是有点恍然大悟,rep就是调用我们添加的readResolve方法返回的单例对象,源码中做了一个判断,如果创建的obj对象与rep对象不同,则将rep赋值给obj,最后返回,这样反序列化返回的就是单例对象。通过源码分析可以看出readResolve虽然解决了单例模式被破坏的问题,但是实际上该对象还是创建了两次,只是最后返回了相同的引用,如果创建对象的频率加快,内存开销也会增大。

注册式单例模式

注册式单例模式顾名思义就是将每个实例用唯一标识符登记再某个“本子”上,如果要使用某个实例时,就去”本子“上查。注册式单例模式可以分为枚举式和容器式。

1.枚举式单例模式

首先我们来看一下枚举式单例模式的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public enum EnumSingleton {
INSTANCE;
private Object data;

public Object getData() {
return data;
}

public void setData(Object data) {
this.data = data;
}

public static EnumSingleton getInstance() {
return INSTANCE;
}
}

然后我们测试一下反序列化能否打破单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void test07() {
try {
EnumSingleton instance1 = null;
EnumSingleton instance2 = EnumSingleton.getInstance();
instance2.setData(new Object());
FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance2);
oos.flush();
oos.close();

FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ooi = new ObjectInputStream(fis);
instance1 = (EnumSingleton) ooi.readObject();
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance1 == instance2);
} catch (Exception e) {
e.printStackTrace();
}
}

运行结果如图所示

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%203.png

为啥枚举式单例模式不会被反序列化破坏呢,我们使用反编译工具jad来看一下EnumSingleton.class的反编译代码, 可以看到如下代码

1
2
3
4
5
6
7
static 
{
INSTANCE = new EnumSingleton("INSTANCE", 0);
$VALUES = (new EnumSingleton[] {
INSTANCE
});
}

这说明枚举型单例模式是饿汉模式的实现,我们再看下JDK源码,搞清楚反序列化为啥不能破坏枚举式单例模式。下面是java.io.ObjectInputStream#readObject0的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
private Object readObject0(Class<?> type, boolean unshared) throws IOException {
....
switch (tc) {
....
case TC_ENUM:
if (type == String.class) {
throw new ClassCastException("Cannot cast an enum to java.lang.String");
}
return checkResolve(readEnum(unshared));
....
}
....
}

再进入readEnum方法

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
31
32
33
34
35
36
37
38
private Enum<?> readEnum(boolean unshared) throws IOException {
if (bin.readByte() != TC_ENUM) {
throw new InternalError();
}

ObjectStreamClass desc = readClassDesc(false);
if (!desc.isEnum()) {
throw new InvalidClassException("non-enum class: " + desc);
}

int enumHandle = handles.assign(unshared ? unsharedMarker : null);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(enumHandle, resolveEx);
}

String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
try {
@SuppressWarnings("unchecked")
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
} catch (IllegalArgumentException ex) {
throw (IOException) new InvalidObjectException(
"enum constant " + name + " does not exist in " +
cl).initCause(ex);
}
if (!unshared) {
handles.setObject(enumHandle, result);
}
}

handles.finish(enumHandle);
passHandle = enumHandle;
return result;
}

可以发现,反序列化的枚举类型是通过类名和类对象来找到唯一的枚举对象,因此枚举对象不会被类加载器加载多次。再试试反射能否破坏单例模式, 首先java.lang.Enum只有一个构造器:

1
2
3
4
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}

我们尝试通过反射来调用这个构造器,创建对象

1
2
3
4
5
6
7
8
9
10
11
@Test
public void test08() {
try {
Class<EnumSingleton> clazz = EnumSingleton.class;
Constructor<EnumSingleton> c = clazz.getDeclaredConstructor(String.class, int.class);
c.setAccessible(true);
EnumSingleton miHoltel = c.newInstance("miHoltel", "666");
} catch (Exception e) {
e.printStackTrace();
}
}

运行结果如下图所示:

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%204.png

错误提示不能通过反射创建枚举对象,我们再来看看JDK源码,进入Contructor类的newInstance方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}

可以看到在newInstance方法中做了强制判断,如果是枚举类型则直接抛出异常。

容器式单例模式

下面来看一下注册式单例模式的另外一种写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ContainerSingleton {

private ContainerSingleton() {}

private static Map<String, Object> ioc = new ConcurrentHashMap<>();

public static Object getBean(String className) {
synchronized (ioc) {
if (!ioc.containsKey(className)) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className, obj);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
} else {
return ioc.get(className);
}
}
}
}

容器式单例模式适用于实例非常多的情况。Spring中应用容器式单例模式的代码如下:

1
2
3
4
5
6
7
8
9
10
11
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
.....
private final ConcurrentMap<String, BeanWrapper> factoryBeanInstanceCache;

public AbstractAutowireCapableBeanFactory() {
...
this.factoryMethodCandidateCache = new ConcurrentHashMap();
...
}
.....
}

原型模式

在开发中会遇到很多set或get的场景,但是这样的代码纯属”体力劳动“。原型模式就能帮助解决这些问题,可以通过复制原型来创建新的对象。

原型模式有以下适用场景:

  1. 类初始化消耗资源较多
  2. 使用new生成一个对象需要非常繁琐的过程(数据准备、访问权限等)
  3. 构造函数比较复杂

在Spring中原型模式的应用有 scope = ”prototype“

浅克隆

首先创建一个Propotype的接口

1
2
3
public interface Prototype {
Prototype clone();
}

创建一个具体要克隆的类

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
31
32
33
34
35
36
37
38
public class ConcretePrototypeA implements Prototype{
private int age;
private String name;
private List hobbies;

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public List getHobbies() {
return hobbies;
}

public void setHobbies(List hobbies) {
this.hobbies = hobbies;
}

@Override
public ConcretePrototypeA clone() {
ConcretePrototypeA concretePrototype = new ConcretePrototypeA();
concretePrototype.setAge(this.age);
concretePrototype.setName(this.name);
concretePrototype.setHobbies(this.hobbies);// 复制的只是引用地址
return concretePrototype;
}
}

创建一个Client类

1
2
3
4
5
6
7
8
9
public class Client {
private Prototype prototype;
public Client(Prototype prototype) {
this.prototype = prototype;
}
public Prototype startClone() {
return prototype.clone();
}
}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void test01() {
ConcretePrototypeA concretePrototype = new ConcretePrototypeA();
concretePrototype.setAge(12);
concretePrototype.setName("二狗");
ArrayList<Object> objects = new ArrayList<>();
concretePrototype.setHobbies(objects);
System.out.println(concretePrototype);

Client client = new Client(concretePrototype);
ConcretePrototypeA concretePrototypeClone = (ConcretePrototypeA) client.startClone();
System.out.println(concretePrototypeClone);

System.out.println("克隆对象中的引用类型地址" + concretePrototypeClone.getHobbies());
System.out.println("原对象中的引用类型地址" + concretePrototype.getHobbies());
System.out.println("地址比较" + (concretePrototypeClone.getHobbies() == concretePrototype.getHobbies()));
}

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%205.png

可以发现对于引用对象,浅克隆只是复制了引用地址。

深克隆

齐天大圣会72变,可以变出成千上万个猴子,下面来利用原型模式模拟一下, 创建Monkey类

1
2
3
4
5
public class MonKey {
protected int height;
protected int weight;
protected Date birthday;
}

创建JinGuBang类

1
2
3
4
5
6
7
8
9
10
11
12
public class JinGuBang implements Serializable {
public float h = 100;
public float d = 10;
public void big() {
this.h *= 2;
this.d *= 2;
}
public void small() {
this.h /= 2;
this.d /= 2;
}
}

创建具体的齐天大圣类

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
31
32
33
34
35
36
37
38
public class QitianDaSheng extends MonKey implements Cloneable, Serializable {

public JinGuBang jinGuBang;

public QitianDaSheng() {
this.birthday = new Date();
this.jinGuBang = new JinGuBang();
}

@Override
protected Object clone() throws CloneNotSupportedException {
return this.deepClone();
}

private Object deepClone() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

public QitianDaSheng shallowClone(QitianDaSheng target) {
QitianDaSheng qitianDaSheng = new QitianDaSheng();
qitianDaSheng.height = target.height;
qitianDaSheng.weight = target.weight;

qitianDaSheng.jinGuBang = target.jinGuBang;
qitianDaSheng.birthday = target.birthday;
return qitianDaSheng;
}
}

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
@Test
public void test01() {
QitianDaSheng qitianDaSheng = new QitianDaSheng();
try {
QitianDaSheng clone = (QitianDaSheng) qitianDaSheng.clone();
System.out.println("深克隆:" + (qitianDaSheng.jinGuBang == clone.jinGuBang));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}

QitianDaSheng qitianDaSheng1 = qitianDaSheng.shallowClone(qitianDaSheng);
System.out.println("浅克隆:" + (qitianDaSheng1.jinGuBang == qitianDaSheng.jinGuBang));
}
}

运行结果如下图所示:

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%206.png

克隆破坏单例模式

如果克隆的对象是单例对象,那么肯定会破坏单例模式。防止克隆破坏单例模式只需要禁止克隆。

  1. 不实现Cloneable接口
  2. 克隆方法中返回单例对象

代理模式

代理模式简而言之就是被代理类和代理类都是先相同的功能(接口),代理类可以扩展被代理类的功能。代理模式可以分为静态代理和动态代理。

静态代理

举个生活中的例子,每个人成年后都要找对象,如果;平时工作忙就需要找别人代理自己相亲,比如自己的父母,下面通过代码来实现这个场景,首先创建Person接口

1
2
3
public interface Person {
void findLove();
}

再创建Son类实现Person接口

1
2
3
4
5
6
public class Son implements Person {
@Override
public void findLove() {
System.out.println("儿子的要求:" + "肤白貌美");
}
}

再创建一个代理类Father实现相同的接口,通过组合的方式代理son相亲

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Father implements Person {

private Person person;

public Father(Person person) {
this.person = person;
}

@Override
public void findLove() {
System.out.println("父亲物色对象");
this.person.findLove();
System.out.println("满足要求同意相亲");
}
}

下面是测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Father implements Person {

private Person person;

public Father(Person person) {
this.person = person;
}

@Override
public void findLove() {
System.out.println("父亲物色对象");
this.person.findLove();
System.out.println("满足要求同意相亲");
}

public static void main(String[] args) {
Son son = new Son();
Father father = new Father(son);
father.findLove();
}
}

运行结果:

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%207.png

在实际的业务场景中,会对数据库分库分表,分库分表之后就会需要配置多个数据源,可以通过数据源路由来动态切换数据源。首先创建一个订单类

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
public class OrderDO {

private Object orderInfo;
private Long createTime;
private String id;

public Object getOrderInfo() {
return orderInfo;
}

public void setOrderInfo(Object orderInfo) {
this.orderInfo = orderInfo;
}

public Long getCreateTime() {
return createTime;
}

public void setCreateTime(Long createTime) {
this.createTime = createTime;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}
}

再创建一个持久层操作类

1
2
3
4
5
6
public class OrderDao {
public int insert(OrderDO orderDO) {
System.out.println("OrderDAO 创建Order成功");
return 1;
}
}

创建IOrderService接口

1
2
3
public interface IOrderService {
int createOrder(OrderDO orderDO);
}

创建OrderService实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class OrderService implements IOrderService {
private OrderDao orderDao;

public OrderService(OrderDao orderDao) {
this.orderDao = orderDao;
}

@Override
public int createOrder(OrderDO orderDO) {
System.out.println("OrderService调用DAO创建订单");
return orderDao.insert(orderDO);
}
}

下面使用静态代理实现按照订单的创建时间来自动按年份进行分库,首先创建一个数据源路由对象,通过ThreadLocal单例模式来实现,每个线程自己获得的数据源是相同的

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
public class DynamicDataSourceEntry {

public final static String DEFAULT_SOURCE = null;

private final static ThreadLocal<String> local = new ThreadLocal<String>();

private DynamicDataSourceEntry() {}

// 清空数据源
public static void clear() {
local.remove();
}

// 还原数据源
public void restore() {
local.set(DEFAULT_SOURCE);
}

// 设置已知名字的数据源
public static void set(String source) {
local.set(source);
}

// 根据年份动态设置数据源
public static void set(int year) {
local.set("DB_" + year);
}

}

创建切换数据源的代理类

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
31
public class OrderServiceStaticProxy implements IOrderService{

private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");

private IOrderService orderService;

public OrderServiceStaticProxy(IOrderService orderService) {
this.orderService = orderService;
}

@Override
public int createOrder(OrderDO orderDO) {
before();
Long createTime = orderDO.getCreateTime();
Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(createTime)));
System.out.println("静态代理自动分配到【DB_" + dbRouter + "】处理数据");
DynamicDataSourceEntry.set(dbRouter);
orderService.createOrder(orderDO);
after();
return 0;
}

private void before() {
System.out.println("Proxy before method");
}

private void after() {
System.out.println("Proxy after method");
}

}

下面是测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
try {
OrderDO orderDO = new OrderDO();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
Date parse = dateFormat.parse("2017/02/01");
orderDO.setCreateTime(parse.getTime());

IOrderService orderService = new OrderServiceStaticProxy(new OrderService(new OrderDao()));
orderService.createOrder(orderDO);
} catch (Exception e) {
e.printStackTrace();
}
}

运行结果如下图所示:

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%208.png

动态代理

静态代理存在类爆炸的问题,并且静态代理只能代理特定的对象。还是以相亲为例,之前的例子是父亲代理儿子寻找相亲对象,但是随着产业的发展会衍生出媒婆等机构,这些机构可以满足任何单身人士寻找相亲对象。下面我们来升级一下代码:

1.jdk实现方式

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
public class JDKMeipo implements InvocationHandler {

private Object target;

public Object getInstance(Object target) {
this.target = target;
Class<?> clazz = target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object obj = method.invoke(this.target, args);
after();
return obj;
}

private void before() {
System.out.println("我是媒婆现在给你寻找对象,已确认需求");
System.out.println("开始物色对象");
}

private void after() {
System.out.println("物色结束,您满意吗");
}

}

测试代码如下:

1
2
3
4
5
6
7
8
public static void main(String[] args) {
try {
Person obj = (Person) new JDKMeipo().getInstance(new Son());
obj.findLove();
} catch (Exception e) {
e.printStackTrace();
}
}

运行结果:

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%209.png

下面再使用动态代理来改造一下数据源动态路由的业务,代码如下

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class OrderServiceDynamicProxy implements InvocationHandler {

private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");

private Object target;

private Object getInstance(Object target) {
this.target = target;
Class<?> clazz = target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before(args[0]);
Object obj = method.invoke(target, args);
after();
return obj;
}

private void before(Object order) {
OrderDO orderDO = (OrderDO) order;
Long createTime = orderDO.getCreateTime();
int dbRouter = Integer.valueOf(yearFormat.format(new Date(createTime)));
System.out.println("静态代理自动分配到【DB_" + dbRouter + "】处理数据");
DynamicDataSourceEntry.set(dbRouter);
}

private void after() {
System.out.println("Proxy after method");
}

public static void main(String[] args) {
try {
OrderDO orderDO = new OrderDO();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
Date parse = dateFormat.parse("2017/02/01");
orderDO.setCreateTime(parse.getTime());

IOrderService orderService = (IOrderService) new OrderServiceDynamicProxy().getInstance(new OrderService(new OrderDao()));
orderService.createOrder(orderDO);
} catch (Exception e) {
e.printStackTrace();
}
}
}

运行结果如下图所示:

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%2010.png

JDK动态代理是如何实现的呢?我们来探究一下原理,并自己手动写一个属于自己的动态代理。

JDK动态代理采用了字节码重组,重新生成对象来代替原有的对象,以达到动态代理的目的,JDK动态代理生成对象的步骤如下:

  1. 获取被代理对象的引用,并且获取它的所有接口
  2. JDK动态代理重新生成一个新的类,该类实现了被代理类的所有接口
  3. 动态生成Java代码,新加的业务逻辑由一定的逻辑代码调用
  4. 编译生成新的Java代码.class文件
  5. 重新加载到JVM中运行

上述过程就叫做字节码重组。JDK中如果类路径下以$开头的.class文件一般都是自动生成的。我们可以将内存中的字节码输出到新的.class文件,然后使用jad反编译工具来看看动态生成的代理类源码长什么样,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
try {
Person obj = (Person) new JDKMeipo().getInstance(new Son());
obj.findLove();

byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0.class", new Class[] {Person.class});
FileOutputStream fos = new FileOutputStream("E://$Proxy0.class");
fos.write(bytes);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}

反编译后的代码如下:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import java.lang.reflect.*;

public final class class extends Proxy
implements Person
{

public class(InvocationHandler invocationhandler)
{
super(invocationhandler);
}

public final boolean equals(Object obj)
{
try
{
return ((Boolean)super.h.invoke(this, m1, new Object[] {
obj
})).booleanValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}

public final void findLove()
{
try
{
super.h.invoke(this, m3, null);
return;
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}

public final String toString()
{
try
{
return (String)super.h.invoke(this, m2, null);
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}

public final int hashCode()
{
try
{
return ((Integer)super.h.invoke(this, m0, null)).intValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}

private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;

static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
Class.forName("java.lang.Object")
});
m3 = Class.forName("chapter2.section02.topic04.demo01.Person").getMethod("findLove", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
}
catch(NoSuchMethodException nosuchmethodexception)
{
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch(ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}

可以看到动态生成的代理类继承了Proxy并且实现了Person接口,并在静态代码块中获取了目标对象所有方法的引用,重写的方法通过反射来调用目标对象的方法。我们可以自己实现动态生成源代码、完成编译,然后替代目标对象执行。

首先创建 MHTInvocationHandler 接口

1
2
3
public interface MHTInvocationHandler {
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

然后创建MHTProxy类

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
public class MHTProxy {

private static final String ln = "\r\n";

public static Object newProxyInstance(MHTClassLoader classLoader, Class<?>[] interfaces, MHTInvocationHandler h) {
try {
// 动态生成源代码.java文件
String src = generateSrc(interfaces);
// java文件保存到磁盘
String filePath = MHTProxy.class.getResource("").getPath();
File f = new File(filePath+"$Proxy0.java");
FileWriter fw = new FileWriter(f);;
fw.write(src);
fw.flush();
fw.close();
// 生成的java文件编译成.class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> iterable = manager.getJavaFileObjects(f);
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
task.call();
manager.close();
// 把编译生成的.class文件加载到JVM中
Class proxyClass = classLoader.findClass("$Proxy0");
Constructor c = proxyClass.getConstructor(MHTInvocationHandler.class);
return c.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

private static String generateSrc(Class<?>[] interfaces) {
StringBuffer sb = new StringBuffer();
sb.append("package chapter2.section02.topic04.demo03;" + ln);
sb.append("import chapter2.section02.topic04.demo01.Person;" + ln);
sb.append("import java.lang.reflect.*;" + ln);
sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
sb.append("MHTInvocationHandler h;" + ln);
sb.append("public $Proxy0(MHTInvocationHandler h) { ");
sb.append("this.h = h;" );
sb.append(" }" + ln);
// 获取需实现接口的所有方法
for (Method m : interfaces[0].getMethods()) {
Class<?>[] params = m.getParameterTypes();
StringBuffer paramNames = new StringBuffer();// 参数名: 类型 + 名字
StringBuffer paramValues = new StringBuffer();// 参数名:名字
StringBuffer paramClasses = new StringBuffer();// 参数的类型
for (int i = 0; i < params.length; i++) {
Class clazz = params[i];
String type = clazz.getName();
String paramName = toLowerFirstCase(clazz.getSimpleName());
paramNames.append(type + " " + paramName);
paramValues.append(paramName);
paramClasses.append(clazz.getName() + ".class");
if (i > 0 && i < params.length - 1) {
paramNames.append(",");
paramValues.append(",");
paramClasses.append(",");
}
}
// 生成实现方法的代码
sb.append("public " + m.getReturnType().getName() + " " + m.getName() + "(" + paramNames + ") {" + ln);
sb.append("try { " + ln);
sb.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\""+ m.getName() + "\", new Class[]{" + paramClasses + "});" + ln);
sb.append((hasReturnValue(m.getReturnType()) ? "return " : "") +
getCaseCode("this.h.invoke(this, m, new Object[]{" + paramValues + "})", m.getReturnType()) + ";" + ln);
sb.append("} catch(Error _ex) { }" + ln);
sb.append("catch(Throwable throwable) {" + ln);
sb.append(" throw new UndeclaredThrowableException(throwable); }}");
}
sb.append("}" + ln);
return sb.toString();
}

private static Map<Class, Class> mappings = new HashMap<>();
static {
mappings.put(int.class, Integer.class);
}

private static String getCaseCode(String code, Class<?> returnClass) {
if (mappings.containsKey(returnClass)) {
return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()";
}
return code;
}

private static String getReturnEmptyCode( Class<?> returnClass) {
if (mappings.containsKey(returnClass)) {
return "return 0;";
} else if (returnClass == void.class) {
return "";
} else {
return "return null;";
}
}

private static String toLowerFirstCase(String src) {
char[] chars = src.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}

private static boolean hasReturnValue(Class<?> clazz) {
return clazz != void.class;
}
}

该类的逻辑是通过反射获取接口中的所有方法,然后动态生成源码实现这些方法,在对源码进行编译生成对应的.class文件,通过自定义的ClassLoader加载.class文件,利用反射调用该类对象的构造器创建代理对象。

动态生成的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class $Proxy0 implements Person {
MHTInvocationHandler h;

public $Proxy0(MHTInvocationHandler var1) {
this.h = var1;
}

public void findLove() {
try {
Method var1 = Person.class.getMethod("findLove");
this.h.invoke(this, var1, new Object[0]);
} catch (Error var2) {
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}

}
}

自定义的类加载器代码如下:

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
public class MHTClassLoader extends ClassLoader {

private File classPathFile;

public MHTClassLoader() {
String classPath = MHTClassLoader.class.getResource("").getPath();
this.classPathFile = new File(classPath);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String className = MHTClassLoader.class.getPackage().getName() + "." + name;
if (classPathFile != null) {
File classFile = new File(classPathFile, name.replaceAll("\\.","/") + ".class");
if (classFile.exists()) {
try(FileInputStream in = new FileInputStream(classFile); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
return defineClass(className, out.toByteArray(), 0, out.size());
} catch (Exception e) {
e.printStackTrace();
}
}
}
return null;
}
}

下面通过我们自己手写的动态代理来实现媒婆代理寻找相亲对象的功能,MHTMeipo的实现如下:

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
31
32
public class MHTMeipo implements MHTInvocationHandler{

private Object target;

public Object getInstance(Object target) {
this.target = target;
Class<?> clazz = target.getClass();
return MHTProxy.newProxyInstance(new MHTClassLoader(), clazz.getInterfaces(), this);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object obj = method.invoke(this.target, args);
after();
return obj;
}

private void before() {
System.out.println("我是媒婆现在给你寻找对象,已确认需求");
System.out.println("开始物色对象");
}

private void after() {
System.out.println("物色结束,您满意吗");
}

public static void main(String[] args) {
Person person = (Person)new MHTMeipo().getInstance(new Son());
person.findLove();
}
}

运行结果如下:

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%2011.png

2.CGLib实现动态代理

下面我们来看一下怎么通过CGlib实现动态代理, 还是以寻找相亲对象为例子:

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
31
32
33
34
35
public class CGLibMeipo implements MethodInterceptor {
public Object getInstance(Class<?> clazz) throws Exception {
Enhancer enhancer = new Enhancer();
// 要把哪个设置成即将生成的新类的父类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object obj = methodProxy.invokeSuper(o, objects);
after();
return obj;
}

private void before() {
System.out.println("我是媒婆现在给你寻找对象,已确认需求");
System.out.println("开始物色对象");
}

private void after() {
System.out.println("物色结束,您满意吗");
}

public static void main(String[] args) {
try {
Son obj = (Son) new CGLibMeipo().getInstance(Son.class);
obj.findLove();
} catch (Exception e) {
e.printStackTrace();
}
}
}

运行结果如下:

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%2012.png

我们给测试代码添加上一句代码,让CGLib代理后的.class文件写入磁盘,然后通过反编译来看一下:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
try {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\Resourse\\Java\\spring-study\\target\\classes\\chapter2\\section02\\topic04\\demo04\\");
Son obj = (Son) new CGLibMeipo().getInstance(Son.class);
obj.findLove();
} catch (Exception e) {
e.printStackTrace();
}
}

运行后可以看到三个class文件

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%2013.png

其中Son$$EnhancerByCGLIB$$f9965969是CGLib生成的代理类,继承了Son类

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class Son$$EnhancerByCGLIB$$f9965969 extends Son implements Factory {
private boolean CGLIB$BOUND;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$findLove$0$Method;
private static final MethodProxy CGLIB$findLove$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$finalize$1$Method;
private static final MethodProxy CGLIB$finalize$1$Proxy;
private static final Method CGLIB$equals$2$Method;
private static final MethodProxy CGLIB$equals$2$Proxy;
private static final Method CGLIB$toString$3$Method;
private static final MethodProxy CGLIB$toString$3$Proxy;
private static final Method CGLIB$hashCode$4$Method;
private static final MethodProxy CGLIB$hashCode$4$Proxy;
private static final Method CGLIB$clone$5$Method;
private static final MethodProxy CGLIB$clone$5$Proxy;

static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
Class var0;
ClassLoader var10000 = (var0 = Class.forName("chapter2.section02.topic04.demo01.Son$$EnhancerByCGLIB$$f9965969")).getClassLoader();
CGLIB$emptyArgs = new Object[0];
CGLIB$findLove$0$Proxy = MethodProxy.create(var10000, (CGLIB$findLove$0$Method = Class.forName("chapter2.section02.topic04.demo01.Son").getDeclaredMethod("findLove")).getDeclaringClass(), var0, "()V", "findLove", "CGLIB$findLove$0");
CGLIB$finalize$1$Proxy = MethodProxy.create(var10000, (CGLIB$finalize$1$Method = Class.forName("java.lang.Object").getDeclaredMethod("finalize")).getDeclaringClass(), var0, "()V", "finalize", "CGLIB$finalize$1");
CGLIB$equals$2$Proxy = MethodProxy.create(var10000, (CGLIB$equals$2$Method = Class.forName("java.lang.Object").getDeclaredMethod("equals", Class.forName("java.lang.Object"))).getDeclaringClass(), var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
CGLIB$toString$3$Proxy = MethodProxy.create(var10000, (CGLIB$toString$3$Method = Class.forName("java.lang.Object").getDeclaredMethod("toString")).getDeclaringClass(), var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
CGLIB$hashCode$4$Proxy = MethodProxy.create(var10000, (CGLIB$hashCode$4$Method = Class.forName("java.lang.Object").getDeclaredMethod("hashCode")).getDeclaringClass(), var0, "()I", "hashCode", "CGLIB$hashCode$4");
CGLIB$clone$5$Proxy = MethodProxy.create(var10000, (CGLIB$clone$5$Method = Class.forName("java.lang.Object").getDeclaredMethod("clone")).getDeclaringClass(), var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
}

final void CGLIB$findLove$0() {
super.findLove();
}

public final void findLove() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}

if (var10000 != null) {
var10000.intercept(this, CGLIB$findLove$0$Method, CGLIB$emptyArgs, CGLIB$findLove$0$Proxy);
} else {
super.findLove();
}
}
....

通过源码发现代理类重写了父类的所有方法,并且每个方法会有对应的MethodProxy,如果拦截器不为空代理类的findLove方法就会执行拦截器的intercept方法,我们设置的拦截器中通过MethodProxy调用了invokeSuper方法,所以MethodProxy很重要,看一下MethodProxy的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MethodProxy {
....
private Signature sig;
private String superName;
private FastClass f1;
private FastClass f2;
private int i1;
private int i2;
.....
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
return this.f2.invoke(this.i2, obj, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
}
}
....
}

可以看到invokeSuper中获取了对应了FastClass并执行了被代理类的方法,之前CGLib生成的三个class文件其中两个就分别是代理类的FastClass和被代理类的FastClass。因此我们可以知道CGLib代理方法的效率之所以比JDK高,是因为CGLib采用了FastClass机制。简单来说就是它为代理类和被代理类都生成了一个类,这个类会给代理类和被代理类的方法生成一个index,通过这个index,FastClass就可以直接定位到需要调用的方法进行调用,省去了反射调用,所以调用效率比JDK反射调用高。

并且FastClass不是和代理类一起生成的,而是在第一次执行MehodProxy的invoke或invokeSuper方法时生成的,并放在了缓存中,缓存中没有才会重写生成FastClass。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class MethodProxy {
private Signature sig;
private String superName;
private FastClass f1;
private FastClass f2;
private int i1;
private int i2;

public static MethodProxy create(ClassLoader loader, Class c1, Class c2, String desc, String name1, String name2) {
final Signature sig1 = new Signature(name1, desc);
Signature sig2 = new Signature(name2, desc);
FastClass f1 = helper(loader, c1);// 缓存中没有才会重写生成FastClass
FastClass f2 = helper(loader, c2);
int i1 = f1.getIndex(sig1);
int i2 = f2.getIndex(sig2);
MethodProxy proxy;
if (i1 < 0) {
proxy = new MethodProxy() {
public Object invoke(Object obj, Object[] args) throws Throwable {
throw new IllegalArgumentException("Protected method: " + sig1);
}
};
} else {
proxy = new MethodProxy();
}

proxy.f1 = f1;
proxy.f2 = f2;
proxy.i1 = i1;
proxy.i2 = i2;
proxy.sig = sig1;
proxy.superName = name2;
return proxy;
}

private static FastClass helper(ClassLoader loader, Class type) {
Generator g = new Generator();
g.setType(type);
g.setClassLoader(loader);
AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
if (fromEnhancer != null) {
g.setNamingPolicy(fromEnhancer.getNamingPolicy());
g.setStrategy(fromEnhancer.getStrategy());
g.setAttemptLoad(fromEnhancer.getAttemptLoad());
}

return g.create();
}
.....

3.CGLib和JDK动态代理的对比

  1. JDK动态代理实现了被代理对象的接口, CGLIB动态代理继承了被代理对象
  2. JDK和cglib动态代理都在运行时生成字节码,JDK代理直接写Class字节码,CGLIB使用ASM框架写Class字节码,其代理实现更复杂,生成的代理类比JDK动态代理效率低
  3. JDK动态代理是通过反射机制调用的,而CGLIB是通过FastClass机制直接调用的,所以CGLib代理执行的效率更高。

Spring中的代理模式

1.代理模式在Spring中的应用

我们来看一下Spring中ProxyFactoryBean中的getObject方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.....
private boolean singleton = true;
.....
@Nullable
public Object getObject() throws BeansException {
this.initializeAdvisorChain();
if (this.isSingleton()) {
return this.getSingletonInstance();
} else {
if (this.targetName == null) {
this.logger.info("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the 'targetName' property.");
}

return this.newPrototypeInstance();
}
}
....

可以看到getObject方法中主要调用了getSingletonInstance和newPrototypeInstance方法,默认情况下Spring代理生成的Bean都是单例对象。

Spring动态代理实现Aop有两个非常重要的类JDKDynamicAopProxy和CglibAopProxy,类图如下所示:

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%2014.png

Spring的代理选择原则是:当Bean有实现接口时会使用JDK的动态代理,当Bean没有实现接口时,Spring会选择CGlib代理,Spring可以通过以下配置强制使用AOP代理

1
<aop:aspectj-autoproxy proxy-target-class="true">

总结

静态代理和动态代理的区别

  1. 静态代理需要手动完成代理操作,如果被代理类增加了方法,代理类也得增加,不符合开闭原则
  2. 动态代理在运行时动态生成代码,取消了对被代理类的扩展限制,符合开闭原则
  3. 动态代理若要对目标增强的逻辑进行扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码

代理模式的优缺点

优点:

  1. 代理类能够实现代理对象和真实被调用对象分离
  2. 降低了耦合性扩展性好
  3. 起到了保护目标对象的作用
  4. 可以增强目标对象的功能

缺点:

  1. 会造成系统设计中类的数量增加
  2. 在客户端和目标对象之间增加了一个代理对象,会导致请求处理速度变慢
  3. 增加了系统的复杂度

委派模式

委派模式也叫Delegate Pattern 不属于 GoF 23种设计模式。委派模式的基本作用就是负责任务的调用和分配。下面举个例子:老板给项目经理下达任务,项目经理根据情况给每个工人派发任务,待工人完成任务后,项目经理再向老板汇报结果。

首先创建一个IEmployee接口

1
2
3
public interface IEmployee {
void doing(String command);
}

然后创建员工A和B实现该接口

1
2
3
4
5
6
public class EmpolyeeA implements IEmployee {
@Override
public void doing(String command) {
System.out.println("我是员工A, 我现在开始干" + command + "工作");
}
}
1
2
3
4
5
6
public class EmpolyeeB implements IEmployee {
@Override
public void doing(String command) {
System.out.println("我是员工A, 我现在开始干" + command + "工作");
}
}

创建经理类Leader分配工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Leader implements IEmployee {

private Map<String, IEmployee> targets = new HashMap<>();

public Leader() {
targets.put("加密", new EmpolyeeA());
targets.put("登录", new EmpolyeeB());
}

@Override
public void doing(String command) {
targets.get(command).doing(command);
}
}

创建Boss类下达命令

1
2
3
4
5
public class Boss {
public void command(String command, Leader leader) {
leader.doing(command);
}
}

测试代码如下:

1
2
3
4
5
public class Client {
public static void main(String[] args) {
new Boss().command("登录", new Leader());
}
}

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%2015.png

下面我们再来还原以下Spring MVC中 DispatcherServlet是怎样实现委派模式的。首先创建几个Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MemberController {
public void getMemberById(String mid) {

}
}
public class OrderController {
public void getOrderById(String mid) {

}
}
public class SystemController {
public void logout() {

}
}

然后创建DispatcherServlet类实现HttpServlet接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class DispatcherServlet extends HttpServlet {
private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
String uri = request.getRequestURI();
String mid = request.getParameter("mid");
if ("getMemberById".equals(uri)) {
new MemberController().getMemberById(mid);
} else if ("getOrderById".equals(uri)) {
new OrderController().getOrderById(mid);
} else if ("logout".equals(uri)) {
new SystemController().logout();
} else {
response.getWriter().write("404 Not Found!!!!");
}
}

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
doDispatch(req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
}

最后我们只需要将这个DispatcherServlet配置到web.xml文件中就行了,这样就实现了一个完整的委派模式。在Spring源码中以Delegate结尾的地方都实现了委派模式,比如BeanDefinitionParserDelegate,它可以根据不同的类型委派不同的逻辑解析BeanDefinition

策略模式

策略模式就是对算法家族进行封装,让他们之间能够相互替换但是不会影响使用算法的用户。策略模式的应用场景有:

  1. 系统中有很多类,他们的区别只有行为不同
  2. 一个系统需要在几种算法中动态的选择一种

使用用策略模式的业务场景

1.选择优惠的业务

比如说要开设一门课程,然后这门课程可以有很多的优惠策略比如促销、拼团。下面用代码来模拟以下,首先创建一个促销的策略接口:

1
2
3
public interface PromotionStrategy {
void doPromotion();
}

然后分别创建优惠券、返现、拼团、无优惠等策略类

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
public class CouponStrategy implements PromotionStrategy{
@Override
public void doPromotion() {
System.out.println("领取优惠券,课程的价格直减领取优惠券面值");
}
}

public class CashbackStrategy implements PromotionStrategy {
@Override
public void doPromotion() {
System.out.println("返现促销,返回的金额转到支付宝账号");
}
}

public class GroupbuyStrategy implements PromotionStrategy {
@Override
public void doPromotion() {
System.out.println("拼团,满20人成团,全团享受团购价");
}
}

public class EmptyStrategy implements PromotionStrategy {
@Override
public void doPromotion() {
System.out.println("无促销活动");
}
}

创建促销活动类:

1
2
3
4
5
6
7
8
9
10
11
public class PromotionActivity {
private PromotionStrategy promotionStrategy;

public PromotionActivity(PromotionStrategy strategy) {
this.promotionStrategy = strategy;
}

public void execute() {
promotionStrategy.doPromotion();
}
}

编写客户端测试类:

1
2
3
4
5
6
7
8
public class Client {
public static void main(String[] args) {
PromotionActivity activity618 = new PromotionActivity(new CouponStrategy());
PromotionActivity activity1111 = new PromotionActivity(new CashbackStrategy());
activity618.execute();
activity1111.execute();
}
}

可以发现上面的客户端代码不符合实际的业务场景,真实情况下我们会让客户来选择优惠类型,然后执行对应的优惠方案,所以客户端代码可以做如下修改:

1
2
3
4
5
6
7
8
9
10
11
12
public class Client {
public static void main(String[] args) {
PromotionActivity promotionActivity = null;
String promotionKey = "COUPON";
if (StringUtils.equals(promotionKey, "COUPON")) {
promotionActivity = new PromotionActivity(new CouponStrategy());
} else if(StringUtils.equals("CASHBACK", promotionKey)) {
promotionActivity = new PromotionActivity(new CashbackStrategy());
}
promotionActivity.execute();
}
}

改进后用户就能根据自己的需求选择不同的优惠策略,但是随着业务积累促销活动就会越来越多,采用if else这种方式客户端的代码就会变成”一坨“难以维护。我们可以使用单例模式和工厂模式来进行优化。下面创建PromotionStrategyFactory类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class PromotionStrategyFactory {

private static final PromotionStrategy NON_PROMOTION = new EmptyStrategy();

private static Map<String, PromotionStrategy> PROMOTION_STRATEGY_MAP = new HashMap<>();
static {
PROMOTION_STRATEGY_MAP.put(PromotionKey.COUPON, new CouponStrategy());
PROMOTION_STRATEGY_MAP.put(PromotionKey.CASHBACK, new CashbackStrategy());
PROMOTION_STRATEGY_MAP.put(PromotionKey.GROUPBY, new GroupbuyStrategy());
}

private PromotionStrategyFactory() {}

public static PromotionStrategy getPromotionStrategy(String key) {
PromotionStrategy promotionStrategy = PROMOTION_STRATEGY_MAP.get(key);
return promotionStrategy == null ? NON_PROMOTION : promotionStrategy;
}

private interface PromotionKey {
String COUPON = "COUPON";
String CASHBACK = "CASHBACK";
String GROUPBY = "GROUPBY";
}
}

这样客户端代码就可以改造为:

1
2
3
4
5
6
7
public class Client2 {
public static void main(String[] args) {
String promotionKey = "COUPON";
PromotionStrategy promotionStrategy = PromotionStrategyFactory.getPromotionStrategy(promotionKey);
promotionStrategy.doPromotion();
}
}

2.选择支付方式的业务

我们在手机上点完外卖下单都会有选择支付方式的功能,下面我们使用策略模式来实现这个业务场景,首先创建Payment接口来定义支付规范和逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class Payment {
// 获取支付类型
public abstract String getPaytype();
// 查询余额
public abstract double queryBalance(String uid);
// 扣款支付
public PayState pay(String uid, double amount) {
if (queryBalance(uid) < amount) {
return new PayState(500, "支付失败","余额不足");
}
return new PayState(200, "支付成功", "支付金额:" + amount);
}
}

创建支付状态的包装类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class PayState {
private int code;
private Object data;
private String msg;

public PayState(int code, Object data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
}

@Override
public String toString() {
return "PayState{" +
"code=" + code +
", data=" + data +
", msg='" + msg + '\'' +
'}';
}
}

接下来创建各种支付类:微信、支付宝、银联、京东白条

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class Alipay extends Payment{
@Override
public String getPaytype() {
return "支付宝";
}

@Override
public double queryBalance(String uid) {
return 900;
}
}

public class WechatPay extends Payment{
@Override
public String getPaytype() {
return "微信";
}

@Override
public double queryBalance(String uid) {
return 256;
}
}

public class UnionPay extends Payment {
@Override
public String getPaytype() {
return "银联";
}

@Override
public double queryBalance(String uid) {
return 120;
}
}

public class JDPay extends Payment {
@Override
public String getPaytype() {
return "京东";
}

@Override
public double queryBalance(String uid) {
return 500;
}
}

创建支付策略的管理类PayStrategy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class PayStrategy {

private final static String ALI_PAY = "AliPay";
private final static String WECHAT_PAY = "WechatPay";
private final static String UNION_PAY = "UNION_PAY";
private final static String JD_PAY = "JDpay";
private final static String DEFAULT_PAY = "Alipay";

private static Map<String, Payment> payStrategy = new HashMap<>();
static {
payStrategy.put(ALI_PAY, new Alipay());
payStrategy.put(WECHAT_PAY, new WechatPay());
payStrategy.put(UNION_PAY, new UnionPay());
payStrategy.put(JD_PAY, new JDPay());
payStrategy.put(DEFAULT_PAY, new Alipay());
}

public static Payment get(String key) {
if (!payStrategy.containsKey(key)) {
return payStrategy.get(DEFAULT_PAY);
}
return payStrategy.get(key);
}
}

创建订单类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Order {

private String uid;
private String orderId;
private double amount;

public Order(String uid, String orderId, double amount) {
this.uid = uid;
this.orderId = orderId;
this.amount = amount;
}

public PayState pay() {
return pay(PayStrategy.DEFAULT_PAY);
}

public PayState pay(String key) {
Payment payment = PayStrategy.get(key);
System.out.println("欢迎使用" + payment.getPaytype());
System.out.println("本次交易金额为" + amount + "开始扣款");
return payment.pay(uid, amount);
}
}

测试代码如下:

1
2
3
4
5
6
7
public class Client {
public static void main(String[] args) {
// 创建订单
Order order = new Order("1","zz197320", 63.2);
System.out.println(order.pay(PayStrategy.JD_PAY));
}
}

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%2016.png

JDK源码中策略模式的体现

首先我们比较常用的比较器Comparator接口,其compare方法就是策略模式的体现

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%2017.png

Comparator接口有很多实现,我们经常将该接口作为传入参数实现排序策略,比如Arrays.sort()

Spring中策略模式的体现

下面是Spring的Resource接口

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
31
32
33
34
35
36
public interface Resource extends InputStreamSource {
boolean exists();

default boolean isReadable() {
return this.exists();
}

default boolean isOpen() {
return false;
}

default boolean isFile() {
return false;
}

URL getURL() throws IOException;

URI getURI() throws IOException;

File getFile() throws IOException;

default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(this.getInputStream());
}

long contentLength() throws IOException;

long lastModified() throws IOException;

Resource createRelative(String var1) throws IOException;

@Nullable
String getFilename();

String getDescription();
}

该接口有很多的实现类

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%2018.png

Spring的初始化也采用了策略模式,不同的类型采用不同的初始化策略,看一下InstantiationStrategy接口的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface InstantiationStrategy {

Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner)
throws BeansException;

Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
Constructor<?> ctor, Object... args) throws BeansException;

Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
@Nullable Object factoryBean, Method factoryMethod, Object... args)
throws BeansException;

}

Spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%200341e824ec254ea9aa871bf8fad0e8e1/Untitled%2019.png

通过类图我们可以发现该接口下有两个策略,并且策略之间可以继承使用。

策略模式的优缺点

策略模式的优点如下:

  1. 符合开闭原则
  2. 可以避免过多了if else语句
  3. 可以提高算法的保密性和安全性

策略模式的缺点如下:

  1. 客户端需要提前只要有哪些策略并选择一个策略
  2. 代码中会有很多策略类,增加代码维护难度

委派模式和策略模式的综合应用

回顾我们之前在委派模式中写的DispatcherServlet其中有大量的if else, 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class DispatcherServlet extends HttpServlet {
private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
String uri = request.getRequestURI();
String mid = request.getParameter("mid");
if ("getMemberById".equals(uri)) {
new MemberController().getMemberById(mid);
} else if ("getOrderById".equals(uri)) {
new OrderController().getOrderById(mid);
} else if ("logout".equals(uri)) {
new SystemController().logout();
} else {
response.getWriter().write("404 Not Found!!!!");
}
}

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
doDispatch(req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
}

我们尝试使用策略模式来进行改造:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class DispatcherServlet extends HttpServlet {

private List<Handler> handlerMapping = new ArrayList<>();

public void init() throws ServletException {
try {
Class<MemberController> memberControllerClass = MemberController.class;
Handler handler = new Handler();
handler.setController(memberControllerClass.newInstance());
handler.setMethod(memberControllerClass.getMethod("getMemberById", String.class));
handler.setUri("/web/getMemberById.json");
handlerMapping.add(handler);
} catch (Exception e) {

}
}

private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 每个url对应一个servlet
String uri = request.getRequestURI();
String mid = request.getParameter("mid");

// 拿到servlet后通过url通过这个url找到对应的java方法
Handler handle = null;
for (Handler h : handlerMapping) {
if (StringUtils.equals(uri, h.uri)) {
handle = h;
break;
}
}

// 通过反射调用对应的Method
Object obj = handle.getMethod().invoke(handle.getController(), request.getParameter("mid"));
}

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
doDispatch(req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}

class Handler {
private Object controller;
private Method method;
private String uri;

public Object getController() {
return controller;
}

public Method getMethod() {
return method;
}

public String getUri() {
return uri;
}

public void setController(Object controller) {
this.controller = controller;
}

public void setMethod(Method method) {
this.method = method;
}

public void setUri(String uri) {
this.uri = uri;
}
}
}

模板模式

模板模式是指定义一个算法框架,允许子类为一个或多个步骤提供实现。模板模式可以在不改变算法结构的情况下重新定义算法的某些步骤,属于行为型设计模式。

模板模式使用于以下场景:

  • 一次性实现一个算法的不可变部分,并将可变部分留给子类。
  • 各个子类的公共行为被提取出来并集中到一个公共父类中,从而避免代码重复。

下面使用模板模式来JDBC操作的业务场景

创建一个JdbcTemplate封装所有的Jdbc操作,以查询唯一,查询不同的数据库表,每次查询只是映射的DO对象不同,而整体流程是一致的,因此可以使用模板模式进行设计。

首先创建IRowMapper接口:

1
2
3
public interface IRowMapper<T> {
T mapRow(ResultSet rs, int rowNum) throws Exception;
}

再创建封装了所有处理有流程的抽象类JdbcTemplate

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public abstract class JdbcTemplate {
private DataSource dataSource;

public JdbcTemplate(DataSource dataSource) {
this.dataSource = dataSource;
}

public List<?> executeQuery(String sql, IRowMapper<?> rowMapper, Object[] values) {
try {
// 获取连接
Connection conn = this.getConnection();
// 创建语句集
PreparedStatement pstm = this.createPrepareStatement(conn,sql);
// 执行语句集
ResultSet rs = this.executeQuery(pstm, values);
// 处理结果集
List<?> result = this.paresResultSet(rs, rowMapper);
// 关闭结果集
this.closeResultSet(rs);
// 关闭语句集
this.closeStatement(pstm);
// 关闭连接
this.closeConnection(conn);
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

protected void closeConnection(Connection conn) throws Exception{
conn.close();
}

protected void closeStatement(PreparedStatement pstm) throws Exception {
pstm.close();
}

protected void closeResultSet(ResultSet rs) throws Exception {
rs.close();
}

protected List<?> paresResultSet(ResultSet rs, IRowMapper<?> rowMapper) throws Exception {
List<Object> result = new ArrayList<>();
int rowNum = 1;
while (rs.next()) {
result.add(rowMapper.mapRow(rs, rowNum++));
}
return result;
}

protected PreparedStatement createPrepareStatement(Connection conn, String sql) throws Exception {
return conn.prepareStatement(sql);
}

protected Connection getConnection() throws Exception {
return this.dataSource.getConnection();
}

protected ResultSet executeQuery(PreparedStatement pstm, Object[] values) throws Exception {
for (int i = 0; i < values.length; i++) {
pstm.setObject(i + 1, values[i]);
}
return pstm.executeQuery();
}
}

创建实体对象类Student

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class Student implements Serializable {

private Long id;
private String name;
private Integer age;

public Student() {
}

public Student(String name, Integer age) {
this.name = name;
this.age = age;
}

public Student(Long id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}

创建数据库操作类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class StudentDAO extends JdbcTemplate{

public StudentDAO(DataSource dataSource) {
super(dataSource);
}

public List<?> selectAll() {
String sql = "select * from students";
return executeQuery(sql, (rs, rowNum)->{
Student student = new Student();
student.setId(rs.getLong("id"));
student.setAge(rs.getInt("age"));
student.setName(rs.getString("name"));
return student;
}, new Object[]{});
}
}

客户端测试代码如下:

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
public class Client {

public static MysqlConnectionPoolDataSource dataSource = null;

static {
InputStream in = Client.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
try {
properties.load(in);
dataSource = new MysqlConnectionPoolDataSource();
dataSource.setURL(properties.getProperty("url"));
dataSource.setUser(properties.getProperty("username"));
dataSource.setPassword(properties.getProperty("password"));
} catch (IOException e) {
e.printStackTrace();
}

}

public static void main(String[] args) {
StudentDAO studentDAO = new StudentDAO(dataSource);
List<?> result = studentDAO.selectAll();
System.out.println(result);
}
}

模板模式在JDK中的体现

在JDK中AbstractList类就是模板模式

1
abstract public E get(int index);

其中的get方法交给子类来实现,同理也有AbstractSet、AbstractMap,HttpServlet也是模板方法的抽象实现

模板模式的优缺点

优点:

  1. 可以将相同处理逻辑的代码放在父类处理逻辑当中,提高代码复用性。
  2. 将不同的代码放到不同的子类当中提升的代码的扩展性
  3. 父类中抽取了各种子类的相同代码,提供了很好的代码复用平台,符合开闭原则

缺点

  1. 抽象类需要子类来实现,导致类数量过多
  2. 类数量增加间接性的导致了系统的复杂性
  3. 如果父类添加新的抽象方法子类都要改一遍

适配器模式详解

适配器模式是指将一个类的接口转化为另一个用户期望的接口,使原本不兼容的类可以一起工作,属于结构型设计模式。

适配器模式适用于以下场景:

  1. 已经存在的类的方法和需求不匹配的情况
  2. 适配器模式不是在软件初始阶段考虑的设计模式,是随着软件的发展,由于不同产品、不同厂家造成功能类似而接口不同的解决方案

在生活中的应用场景就是各种电源转换头,中国民用电是220V,而手机充电需要5v直流电,下面来模拟一下这个场景:

首先创建AC220类提供交流电输出:

1
2
3
4
5
6
public class AC220 {
public int outPutAC220V() {
System.out.println("输出交流电220V");
return 220;
}
}

再创建DC5接口,表示5V直流电:

1
2
3
public interface DC5 {
int outputDC5();
}

创建适配器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class PowerAdapter implements DC5 {

private AC220 ac220;

public PowerAdapter(AC220 ac220) {
this.ac220 = ac220;
}

@Override
public int outputDC5() {
int input = ac220.outPutAC220V();
int adapterOutput = input / 44;
System.out.println("使用PowerAdapter输入AC" + input + "V" + "输出" + adapterOutput + "V");
return adapterOutput;
}
}

客户端测试类:

1
2
3
4
5
6
7
public class Client {
public static void main(String[] args) {
AC220 ac220 = new AC220();
PowerAdapter powerAdapter = new PowerAdapter(ac220);
powerAdapter.outputDC5();
}
}

第三方登录自由适配的业务场景

开发系统时可能一开始只有账号和密码登录,但是随着业务的发展会出现QQ、微信、微博等多种登录方式,虽然登录方式丰富了,但是处理逻辑可以不改,都是将登录状态保存到session,遵循开闭原则。

创建统一返回结果类ResultMsg

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
31
32
33
34
35
36
public class ResultMsg {

private int code;
private String msg;
private Object data;

public ResultMsg(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public Object getData() {
return data;
}

public void setData(Object data) {
this.data = data;
}
}

创建老的登录代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SiginService {

// 注册方法
public ResultMsg regist(String username, String password) {
return new ResultMsg(200, "注册成功", new Member());
}

// 登录方法
public ResultMsg login(String username, String password) {
return null;
}

}

创建Member类

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
31
32
33
34
35
36
37
38
39
40
public class Member {

private String username;
private String password;
private String mid;
private String info;

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getMid() {
return mid;
}

public void setMid(String mid) {
this.mid = mid;
}

public String getInfo() {
return info;
}

public void setInfo(String info) {
this.info = info;
}

}

再创建新的类来继承原来的代码

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
public class SigninForThirdService extends SiginService {

public ResultMsg loginForQQ(String openId) {
// openId为一个全局唯一的Id,可以将其当作用户名
// 密码使用默认值
// 现在系统中注册一个用户
return loginForRegist(openId);
}

public ResultMsg loginForWechat(String openId) {
return null;
}

public ResultMsg loginForToken(String token) {
return null;
}

public ResultMsg loginForTelphone(String phone, String code) {
return null;
}

private ResultMsg loginForRegist(String openId) {
super.regist(openId, null);
return super.login(openId, null);
}
}

这样通过继承我们就简单实现了代码的复用。

我们也可以为不同的登录方式设置对应的Adapter,首先创建LoginAdapter接口

1
2
3
4
public interface LoginAdapter {
boolean support(Object object);
ResultMsg login(Object[] param, Object adapter);
}

创建不同方式的Adapter

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
public class LoginForQQAdapter implements LoginAdapter {
@Override
public boolean support(Object object) {
return object instanceof LoginForQQAdapter;
}

@Override
public ResultMsg login(Object[] param, Object obj) {
SiginService siginService = (SiginService) obj;
siginService.regist((String) param[0], null);
return siginService.login((String) param[0], null);
}
}

public class LoginForSinaAdapter implements LoginAdapter {
@Override
public boolean support(Object object) {
return object instanceof LoginForSinaAdapter;
}

@Override
public ResultMsg login(Object[] param, Object adapter) {
return null;
}
}

创建第三方登录兼容接口

1
2
3
4
public interface IPasswordForThird {
ResultMsg loginForQQ(Object[] params);
ResultMsg loginForSinaChat(Object[] params);
}

创建实现类

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
public class PassportForThirdAdapter extends SiginService implements IPasswordForThird{
@Override
public ResultMsg loginForQQ(Object[] params) {
return processLogin(params, LoginForQQAdapter.class);
}

@Override
public ResultMsg loginForSinaChat(Object[] params) {
return processLogin(params, LoginForSinaAdapter.class);
}

public ResultMsg processLogin(Object[] params, Class<? extends LoginAdapter> clazz) {
try {
LoginAdapter adapter = clazz.newInstance();
if (adapter.support(adapter)) {
return adapter.login(params, this);
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

创建测试类

1
2
3
4
5
6
public class Client2 {
public static void main(String[] args) {
PassportForThirdAdapter adapter = new PassportForThirdAdapter();
System.out.println(adapter.loginForQQ(new Object[]{"dasdasdasdasd"}));
}
}

适配器模式的优缺点:

优点:

  1. 提升类的复用性
  2. 目标类和适配器类解耦,提高程序的扩展性

缺点:

  1. 编写过程需要全面考虑,增加了代码的复杂性
  2. 增加了代码的阅读难度,降低了代码的可读性,过多使用适配器会使代码凌乱

装饰者模式详解

装饰者模式是指在不改变原有对象的基础上,将功能附加到对象上,提供比继承更加富有弹性的方案,属于结构型模式。

装饰器适用于以下场景:

  1. 扩展一个类的功能或者给一个类附加职责
  2. 动态的给一个类添加功能,这些功能可以动态的撤销

在生活中也有类似的场景,比如给买煎饼可以加蛋,房子可以装修。下面我们以买煎饼加蛋为例子,首先创建一个煎饼类Battercake

1
2
3
4
5
6
7
8
9
10
11
public class Battercake {

protected String getMsg() {
return "煎饼";
}

protected int getPrice() {
return 5;
}

}

假设我要加一个鸡蛋,如果使用继承是这样实现的,创建一个BattercakeWithEgg继承Battercake

1
2
3
4
5
6
7
8
9
10
11
12
public class BattercakeWithEgg extends Battercake{
@Override
protected String getMsg() {

return super.getMsg() + "+1个鸡蛋";
}

@Override
protected int getPrice() {
return super.getPrice() + 1;
}
}

如果我们需要加一个鸡蛋,然后再加一个香肠,使用继承实现,创建

1
2
3
4
5
6
7
8
9
10
11
public class BattercakeWithEggAndSausage extends BattercakeWithEgg{
@Override
protected String getMsg() {
return super.getMsg() + "+1个香肠";
}

@Override
protected int getPrice() {
return super.getPrice() + 2;
}
}

编写客户端测试代码

1
2
3
4
5
6
7
8
9
10
public class client {
public static void main(String[] args) {
Battercake battercake = new Battercake();
System.out.println(battercake.getMsg() + "总价格:" + battercake.getPrice());
BattercakeWithEgg battercakeWithEgg = new BattercakeWithEgg();
System.out.println(battercakeWithEgg.getMsg() + "总价格:" + battercakeWithEgg.getPrice());
BattercakeWithEggAndSausage battercakeWithEggAndSausage = new BattercakeWithEggAndSausage();
System.out.println(battercakeWithEggAndSausage.getMsg() + "总价格:" + battercakeWithEggAndSausage.getPrice());
}
}

运行结果如下所示:

Untitled

如果我们有新的需求比如加两个鸡蛋,加一个香肠,只能再创建一个类做定制。这样显然是不科学的,下面使用装饰器模式来解决这个问题。

首先创建Battercake抽象类

1
2
3
4
public abstract class Battercake {
protected abstract String getMsg();
protected abstract int getPrice();
}

创建最基础的煎饼类

1
2
3
4
5
6
7
8
9
10
11
public class BaseBatterCake extends Battercake{
@Override
protected String getMsg() {
return "煎饼";
}

@Override
protected int getPrice() {
return 5;
}
}

然后再创建一个扩展套餐的抽象装饰类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class BattercakeDecorator extends Battercake{

private Battercake battercake;

public BattercakeDecorator(Battercake battercake) {
this.battercake = battercake;
}

protected abstract void doSomething();

@Override
protected String getMsg() {
return this.battercake.getMsg();
}

@Override
protected int getPrice() {
return this.battercake.getPrice();
}
}

创建鸡蛋装饰类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class EggDecorator extends BattercakeDecorator {

public EggDecorator(Battercake battercake) {
super(battercake);
}

@Override
protected void doSomething() {

}

@Override
protected String getMsg() {
return super.getMsg() + "1个鸡蛋";
}

@Override
protected int getPrice() {
return super.getPrice() + 1;
}
}

创建香肠装饰类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SausageDecorator extends BattercakeDecorator {

public SausageDecorator(Battercake battercake) {
super(battercake);
}

@Override
protected void doSomething() {

}

@Override
protected String getMsg() {
return super.getMsg() + "+1个香肠";
}

@Override
protected int getPrice() {
return super.getPrice() + 2;
}
}

测试代码如下

1
2
3
4
5
6
7
8
9
10
11
public class Client {
public static void main(String[] args) {
BaseBatterCake baseBatterCake = new BaseBatterCake();
EggDecorator eggDecorator = new EggDecorator(baseBatterCake);
EggDecorator eggDecorator1 = new EggDecorator(eggDecorator);
System.out.println(eggDecorator.getMsg() + "总价格:" + eggDecorator.getPrice());
System.out.println(eggDecorator1.getMsg() + "总价格:" + eggDecorator1.getPrice());
SausageDecorator sausageDecorator = new SausageDecorator(eggDecorator1);
System.out.println(sausageDecorator.getMsg() + "总价格:" + sausageDecorator.getPrice());
}
}

Untitled

装饰者模式的优缺点:

优点:

  1. 装饰者模式是继承的补充,更加灵活,可以在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用
  2. 使用不同的装饰类以及装饰类的排列组合可以实现不同的效果
  3. 符合开闭原则

缺点:

  1. 会出现更多的代码,更多的类,增加了程序的复杂性
  2. 动态装饰时,多层的装饰会更加复杂

观察者模式

观察者模式定义了对象之间的一对多的依赖关系,让多个观察者对象同时监听一个主体对象,当主体对象发生变化时,他的所有依赖者都会收到通知并更新,属于行为型模式。观察者模式也叫做发布订阅模式,主要用于在关联行为之间建立一套触发机制的场景。

微信朋友圈的动态通知、邮件通知、广播通知、桌面程序的事件响应都是观察者模式的体现。

下面模拟咕泡学院学员发送提问给老师的场景, 首先创建GPer类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class GPer extends Observable {
private String name = "GPer生态圈";
private static GPer gper = null;
private GPer() {}

public static GPer getInstance() {
if (gper == null) {
gper = new GPer();
}
return gper;
}

public String getName() {
return name;
}

public void publishQuestion(Question question) {
System.out.println(question.getUsername() + "在" + this.name + "上提交了一个问题。");
setChanged();
notifyObservers(question);
}
}

创建Question类

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
public class Question {
private String username;
private String content;

public Question(String username, String content) {
this.username = username;
this.content = content;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}
}

创建Teacher类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Teacher implements Observer {

private String name;

public Teacher(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
GPer gPer = (GPer) o;
Question question = (Question) arg;
System.out.println("================================");
System.out.println(name + "老师,你好!\n" + "您收到一个来自" +
gPer.getName() + "的提问:\n" + question.getContent() + "\n提问者: " + question.getUsername());
}
}

创建客户端测试类

1
2
3
4
5
6
7
8
public class Client {
public static void main(String[] args) {
Teacher teacher = new Teacher("大司马");
GPer gPer = GPer.getInstance();
gPer.addObserver(teacher);
gPer.publishQuestion(new Question("Warms", "程序员为啥这么卷?!"));
}
}

输出:

Untitled

下面来写一个鼠标点击的事件监听器,首先封装Event

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class Event {
// 事件源
private Object source;
// 请求对象
private Object target;
// 请求对象的具体回调方法
private Method callback;
// 触发的事件名称
private String trigger;
// 触发的事件的时间
private Date date;

public Event(Object target, Method callback) {
this.target = target;
this.callback = callback;
}

public Object getSource() {
return source;
}

public void setSource(Object source) {
this.source = source;
}

public String getTrigger() {
return trigger;
}

public Event setTrigger(String trigger) {
this.trigger = trigger;
return this;
}

public Object getTarget() {
return target;
}

public void setTarget(Object target) {
this.target = target;
}

public Method getCallback() {
return callback;
}

public void setCallback(Method callback) {
this.callback = callback;
}

public Date getDate() {
return date;
}

public void setDate(Date date) {
this.date = date;
}

@Override
public String toString() {
return "Event{" +
"source=" + source +
", target=" + target +
", callback=" + callback +
", trigger='" + trigger + '\'' +
", date=" + date +
'}';
}
}

然后创建事件监听器类,通过addLisenter方法向HashMap中注册需要监听的事件

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
31
32
33
34
35
36
37
38
39
40
public class EventLisenter {

protected Map<String, Event> events = new HashMap<>();

public void addLisenter(String eventType, Object target) {
try {
addLisenter(eventType, target, target.getClass().getMethod("on" + toUpperFirstCase(eventType), Event.class));
} catch (Exception e) {
e.printStackTrace();
}
}

private String toUpperFirstCase(String str) {
char[] chars = str.toCharArray();
chars[0] -= 32;
return new String(chars);
}

public void addLisenter(String eventType, Object target, Method callback) {
events.put(eventType, new Event(target, callback));
}

private void trigger(Event event) {
event.setSource(this);
event.setDate(new Date());
try {
if (event.getCallback() != null) {
event.getCallback().invoke(event.getTarget(), event);
}
} catch (Exception e) {
e.printStackTrace();
}
}

protected void trigger(String eventType) {
if (!events.containsKey(eventType)) return;
trigger(events.get(eventType).setTrigger(eventType));
}

}

创建需要被监听的对象Mouse继承EventLisenter ,调用trigger方法捞取对应的Event对象,通过反射触发回调方法

1
2
3
4
5
6
7
8
9
10
public class Mouse extends EventLisenter {
public void click() {
System.out.println("调用单击方法");
trigger(MouseEventType.CLICK);
}
public void doubleClick() {
System.out.println("调用双击方法");
trigger(MouseEventType.DOUBLE_CLICK);
}
}

创建事件类型

1
2
3
4
public interface MouseEventType {
String CLICK = "click";
String DOUBLE_CLICK = "doubleClick";
}

创建事件触发时需要通知的对象

1
2
3
4
5
6
7
8
9
10
public class MouseCallBack {
public void onClick(Event e) {
System.out.println("==========触发鼠标单机事件==========");
System.out.println(e);
}
public void onDoubleClick(Event e) {
System.out.println("==========触发鼠标双击事件==========");
System.out.println(e);
}
}

创建客户端测试类

1
2
3
4
5
6
7
8
9
10
public class Client {
public static void main(String[] args) {
Mouse mouse = new Mouse();
MouseCallBack callBack = new MouseCallBack();
mouse.addLisenter(MouseEventType.CLICK, callBack);
mouse.addLisenter(MouseEventType.DOUBLE_CLICK, callBack);
mouse.click();
mouse.doubleClick();
}
}

输出:

Untitled

观察者和被观察者的优缺点:

优点:

(1)观察者和被观察者之间建立了一个抽象的耦合

(2)观察者模式支持广播通信

缺点

(1)观察者之间有过多的细节依赖、时间消耗多,程序复杂性高

(2)使用不当会出现循环调用