1 设计模式概述

​ 软件设计模式(Software Design Pattern),俗称设计模式,设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。使用设计模式的目的是为了代码重用、让代码更容易被他人理解、保证代码可靠性。

设计模式:

设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中一些不断重复发生的问题,以及该问题的解决方案。

设计模式使用场景:

1、在程序设计上会使用到设计模式(宏观)
2、在软件架构设计上会使用到设计模式(程序中的体现)

设计模式的目的:

1、提高代码的可重用性
2、提高代码的可读性
3、保障代码的可靠性

GOF

​ 《Design Patterns: Elements of Reusable Object-Oriented Software》(即后述《设计模式》一书),由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著(Addison-Wesley,1995)。这几位作者常被称为"四人组(Gang of Four)",而这本书也就被称为"四人组(或 GOF)"书。

​ 在《设计模式》这本书的最大部分是一个目录,该目录列举并描述了 23 种设计模式。

​ GOF中共提到了23种设计模式不是孤立存在的,很多模式之间存在一定的关联关系,在大的系统开发中常常同时使用多种设计模式。这23种设计模式根据功能作用来划分,可以划分为3类:

​ **(1)创建型模式:**用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”,单例、原型、工厂方法、抽象工厂、建造者5种设计模式属于创建型模式。

​ **(2)结构型模式:**用于描述如何将类或对象按某种布局组成更大的结构,代理、适配器、桥接、装饰、外观、享元、组合7种设计模式属于结构型模式。

​ **(3)行为型模式:**用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器11种设计模式属于行为型模式。

GOF的23种设计模式:

1、单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
2、原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
3、工厂方法(Factory Method)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
4、抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
5、建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。6、代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
7、适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
8、桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
9、装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。
10、外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
11、享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
12、组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。13、模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
14、策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
15、命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
16、职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
17、状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
18、观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
19、中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
20、迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
21、访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
22、备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
23、解释器(Interpreter)模式:提供如何定义语言的放法,以及对语言句子的解释方法,即解释器。

2 单例模式

​ 单例模式(Singleton Pattern)是 Java 中最常见的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。该类还提供了一种访问它唯一对象的方式,其他类可以直接访问该方法获取该对象实例,而不需要实例化该类的对象。

单例模式特点:

1、单例类只能有一个实例。                  A a = new A()
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。

单例模式优点:

1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2、避免对资源的多重占用(比如写文件操作)。

单例模式真实应用场景:

1、网站的计数器
2、应用程序的日志应用
3、数据库连接池设计
4、多线程的线程池设计

2.1 单例模式-饿汉式

创建一个单例对象SingleModel,SingleModel 类有它的私有构造函数和本身的一个静态实例。

SingleModel类提供了一个静态方法,供外界获取它的静态实例。DesignTest我们的演示类使用SingleModel类来获取 SingleModel 对象。


创建SingleModel:

public class SingleModel {//创建 SingleModel 的一个对象private static SingleModel instance = new SingleModel();//让构造函数为 private,这样该类就不会被实例化private SingleModel(){}//获取唯一可用的对象public static SingleModel getInstance(){return instance;}public void useMessage(){System.out.println("Single Model!");}
}

单例测试:

