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 { /**** * 单例模式测试 */ @Test public 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代理,被代理的过程须要实现InvocationHandler3、代理过程在invoke中实现4、创立代理对象Proxy.newProxyInstance实现
咱们以王五租房为例,王五通过中介间接租用户主屋宇,中介在这里充当代理角色,户主充当被代理角色。
创立房东接口对象:LandlordService
public interface LandlordService { void rentingPay(String name);}
创立房东对象:Landlord
public class Landlord implements LandlordService{ /**** * @param name */ @Override public 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 */ @Override public 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 */ @Override public 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 { //创立代理对象 @Override public 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 @Nullable public 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)实现剖析
基于代理模式,咱们实现文件上传别离路由到aliyunOSS
和FastDFS
,用例图如下:
解说:
1、FileUpload形象接口,定义了文件上传办法,别离给它写了2种实现。2、AliyunOSSFileUpload是将文件上传到aliyunOSS,次要上传mp4和avi的视频大文件。3、FastdfsFileUpoad是将文件上传到FastDFS,次要上传png/jpg等图片小文件。4、FileUploadProxy是代理对象,供用户拜访,调用了FileUpload的文件上传办法,为用户提供不同文件上传调用。5、FileController是控制器,用于接管用户提交的文件,并调用代理FileUploadProxy实现文件上传。
2)代码实现
bootstrap.yml配置:
server: port: 18081logging: level: #root: debug开启dubug级别 com.seckill.goods.dao: error pattern: console: "%msg%n"#对应实例的id和须要解决的文件类型的映射关系upload: filemap: aliyunOSSFileUpload: avi,mp4 fastdfsFileUpoad: png,jpg#FastDFS配置fastdfs: url: http://192.168.211.137:28181/#aliyunaliyun: oss: endpoint: oss-cn-beijing.aliyuncs.com accessKey: a7i6rVEjbtaJdYX2 accessKeySecret: MeSZPybPHfJtsYCRlEaUbfRtdH8gl4 bucketName: sklll key: video/ backurl: https://sklll.oss-cn-beijing.aliyuncs.com/video/ #拜访地址配置spring: application: name: seckill-goods datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: 123456 servlet: 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.accessKey}") private String accessKey; @Value("${aliyun.oss.accessKeySecret}") private String accessKeySecret; @Value("${aliyun.oss.key}") private String key; @Value("${aliyun.oss.bucketName}") private String bucketName; @Value("${aliyun.oss.backurl}") private String backurl; /**** * 文件上传 * 文件类型如果是图片,则上传到本地FastDFS * 文件类型如果是视频,则上传到aliyun OSS */ @Override public String upload(byte[] buffers,String extName) { String realName = UUID.randomUUID().toString()+"."+extName; // 创立OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKey, accessKeySecret); // <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 */ @Override public 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创立StorageClient StorageClient storageClient = new StorageClient(trackerServer,null); return storageClient; }}
FileUploadProxy代理实现:
@Data@Component@ConfigurationProperties(prefix = "upload")public class FileUploadProxy implements ApplicationContextAware{ private ApplicationContext act; //aliyunOSSFileUpload -> mp4,avi private Map<String,List<String>> filemap; /*** * 文件上传 * @param file:上传的文件 * @return */ public String upload(MultipartFile file) throws Exception{ //文件名字 1.mp4 String fileName = file.getOriginalFilename(); //扩展名 mp4,jpg String extName = StringUtils.getFilenameExtension(fileName); //循环filemap for (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; } //注入容器对象 @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.act=applicationContext; }}
FileController控制器实现:
@RestController@RequestMapping(value = "/file")public class FileController { @Autowired private 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
外面定义了每个线程中不变的用户身份信息username
、role
、sex
,其余的是可能存在变动的数据能够写一个类继承该类。
@Data@ToString@NoArgsConstructor@AllArgsConstructorpublic 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); } /*** * 扩大对象 */ @Override public void handler() { System.out.println("扩大性能!"); }}
多线程安全控制:ThreadSession
每个线程申请的时候,咱们须要保障会话平安,比方A线程拜访和B线程拜访,他们的用户会话身份不能因为并发起因而产生凌乱。这里咱们能够采纳ThreadLocal来实现。咱们创立一个ThreadSession
对象,并在该对象中创立ThreadLocal<Session>
用户存储每个线程的会话信息,并实现ThreadLocal<Session>
的操作,代码如下:
@Componentpublic 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
拦截器的作用是用于初始化用户拜访的时候用户的身份信息,并将身份信息存储到ThreadSession
的ThreadLocal
中,在用户拜访办法完结,销毁ThreadSession
的ThreadLocal
中会话,代码如下:
@Componentpublic class AuthorizationInterceptor implements HandlerInterceptor { @Autowired private ThreadSession threadSession; /**** * 将用户会话存储到ThreadLocal中 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public 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 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { threadSession.remove(); }}
共享信息应用:
①AOP记录日志:创立AOP切面类LogAspect
用于记录日志,代码如下:
@Component@Aspect@Slf4jpublic class LogAspect { @Autowired private 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 { @Autowired private ItemDao itemDao; //总金额计算 @Override public 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; } //计算金额 @Override public void sum(Order order) { moneySum.sum(order); }}
满100减10元价格计算:创立类FullMoneySum
扩大装璜者类,实现满减价格计算。
@Component(value = "fullMoneySum")public class FullMoneySum extends DecoratorMoneySum{ //原来的性能上进行加强 @Override public 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 { //原有办法上加强 @Override public void sum(Order order) { //原有性能 super.sum(order); //加强 vipMoneySum(order); } //Vip价格优惠-5 public void vipMoneySum(Order order){ order.setPaymoney(order.getPaymoney()-5); }}
领取金额计算:批改OrderServiceImpl
的add()
办法,增加订单金额以及订单领取金额的计算性能,代码如下:
测试成果:
测试数据中,咱们抉择购买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 { //一般会员,没有优惠 @Override public Integer payMoney(Integer payMoney) { return payMoney; }}
定义Vip1策略:StrategyVipTwo
@Component(value = "strategyVipTwo")public class StrategyVipTwo implements Strategy{ //策略2 @Override public Integer payMoney(Integer payMoney) { return payMoney-5; }}
定义Vip2策略:StrategyVipThree
@Component(value = "strategyVipThree")public class StrategyVipThree implements Strategy{ //策略3 @Override public Integer payMoney(Integer payMoney) { return (int)(payMoney*0.7); }}
定义Vip3策略:StrategyVipFour
@Component(value = "strategyVipFour")public class StrategyVipFour implements Strategy{ //策略4 @Override public Integer payMoney(Integer payMoney) { return (int)(payMoney*0.5); }}
定义策略工厂:StrategyFactory
@Data@ConfigurationProperties(prefix = "strategy")@Componentpublic 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){ //依据等级获取策略ID String id = strategyMap.get(level); //依据ID获取对应实例 return act.getBean(id,Strategy.class); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { act=applicationContext; }}
等级策略配置:批改application.yml,将如下策略配置进去
#策略配置strategy: strategyMap: 1: strategyVipOne 2: strategyVipTwo 3: strategyVipThree 4: strategyVipFour
等级管制:批改UserHandler
增加等级属性
批改UserHandlerShare
定义等级,代码如下:
装璜者模式中批改VipMoneySum
的价格运算,代码如下:
测试:
如果本文对您有帮忙,欢送
关注
和点赞
`,您的反对是我保持创作的能源。转载请注明出处!