文章目录
设计模式
一. 单例模式
1.1 单例模式是什么
- 单例模式保证整个应用中某个实例有且只有一个,即类似于一个全局的静态变量,所有线程获取的都是一个实例。保证一个类仅有一个实例,并提供一个访问它的全局访问点。
1.2 单例模式的应用场景
- Windows的Task Manager(任务管理器)就是很典型的单例模式。我们无法打开两个任务管理器
- 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
当然肯定还有很多应用场景
1.3 单例模式的五种基本实现
- 单例模式的五种基本实现:饱汉模式,饿汉模式,DCL双锁检查,静态内部类,枚举类。
- 我的这篇博客中有讲解这五种实现:
单例模式五种实现方式——java
1.4 Spring中的单例模式实现
- Spring中实现单例模式使用的并不是上述的五种,而是使用单例注册表实现的
- 单例注册表实际上就是一个map,使用的是ConcurrentHashMap实现的,里面存放了已经实例过的单例bean。
- Spring实现单例的过程如下:
- 首先从单例注册表中查找是否已经存放单例实例,如果有的话就直接取出,然后返回。
- 如果没有的话就进入synchronized修饰的同步方法
- 再次检查单例注册表是否存在该bean的实例,如果有就取出返回
- 如果没有就开始创建该bean的实例,返回
- 简而言之就是用一个map存放注册过的单例bean,每次需要获取单例的时候就从里面取,没有的话就再创建。
- 使用ConcurrentHashMap的原因是为了保证线程安全,同时两次检查单例注册表也是保证线程安全。类似于DCL双锁检查。
下面为部分源码和注释,是搬运别人的:
public abstract class AbstractBeanFactory implements ConfigurableBeanFactory{
/**
* 充当了Bean实例的缓存,实现方式和单例注册表相同
*/
private final Map singletonCache=new HashMap();
public Object getBean(String name)throws BeansException{
return getBean(name,null,null);
}
...
public Object getBean(String name,Class requiredType,Object[] args)throws BeansException{
//对传入的Bean name稍做处理,防止传入的Bean name名有非法字符(或则做转码)
String beanName=transformedBeanName(name);
Object bean=null;
//手工检测单例注册表
Object sharedInstance=null;
//使用了代码锁定同步块,原理和同步方法相似,但是这种写法效率更高
synchronized(this.singletonCache){
sharedInstance=this.singletonCache.get(beanName);
}
if(sharedInstance!=null){
...
//返回合适的缓存Bean实例
bean=getObjectForSharedInstance(name,sharedInstance);
}else{
...
//取得Bean的定义
RootBeanDefinition mergedBeanDefinition=getMergedBeanDefinition(beanName,false);
...
//根据Bean定义判断,此判断依据通常来自于组件配置文件的单例属性开关
//<bean id="date" class="java.util.Date" scope="singleton"/>
//如果是单例,做如下处理
if(mergedBeanDefinition.isSingleton()){
synchronized(this.singletonCache){
//再次检测单例注册表
sharedInstance=this.singletonCache.get(beanName);
if(sharedInstance==null){
...
try {
//真正创建Bean实例
sharedInstance=createBean(beanName,mergedBeanDefinition,args);
//向单例注册表注册Bean实例
addSingleton(beanName,sharedInstance);
}catch (Exception ex) {
...
}finally{
...
}
}
}
bean=getObjectForSharedInstance(name,sharedInstance);
}
//如果是非单例,即prototpye,每次都要新创建一个Bean实例
//<bean id="date" class="java.util.Date" scope="prototype"/>
else{
bean=createBean(beanName,mergedBeanDefinition,args);
}
}
...
return bean;
}
}
二.代理模式
2.1 代理模式概述
定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
通俗的来讲代理模式就是我们生活中常见的中介。例如我要买房,那么我就要调用这个已有的买房的方法,但是呢买房之前还需要看房,看环境等一系列操作,因此为了省事,我就使用代理去调用这个买房的方法,但是使用代理不同的是,代理不光是仅仅调用买房的方法,买房之前还会执行看房的操作。
如下图所示:
客户类就是我,接口就是开放的买房的接口,委托类中就有买房的方法。
2.2 代理模式的作用
也许有人会说了,我不用代理,我就喜欢先去看房,看环境。这样难道不行吗?实际上是可以的,但是放到一个应用上的话就不是这么简单的问题了。
如果现在已经有一套买房系统(不使用代理),一般客户就是直接买房。但是如果客户突然有一天有看房看环境的需求怎么办?那么就必须修改委托类中的源码,增加功能方法,然后客户类进行调用。
但是如果我们之前就使用代理的方式进行买房,那么此时我完全不需要修改委托类,而是修改代理类,在代理类中增加功能即可,对客户而言还是调用代理,没有任何的改变。对应用程序而言,没有修改内部源码。
因此代理模式的作用有两个。
- 中介隔离作用:
在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
- 符合代码的开闭原则——对外扩展开放,对内修改关闭
代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
2.3 代理模式的分类及原理
代理模式分为静态代理和动态代理,动态代理中又有两种代理方法实现:JDK和CGLib。
2.3.1 静态代理
静态代理的意思就是程序员自己写一个代理类去实现代理功能。也就是说在程序运行之前这个类的class文件就已经存在了。
优点:
- 可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
缺点:
- 我们得为接口中每一个服务方法都得创建代理类,如果接口中的服务方法增加了,就必须再新增一个代理类来代理这个方法。工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
2.4.1 动态代理
刚刚我们提到静态代理是要提前为每一个服务方法写好代理类,并且服务方法增加的话,就需要增加代理类来代理,这样的工作量太大了,因此动态代理应运而生。
动态代理实现的功能就是,通过编写一个动态代理器,某个方法需要代理的时候,我才会创建这个代理类去代理该方法。
说的再简单一点,我现在A类中的B方法需要被代理了,我只需要将A类和B方法传入这个动态代理器,动态代理器就会自动创建一个代理类来代理这个方法。
这里需要注意的是:静态代理和动态代理中的jdk代理都有一个要求,那就是被代理的委托类必须要实现接口。因此对于没有实现接口的委托类,如果需要被代理的话,就可以使用CGLib代理。
2.4.1.1 JDK代理原理
JDK代理是程序执行过程中,使用JDK的反射机制,创建代理类对象,生成一个实现代理接口的匿名类,然后重写方法,实现方法的增强。
什么是反射?
反射是可以在运行时期动态获取任何类的信息,如属性和方法.
反射是可以在运行时期动态调用任何类的属性和方法.
这种动态调用获取信息和动态调用类的方法叫做反射.
注意:JDK动态代理只能对实现了接口的类生成代理,而不能针对类
为什么JDK动态代理只能支持接口代理?
答:之所以 JDK 的动态代理只能通过接口实现,是因为通过JDK动态代理生成的代理类默认已经继承了 Proxy 类,因此只能实现代理我们传入的一堆接口;由于 Java 是单继承的,所以 JDK 动态代理只能代理接口,接口可以实现多个,但是类只能继承实现一个。
再说简单一点就是java不支持多继承。
2.4.1.1 CGLib代理原理
Cglib是针对类实现代理,主要是对指定的类的字节码生成一个子类,覆盖其中的方法,并覆盖其中方法的增强,但是因为采用的是继承,所以该类或方法最好不要生成final,对于final类或方法,是无法继承的。
说简单一点CGLib代理是直接通过操作类的字节码,来创建一个子类。
2.5 JDK代理和CGLib代理的区别
原理不同:
1、JDK动态代理使用的是反射的原理,创建一个代理类,然后重写方法,实现方法的增强。但是运行时因为是基于反射,调用后续的类操作会比较慢。
2、CGLIB是基于继承机制,继承被代理类,所以方法不要声明为final,然后重写父类方法达到增强了类的作用。它底层是基于asm第三方框架,是对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。生成类的速度慢,但是后续执行类的操作时候很快。
简单来说就是:jdk使用反射,CGLib使用字节码编程。
局限性不同:
1、JDK动态代理只能对实现了接口的类生成代理,而不能针对类
2、Cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法的增强,但是因为采用的是继承,所以该类或方法最好不要生成final,对于final类或方法,是无法继承的。
也就是说如果一个类实现了接口,那么可以使用JDK代理或者Cglib代理。如果没有实现接口,则只能使用Cglib代理。
简单来说就是:jdk针对接口,Cglib针对类
效率方面:
1、cglib底层是ASM字节码生成框架,但是字节码技术生成代理类,在JDL1.6之前比使用java反射的效率要高
2、在jdk6之后逐步对JDK动态代理进行了优化,在调用次数比较少时效率高于cglib代理效率
3、只有在大量调用的时候cglib的效率高,但是在1.8的时候JDK的效率已高于cglib
4、Cglib不能对声明final的方法进行代理,因为cglib是动态生成代理对象,final关键字修饰的类不可变只能被引用不能被修改
因此就现在而言:jdk的动态代理效率不低于Cglib。
2.6 spring如何抉择代理使用
1、当bean实现接口时,会用JDK代理模式
2、当bean没有实现接口,用cglib实现
3、可以强制使用cglib(在spring配置中加入<aop:aspectj-autoproxy proxyt-target-class=”true”/>)
2.7 代理模式的应用场景
2.8 代理模式的原理
三.工厂模式
四.装饰器模式
五.观察者模式
六.责任链模式
七.模板方法模式
通用模板类图如下:
模板方法模式实现起来很简单,实际上使用的是java的继承机制。一共分为两个部分:抽象模板类和具体实现类。
- 抽象模板类:其实就是抽象类,里面包括基本方法和模板方法
- 具体实现类:继承抽象模板类,实现其中的基本方法
为了防止恶意的操作,一般模板方法上都添加上final关键字,不允许被覆写。下面放上一个别人博客的例子:
这是一个悍马车的模型:
抽象类实现:
public abstract class HummerModel {
public abstract void start(); //发动
public abstract void stop(); //停止
public abstract void alarm(); //鸣笛
public abstract void engineBoom(); //轰鸣
public final void run() { //车总归要跑
this.start();
this.engineBoom();
this.alarm();
this.stop();
}
}
两个悍马实现类:
//悍马H1
public class HummerH1 implements HummerModel {
@Override
public void start() {
System.out.println("H1发动……");
}
@Override
public void stop() {
System.out.println("H1停止……");
}
@Override
public void alarm() {
System.out.println("H1鸣笛……");
}
@Override
public void engineBoom() {
System.out.println("H1轰鸣……");
}
}
//悍马H2
public class HummerH2 implements HummerModel {
@Override
public void start() {
System.out.println("H2发动……");
}
@Override
public void stop() {
System.out.println("H2停止……");
}
@Override
public void alarm() {
System.out.println("H2鸣笛……");
}
@Override
public void engineBoom() {
System.out.println("H2轰鸣……");
}
这里也许就很奇怪为什么单独的run方法要放在抽象类,因为两个实现类中如果都重新实现run方法,实际上这两个run方法是一样的。所以就可以直接放到抽象类中。这其实就是抽象类存在的意义。
参考博客:https://blog.csdn.net/eson_15/article/details/51323902