public class DemoTest {/***** 单例模式测试*/@Testpublic void testSingleModel(){//不合法的构造函数//编译时错误:构造函数 SingleModel() 是不可见的//SingleModel singleModel = new SingleModel();//获取唯一可用的对象SingleModel singleModel1 = SingleModel.getInstance();SingleModel singleModel2 = SingleModel.getInstance();//显示消息singleModel1.useMessage();//创建的2个对象是同一个对象System.out.println(singleModel1 == singleModel2);}
}

输入结果如下:

Single Model!
true

我们测试创建10万个对象,用单例模式创建,仅占内存:104字节,而如果用传统方式创建10万个对象,占内存大小为2826904字节。

2.2 多种单例模式讲解

​ 单例模式有多种创建方式,刚才创建方式没有特别的问题,但是程序启动就需要创建对象,不管你用不用到对象,都会创建对象,都会消耗一定内存。因此在单例的创建上出现了多种方式。

懒汉式:

懒汉式有这些特点:

1、延迟加载创建,也就是用到对象的时候,才会创建
2、线程安全问题需要手动处理(不添加同步方法,线程不安全,添加了同步方法,效率低)
3、实现容易

案例如下:SingleModel1

如果在创建对象实例的方法上添加同步synchronized,但是这种方案效率低,代码如下:

双重校验锁:SingleModel2

​ 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

public class SingleModel2 {//不实例化private static SingleModel2 instance;//让构造函数为 private,这样该类就不会被实例化private SingleModel2(){}//获取唯一可用的对象public static SingleModel2 getInstance(){//instance为空的时候才创建对象if(instance==null){//同步锁,效率比懒汉式高synchronized (SingleModel2.class){//这里需要判断第2次为空if(instance==null){instance = new SingleModel2();}}}return instance;}public void useMessage(){System.out.println("Single Model!");}
}

指令重排问题解决

对象创建,一般正确流程如下:

1:申请内存空间
2:创建对象
3:将创建的对象指向申请的内存空间地址

但其实在对象创建的时候,也有可能发生 指令重排问题,也就是上面流程会被打乱:

1:申请内存空间
2:将创建的对象指向申请的内存空间地址
3:创建对象

如果是这样的话,双检锁在多线程情况下也会出现问题,需要添加volatile属性,该属性能防止指令重排,代码如下:

public class SingleModel2 {//不实例化private static volatile SingleModel2 instance;//让构造函数为 private,这样该类就不会被实例化private SingleModel2(){}//获取唯一可用的对象public static SingleModel2 getInstance(){//instance为空的时候才创建对象if(instance==null){//同步锁,效率比懒汉式高synchronized (SingleModel2.class){//这里需要判断第2次为空if(instance==null){instance = new SingleModel2();}}}return instance;}public void useMessage(){System.out.println("Single Model!");}
}

3 SpringAOP代理模式

​ Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架,非常受企业欢迎,他解决了业务逻辑层和其他各层的松耦合问题,它将面向接口的编程思想贯穿整个系统应用。在Spring源码中拥有多个优秀的设计模式使用场景,有非常高的学习价值。

3.1 代理模式

定义:

给某对象提供一个代理对象,通过代理对象可以访问该对象的功能。主要解决通过代理去访问[不能直接访问的对象],例如租房中介,你可以直接通过中介去了解房东的房源信息,此时中介就可以称为代理。

优点:

1、职责清晰。
2、高扩展性。
3、智能化。

缺点:

 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

代理实现方式:(代理实现技术方案)

基于接口的动态代理提供者:JDK官方的Proxy类。要求:被代理类最少实现一个接口。
基于子类的动态代理提供者:第三方的CGLib,如果报asmxxxx异常,需要导入asm.jar。要求:被代理类不能用final修饰的类(最终类)。

3.2 JDK动态代理

JDK动态代理要点:

1、被代理的类必须实现一个接口
2、用JDK代理,被代理的过程需要实现InvocationHandler
3、代理过程在invoke中实现
4、创建代理对象Proxy.newProxyInstance实现

​ 我们以王五租房为例,王五通过中介直接租用户主房屋,中介在这里充当代理角色,户主充当被代理角色。

创建房东接口对象:LandlordService

public interface LandlordService {void rentingPay(String name);
}

创建房东对象:Landlord

public class Landlord implements LandlordService{/***** @param name*/@Overridepublic void rentingPay(String name){System.out.println(name+" 来交租!");}
}

创建代理处理过程对象:QFangProxy

public class QFangProxy implements InvocationHandler{private Object instance;public QFangProxy(Object instance) {this.instance = instance;}/***** 代理过程* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {args[0] = "中介QFang带领租户"+args[0];Object result = method.invoke(instance, args);return result;}
}

创建代理,并通过代理调用房东方法:JdkProxyTest

public class JdkProxyTest {public static void main(String[] args) {//给QFang产生代理LandlordService landlordService = new Landlord();QFangProxy proxy = new QFangProxy(landlordService);LandlordService landlordServiceProxy = (LandlordService) Proxy.newProxyInstance(LandlordService.class.getClassLoader(), new Class[]{LandlordService.class}, proxy);//通过代理对象调用Landlord对象的方法landlordServiceProxy.rentingPay("王五");}
}

运行结果如下:

中介QFang带领客户 来交租!

3.3 CGLib动态代理

CGLib动态代理要点:

1、代理过程可以实现MethodInterceptor(Callback)接口中的invoke来实现
2、通过Enhancer来创建代理对象

在上面的案例基础上,把QFangProxy换成SFangProxy,代码如下:

public class SFangProxy implements MethodInterceptor {private Object instance;public SFangProxy(Object instance) {this.instance = instance;}/**** 代理过程* @throws Throwable*/@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {args[0]="S房网带租户"+args[0];return method.invoke(instance,args);}
}

创建测试类:CGLibProxyTest,代码如下

public class CGLibProxyTest {public static void main(String[] args) {//给QFang产生代理LandlordService landlordService = new Landlord();SFangProxy proxy = new SFangProxy(landlordService);LandlordService landlordServiceProxy = (LandlordService) Enhancer.create(LandlordService.class,proxy);//通过代理对象调用Landlord对象的方法landlordServiceProxy.rentingPay("王五");}
}

3.4 Spring AOP-动态代理

​ 基于SpringAOP可以实现非常强大的功能,例如声明式事务、基于AOP的日志管理、基于AOP的权限管理等功能,利用AOP可以将重复的代码抽取,重复利用,节省开发时间,提升开发效率。Spring的AOP其实底层就是基于动态代理而来,并且支持JDK动态代理和CGLib动态代理,动态代理的集中体现在DefaultAopProxyFactory类中,我们来解析下DefaultAopProxyFactory类。


​ 如果我们在spring的配置文件中不配置<aop:config proxy-target-class="true">,此时默认使用的将是JDK动态代理,如果配置了,则会使用CGLib动态代理。

​ JDK动态代理的创建JdkDynamicAopProxy如下:

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {//创建代理对象@Overridepublic Object getProxy(@Nullable ClassLoader classLoader) {if (logger.isDebugEnabled()) {logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());}Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);}@Override@Nullablepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//JDK动态代理过程}}
}

​ CGLib动态代理的创建ObjenesisCglibAopProxy如下:

class ObjenesisCglibAopProxy extends CglibAopProxy {//CGLib动态代理创建过程@Override@SuppressWarnings("unchecked")protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {Class<?> proxyClass = enhancer.createClass();Object proxyInstance = null;if (objenesis.isWorthTrying()) {try {proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());}catch (Throwable ex) {logger.debug("Unable to instantiate proxy using Objenesis, " +"falling back to regular proxy construction", ex);}}if (proxyInstance == null) {// Regular instantiation via default constructor...try {Constructor<?> ctor = (this.constructorArgs != null ?proxyClass.getDeclaredConstructor(this.constructorArgTypes) :proxyClass.getDeclaredConstructor());ReflectionUtils.makeAccessible(ctor);proxyInstance = (this.constructorArgs != null ?ctor.newInstance(this.constructorArgs) : ctor.newInstance());}catch (Throwable ex) {throw new AopConfigException("Unable to instantiate proxy using Objenesis, " +"and regular proxy instantiation via default constructor fails as well", ex);}}((Factory) proxyInstance).setCallbacks(callbacks);return proxyInstance;}
}

3.5 代理模式-文件服务实战

设计模式如果只是去学习他的模式,而不投入实际应用,其实无异于闭门造猪,因此我们要将设计模式投入实际开发使用才是对设计模式真正的领悟。

案例:根据文件类型,将文件存储到不同服务

代理模式:

给一个对象创建一个代理对象,通过代理对象可以使用该对象的功能。

CGLib和JDK是代理模式实现的技术方案。

3.5.1 文件服务应用

​ 代理模式的应用场景除了代码级别,还可以将代理模式迁移到应用以及架构级别,如下图文件上传代理服务,针对一些图片小文件,我们可以直接把文件存储到FastDFS服务,针对大文件,例如商品视频介绍,我们可以把它存储到第三方OSS

​ 用户通过文件上传代理服务可以间接访问OSS和本地FastDFS,这种分布式海量文件管理解决方案,这里不仅在代码层面充分运用了代理模式,在架构层面也充分运用了代理模式。

3.5.2 分布式文件代理服务器实现

1)实现分析

​ 基于代理模式,我们实现文件上传分别路由到aliyunOSSFastDFS,用例图如下:

讲解:

1、FileUpload抽象接口,定义了文件上传方法,分别给它写了2种实现。
2、AliyunOSSFileUpload是将文件上传到aliyunOSS,主要上传mp4和avi的视频大文件。
3、FastdfsFileUpoad是将文件上传到FastDFS,主要上传png/jpg等图片小文件。
4、FileUploadProxy是代理对象,供用户访问,调用了FileUpload的文件上传方法,为用户提供不同文件上传调用。
5、FileController是控制器,用于接收用户提交的文件,并调用代理FileUploadProxy实现文件上传。

2)代码实现

bootstrap.yml配置:

server:port: 18081
logging:level:#root: debug开启dubug级别com.seckill.goods.dao: errorpattern:console: "%msg%n"#对应实例的id和需要处理的文件类型的映射关系
upload:filemap:aliyunOSSFileUpload: avi,mp4fastdfsFileUpoad: png,jpg#FastDFS配置
fastdfs:url: http://192.168.211.137:28181/
#aliyun
aliyun:oss:endpoint: oss-cn-beijing.aliyuncs.comKey: a7i6rVEjbtaJdYX2KeySecret: MeSZPybPHfJtsYCRlEaUbfRtdH8gl4bucketName: sklllkey: video/backurl: https://sklll.oss-cn-beijing.aliyuncs.com/video/ #访问地址配置spring:application:name: seckill-goodsdatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456servlet:multipart:max-file-size: 100MB #上传文件大小配置

FileUpload接口定义:

public interface FileUpload {/**** 文件上传* @param buffers:文件字节数组* @param extName:后缀名* @return*/String upload(byte[] buffers,String extName);
}

AliyunOSSFileUpload实现:

@Component(value = "aliyunOSSFileUpload")
public class AliyunOSSFileUpload implements FileUpload{@Value("${aliyun.oss.endpoint}")private String endpoint;@Value("${aliyun.oss.Key}")private String Key;@Value("${aliyun.oss.KeySecret}")private String KeySecret;@Value("${aliyun.oss.key}")private String key;@Value("${aliyun.oss.bucketName}")private String bucketName;@Value("${aliyun.oss.backurl}")private String backurl;/***** 文件上传*  文件类型如果是图片,则上传到本地FastDFS*  文件类型如果是视频,则上传到aliyun OSS*/@Overridepublic String upload(byte[] buffers,String extName) {String realName = UUID.randomUUID().toString()+"."+extName;// 创建OSSClient实例。OSS ossClient = new OSSClientBuilder().build(endpoint, Key, KeySecret);// <yourObjectName>表示上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key+realName, new ByteArrayInputStream(buffers));// 上传字符串。ObjectMetadata objectMetadata = new ObjectMetadata();objectMetadata.setContentType(FileUtil.getContentType("."+extName));putObjectRequest.setMetadata(objectMetadata);ossClient.putObject(putObjectRequest);// 关闭OSSClient。ossClient.shutdown();return backurl+realName;}
}

FastdfsFileUpoad实现:

@Component(value = "fastdfsFileUpoad")
public class FastdfsFileUpoad implements FileUpload {@Value("${fastdfs.url}")private String url;/**** 文件上传* @param buffers:文件字节数组* @param extName:后缀名* @return*/@Overridepublic String upload(byte[] buffers, String extName) {/**** 文件上传后的返回值* uploadResults[0]:文件上传所存储的组名,例如:group1* uploadResults[1]:文件存储路径,例如:M00/00/00/wKjThF0DBzaAP23MAAXz2mMp9oM26.jpeg*/String[] uploadResults = null;try {//获取StorageClient对象StorageClient storageClient = getStorageClient();//执行文件上传uploadResults = storageClient.upload_file(buffers, extName, null);return url+uploadResults[0]+"/"+uploadResults[1];} catch (Exception e) {throw new RuntimeException(e);}}/**** 初始化tracker信息*/static {try {//获取tracker的配置文件fdfs_client.conf的位置String filePath = new ClassPathResource("fdfs_client.conf").getPath();//加载tracker配置信息ClientGlobal.init(filePath);} catch (Exception e) {e.printStackTrace();}}/**** 获取StorageClient* @return* @throws Exception*/public static StorageClient getStorageClient() throws Exception{//创建TrackerClient对象TrackerClient trackerClient = new TrackerClient();//通过TrackerClient获取TrackerServer对象TrackerServer trackerServer = trackerClient.getConnection();//通过TrackerServer创建StorageClientStorageClient storageClient = new StorageClient(trackerServer,null);return storageClient;}
}

FileUploadProxy代理实现:

@Data
@Component
@ConfigurationProperties(prefix = "upload")
public class FileUploadProxy implements ApplicationContextAware{private ApplicationContext act;//aliyunOSSFileUpload     ->  mp4,aviprivate Map<String,List<String>> filemap;/**** 文件上传* @param file:上传的文件* @return*/public String upload(MultipartFile file) throws Exception{//文件名字  1.mp4String fileName = file.getOriginalFilename();//扩展名  mp4,jpgString extName = StringUtils.getFilenameExtension(fileName);//循环filemapfor (Map.Entry<String, List<String>> entry : filemap.entrySet()) {for (String suffix : entry.getValue()) {//匹配当前extName和当前map中对应的类型是否匹配if(extName.equalsIgnoreCase(suffix)){//一旦匹配,则把key作为唯一值,从容器中获取对应实例return act.getBean(entry.getKey(), FileUpload.class).upload(file.getBytes(),extName);}}}return null;}//注入容器对象@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.act=applicationContext;}
}

FileController控制器实现:

@RestController
@RequestMapping(value = "/file")
public class FileController {@Autowiredprivate FileUploadProxy fileUploadProxy;/**** 文件上传* @param file* @return* @throws IOException*/@PostMapping(value = "/upload")public String upload(MultipartFile file) throws IOException {return fileUploadProxy.upload(file.getBytes(), StringUtils.getFilenameExtension(file.getOriginalFilename()));}
}

文件上传预览效果:

<https://sklll.oss-cn-beijing.aliyuncs.com/video/77df7ada-4eea-4698-bfc5-bedd2c16f240.mp4>

FastDFS地址:

<http://192.168.211.137:28181/group1/M00/00/00/wKjTiV7kLtGASw5TAADJ9uXzZAQ622.png>

4 享元模式

定义:

运用共享技术来有効地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。

享元模式和单利的区别:

单利是对象只能自己创建自己,整个应用中只有1个对象
享元模式根据需要共享,不限制被谁创建(有可能有多个对象实例)

优点:

特定环境下,相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

缺点:

为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。

4.1 享元模式实战

案例:用户下单,会话共享

4.2 会话跟踪分析

​ 会话跟踪,如果是传统项目用Session或者是Cookie,全项目通用,但在微服务项目中,不用Session也不用Cookie,所以想要在微服务项目中实现会话跟踪,是有一定难度的。

​ 当前微服务项目中,身份识别的主流方法是前端将用户令牌存储到请求头中,每次请求将请求头中的令牌携带到后台,后台每次从请求头中获取令牌来识别用户身份。

​ 我们在项目操作过程中,很多地方都会用到用户身份信息,比如下订单的时候,要知道当前订单属于哪个用户,记录下单关键日志的时候,需要记录用户操作的信息以及用户信息,关键日志记录我们一般用AOP进行拦截操作,此时没法直接把用户身份信息传给AOP。这个时候我们可以利用享元模式实现用户会话信息共享操作。操作流程如下图:

4.3 会话共享案例实现

​ 基于上面的分析,我们采用享元模式实现用户会话共享操作,要解决如下几个问题:

1、用户会话共享
2、会话多线程安全
3、订单数据用户信息获取
4、AOP日志记录用户信息获取

定义共享组件Session

Session里面定义了每个线程中不变的用户身份信息usernamerolesex,其他的是可能存在变化的数据可以写一个类继承该类。

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public abstract class Session {//需要共享的用户信息private String username;private String name;private String sex;private String role;private Integer level;//扩展方法public abstract void handler();
}

享元组件逻辑操作对象SessionShar

SessionShar该对象主要用于给当前线程填充共享数据,以及变更访问方法和访问信息等信息的逻辑操作,代码如下:

public class SessionShar extends Session {//方便实例化public SessionShar(String username, String name, String sex, String role, Integer level) {super(username, name, sex, role, level);}/**** 扩展对象*/@Overridepublic void handler() {System.out.println("扩展功能!");}
}

多线程安全控制ThreadSession

​ 每个线程请求的时候,我们需要保障会话安全,比如A线程访问和B线程访问,他们的用户会话身份不能因为并发原因而发生混乱。这里我们可以采用ThreadLocal来实现。我们创建一个ThreadSession对象,并在该对象中创建ThreadLocal<Session>用户存储每个线程的会话信息,并实现ThreadLocal<Session>的操作,代码如下:

@Component
public class ThreadSession {//存储需要共享的对象private static ThreadLocal<Session> sessions = new ThreadLocal<Session>();/***** 添加用户信息记录*/public void add(Session session){sessions.set(session);}/***** 获取LogComponent*/public Session get(){return sessions.get();}/***** 移除*/public void remove(){sessions.remove();}
}

线程会话初始化AuthorizationInterceptor

AuthorizationInterceptor拦截器的作用是用于初始化用户访问的时候用户的身份信息,并将身份信息存储到ThreadSessionThreadLocal中,在用户访问方法结束,销毁ThreadSessionThreadLocal中会话,代码如下:

@Component
public class AuthorizationInterceptor implements HandlerInterceptor {@Autowiredprivate ThreadSession threadSession;/***** 将用户会话存储到ThreadLocal中* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {try {//获取令牌String authorization = request.getHeader("token");//解析令牌if(!StringUtils.isEmpty(authorization)){Map<String, Object> tokenMap = JwtTokenUtil.parseToken(authorization);//封装用户身份信息,存储到ThreadLocal中,供当前线程共享使用//1.封装需要共享的信息//2.创建一个对象继承封装信息,每次共享该对象 (不需要共享,则可以创建另外一个对象继承它)//3.创建共享管理对象,实现共享信息的增加、获取、移除功能threadSession.add(new SessionShar(tokenMap.get("username").toString(),tokenMap.get("name").toString(),tokenMap.get("sex").toString(),tokenMap.get("role").toString(),Integer.valueOf(tokenMap.get("level").toString())));return true;}} catch (Exception e) {e.printStackTrace();}//输出令牌校验失败response.setContentType("application/json;charset=utf-8");response.getWriter().print("身份校验失败!");response.getWriter().close();return false;}/*** 移除会话信息* @throws Exception*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {threadSession.remove();}
}

共享信息使用:

①AOP记录日志:创建AOP切面类LogAspect用于记录日志,代码如下:

@Component
@Aspect
@Slf4j
public class LogAspect {@Autowiredprivate ThreadSession threadSession;/**** 记录日志*/@SneakyThrows@Before("execution(int com.itheima.shop.service.impl.*.*(..))")public void logRecode(JoinPoint joinPoint){//获取方法名字和参数String methodName = joinPoint.getTarget().getClass().getName()+"."+joinPoint.getSignature().getName();//记录日志log.info("用户【"+threadSession.get().toString()+"】访问:"+methodName);}/***** 参数获取*/public String args(Object[] args){StringBuffer buffer = new StringBuffer();for (int i = 0; i <args.length ; i++) {buffer.append("  args("+i+"):"+args[i].toString());}return buffer.toString();}
}

②添加订单获取用户信息:在添加订单方法OrderServiceImpl.add(Order order)中,从ThreadSession中获取用户会话,并填充给Order,代码如下:

添加订单,日志输出可以看到调用添加订单和修改库存时,都记录了日志,并且获取了用户会话,效果如下:

LogComponent(username=zhaoliu, sex=男, role=ROLE_USER, methodName=com.itheima.shop.service.impl.OrderServiceImpl.add, message=  args(0):Order(itemId=1, id=1, money=9999, status=1, num=1, username=null))LogComponent(username=zhaoliu, sex=男, role=ROLE_USER, methodName=com.itheima.shop.service.impl.ItemServiceImpl.modify, message=  args(0):1  args(1):1)

添加的订单数据库数据中也拥有用户信息,效果如下:

5 装饰者模式

定义:

动态的向一个现有的对象添加新的功能,同时又不改变其结构。它属于结构型模式。

扩展新功能,不需要修改现有对象就能实现—>装饰者模式

优点:

装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

缺点:

多层装饰比较复杂。

5.1 装饰者模式实战

案例:结算价格计算,根据不同价格嵌套运算

5.2 订单结算价格实战

​ 在订单提交的时候,订单价格和结算价格其实是两码事,订单价格是当前商品成交价格,而结算价格是用户最终需要支付的金额,最终支付的金额并不是一成不变,它也并不是商品成交价格,能改变结算价格的因素很多,比如满100减10元,VIP用户再减5块。订单结算金额计算我们就可以采用装饰者模式。

5.3 装饰者模式价格运算实现

实现思路分析

1、创建接口(MoneyOperation),定义订单价格计算,因为所有价格波动,都是基于订单价格来波动的。
2、创建订单价格计算类(OrderPayMoneyOperation),实现MoneyOperation接口,实现订单价格计算。
3、创建装饰者对象(Decorator),以供功能扩展。
4、实现优惠券优惠金额计算功能扩展,创建Decorator的扩展类CouponsMoneyOperation,先计算订单金额,再计算优惠券使用之后的优惠金额。
5、实现金币抵现功能扩展,创建Decorator的扩展类GoldMoneyOperation,先计算订单金额,再实现金币优惠之后的金额。

基础接口:创建接口MoneySum,该接口只用于定义计算订单金额的方法。

public interface MoneySum {//订单金额求和计算void sum(Order order);
}

订单金额计算类:创建类OrderPayMoneyOperation实现订单金额的计算。

@Component(value = "orderMoneySum")
public class OrderMoneySum implements MoneySum {@Autowiredprivate ItemDao itemDao;//总金额计算@Overridepublic void sum(Order order) {//商品单价*总数量Item item = itemDao.findById(order.getItemId());order.setPaymoney(item.getPrice()*order.getNum());order.setMoney(item.getPrice()*order.getNum());}
}

装饰者类:创建装饰者类DecoratorMoneySum供其他类扩展。

public class DecoratorMoneySum implements MoneySum {private MoneySum moneySum;public void setMoneySum(MoneySum moneySum) {this.moneySum = moneySum;}//计算金额@Overridepublic void sum(Order order) {moneySum.sum(order);}
}

满100减10元价格计算:创建类FullMoneySum扩展装饰者类,实现满减价格计算。

@Component(value = "fullMoneySum")
public class FullMoneySum extends DecoratorMoneySum{//原来的功能上进行增强@Overridepublic void sum(Order order) {//原有功能super.sum(order);//增强moneySum(order);}//满100减5块public void moneySum(Order order){Integer paymoney = order.getPaymoney();if(paymoney>=100){order.setPaymoney(paymoney-10);}}
}

VIP优惠10元价格计算:创建类VipMoneySum,实现VIP优惠计算。

@Component(value = "vipMoneySum")
public class VipMoneySum extends DecoratorMoneySum {//原有方法上增强@Overridepublic void sum(Order order) {//原有功能super.sum(order);//增强vipMoneySum(order);}//Vip价格优惠-5public void vipMoneySum(Order order){order.setPaymoney(order.getPaymoney()-5);}
}

支付金额计算:修改OrderServiceImpladd()方法,添加订单金额以及订单支付金额的计算功能,代码如下:

测试效果

测试数据中,我们选择购买1件商品,当前登录用户为王五,拥有5个金币,当前购买的商品id=1,商品单价是150元,满减100,VIP优惠5元,最终支付135元。

{"itemId":"1","id":"1","status":1,"num":1,"couponsId":"1"
}

测试生成的订单如下:


不仅如此,我们可以随时撤掉满减和Vip优惠功能。

6 策略模式

定义:

策略模式是对算法的包装,把算法的使用和算法本身分隔开,委派给不同的对象管理。策略模式通常把一系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类或者接口的实现类。

​ 简单来说就是就定义一个策略接口,策略类去实现该接口去定义不同的策略。然后定义一个环境(Context,也就是需要用到策略的对象)类,以策略接口作为成员变量,根据环境来使用具体的策略。

优点:

1、算法可以自由切换。
2、避免使用多重条件判断。
3、扩展性良好。

缺点:

 1、策略类会增多。 2、所有策略类都需要对外暴露。

6.1 策略模式实战

案例:结算价格计算,根据Vip不同等级进行运算

6.2 不同VIP优惠价格分析

​ 用户在购买商品的时候,很多时候会根据Vip等级打不同折扣,尤其是在线商城中体现的淋漓尽致。我们这里也基于真实电商案例来实现VIP等级价格制:

Vip0->普通价格
Vip1->减5元
Vip2->7折
Vip3->5折

6.3 代码实现

定义策略接口Strategy

public interface Strategy {//价格计算Integer payMoney(Integer payMoney);
}

定义Vip0策略StrategyVipOne

@Component(value = "strategyVipOne")
public class StrategyVipOne implements Strategy {//普通会员,没有优惠@Overridepublic Integer payMoney(Integer payMoney) {return payMoney;}
}

定义Vip1策略StrategyVipTwo

@Component(value = "strategyVipTwo")
public class StrategyVipTwo implements  Strategy{//策略2@Overridepublic Integer payMoney(Integer payMoney) {return payMoney-5;}
}

定义Vip2策略StrategyVipThree

@Component(value = "strategyVipThree")
public class StrategyVipThree implements  Strategy{//策略3@Overridepublic Integer payMoney(Integer payMoney) {return (int)(payMoney*0.7);}
}

定义Vip3策略StrategyVipFour

@Component(value = "strategyVipFour")
public class StrategyVipFour implements  Strategy{//策略4@Overridepublic Integer payMoney(Integer payMoney) {return (int)(payMoney*0.5);}
}

定义策略工厂StrategyFactory

@Data
@ConfigurationProperties(prefix = "strategy")
@Component
public class StrategyFactory implements ApplicationContextAware{//ApplicationContext//1、定义一个Map存储所有策略【strategyVipOne=instanceOne】//                          【strategyVipTwo=instanceTwo】private ApplicationContext act;//定义一个Map,存储等级和策略的关系,通过application.yml配置注入进来private Map<Integer,String> strategyMap;//3、根据会员等级获取策略【1】【2】【3】public Strategy getStrategy(Integer level){//根据等级获取策略IDString id = strategyMap.get(level);//根据ID获取对应实例return act.getBean(id,Strategy.class);}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {act=applicationContext;}
}

等级策略配置:修改application.yml,将如下策略配置进去

#策略配置
strategy:strategyMap:1: strategyVipOne2: strategyVipTwo3: strategyVipThree4: strategyVipFour

等级控制:修改UserHandler添加等级属性

修改UserHandlerShare定义等级,代码如下:


装饰者模式中修改VipMoneySum的价格运算,代码如下:

测试:

本文由传智教育博学谷教研团队发布。

如果本文对您有帮助,欢迎关注点赞;如果您有任何建议也可留言评论私信,您的支持是我坚持创作的动力。

转载请注明出处!

9000+字,唠唠架构中的设计模式相关推荐

  1. 读书笔记《Java开发技术-在架构中体验设计模式和架构之美》

    一.Java程序员的三层境界 第一层:豪情万丈,欲与天公试比高 java开发技术掌握,工具娴熟,可以按要求独立完成类.接口和算法的开发:能注重技巧:热衷于谈技术问题.修炼第一层境界还是比较辛苦的. 第 ...

  2. 架构中的设计原则之单一职责原则 - 《java开发技术-在架构中体验设计模式和算法之美》...

    2019独角兽企业重金招聘Python工程师标准>>> 单一职责模式: 单一职责原则的核心思想就是:系统中的每一个对象都应该只有一个单独的职责,而所有对象所关注的就是自身职责的完成. ...

  3. 微服务架构中10个常用的设计模式

    从软件开发早期(1960 年代)开始,应对大型软件系统中的复杂性一直是一项令人生畏的任务.多年来为了应对软件系统的复杂性,软件工程师和架构师们做了许多尝试:David Parnas 的模块化和封装 ( ...

  4. 《微服务架构设计模式》读书笔记 | 第9章 微服务架构中的测试策略(上)

    第9章 微服务架构中的测试策略(上) 前言 1. 微服务架构中的测试策略概述 1.1 编写自动化测试 1.2 使用模拟和桩进行测试 1.3 使用范围对测试进行分类 1.4 使用测试象限对测试进行分类 ...

  5. 9000字深度复盘 | 创业公司1周年总结:拥抱不确定性谈何容易?来聊聊个人成长吧-Ω星 丨 产品经理与工作复盘 sprint...

    天上一天,地上一年. 全文约9000字,阅读需要10分钟,慎重点击,看完再收藏. 出品丨punkboy 作者丨punkboy punkboy的理想星球 目录 1.工作链条位置的变化 2.价值呈现 3. ...

  6. oracle数据库有哪些文件构成,Oracle数据库架构中包括几层?每层都有什么元素?...

    Oracle数据库架构中包括几层?每层都有 什么元素? 1 PL/SQL代表 A PROCEDURAL LANGUAGE/SQL B PROGRAM LANGUAGE SQL C POWER LANG ...

  7. 如何在微服务架构中实现安全性?

    点击上方"方志朋",选择"置顶公众号" 技术文章第一时间送达! 作者 | Chris Richardson 网络安全已成为每个企业都面临的关键问题.几乎每天都有 ...

  8. 面试官:哥们,你们的系统架构中为什么要引入消息中间件?

    点击上方"蓝字", 右上角选择"设为星标" 周一至五早11点半!精品文章准时送上! 本文来自石杉的架构笔记 这篇文章开始,我们把消息中间件这块高频的面试题给大家 ...

  9. 在微服务架构中做机器学习,真的太难了

    2020-05-29 14:42:56 我曾经参与过很多由深度学习技术驱动的项目,最糟糕的情况就是被迫处理面向微服务的架构,我不是呼吁大家停止使用微服务,但想在面向微服务的架构中推动机器学习项目,很大 ...

最新文章

  1. The 'microsoft.jet.oledb.4.0' provider is not registered on the local machin
  2. 为什么需要StringBuffer
  3. Handler的一个图片轮播程序
  4. 百度统计 java 实现思路_211本+985硕+计算机专业投面百度,坐等一周迎来三面,已拿offer...
  5. SCPPO(二十七):技术盛宴—报表交流会
  6. Android App应用包增量升级(one)
  7. 基于Vue+nodejs+Web的网上书城系统
  8. 蓝桥杯基础练习 杨辉三角形Python实现
  9. 医学统计学-为什么是个医学生就都要学R语言?
  10. jxls对比_13、进阶之Jxls2与Jxls1的历史问题
  11. 三行代码让你轻松下载全网任意视频-Python小知识
  12. 【定量分析、量化金融与统计学】R语言MANOVA多元方差分析
  13. (拓扑排序+并查集)HDU - 1811 Rank of Tetris
  14. 手把手教你完成unity3D跑酷游戏系列(二)
  15. 跟着清风学建模——拟合算法介绍及mathlab代码实现
  16. linux安装压缩文件的工具,Linux上安装rar解压工具
  17. 最老程序员开发实训10--Android---应用介绍页面实现2
  18. cad打印去掉边框_win7系统怎么去掉CAD打印图纸时图框的白边
  19. oracle判断字符串以什么开头_oracle 如何查找特定字母开头的某个字段?
  20. CSS 与 地图可视化 模糊注记 (十七)

热门文章

  1. 工业企业数字化转型--设备管理运维系统
  2. 520 送女朋友礼物大全 (推荐男生收藏)
  3. python怎么循环终止_Python 循环终止语句的三种方法小结
  4. iPhoneX无导航栏页面适配
  5. jzxx4015求和2
  6. 不能位虚拟电脑打开一个新任务
  7. 双目视觉---小孔成像原理视差原理
  8. 管理学中的知名定律之安慰剂效应(Placebo Effect)
  9. 计蒜客 2020 蓝桥杯大学 A 组省赛模拟赛 (一)题目及解析
  10. 关键词提取有哪些方案?刘志远回答