maven是啥:
1.Maven是一个项目管理和综合工具。Maven提供了开发人员构建一个完整的生命周期框架。
创建—导入jar报–编写配置文件—实现业务功能—测试—发布上线、
2.开发团队可以自动完成项目的基础工具建设,Maven使用标准的目录结构和默认构建生命周期。
3.在多个开发团队的环境时,Maven可以设置按标准在非常短的时间里完成配置工作。
由于大部分项目的设置都很简单,并且可重复使用,Maven
让开发人员的工作更轻松,同时创建报表,检查,构建和测试自动化设置。
maven可以自动下载jar报
maven业务场景
前段时间在研究maven,知道
maven是一个项目管理工具,其核心特点就是通过
maven可以进行包的依赖管理,保证jar包版本的一致性,以及可以使多个项目共享jar包,
从而能够在开发大型j2ee应用的时候,减小项目的大小,并且和ant比起来,maven根据“约定优于配置”的特性,可以对其项目的编译打包部署进行了
更为抽象的封装,使得自己不需要像ant那样进行详细配置文件的编写,直接使用
系统预定好的mvn clean,compile,test,package等命令进行项目的操作。于是我就
在XX项目中采用了maven,为了保证团队中的成员能够节省下载jar包所需要的时间,
于是我就采用nexus搭建了在局域网内的maven私服,然后通过配置settings.xml中
建立 镜像,将所有下载jar包的请求都转发到maven私服上,之后通过在pom.xml
即(project object model)中配置项目所依赖的jar包,从而达到在构建项目的时候,
先从本地仓库中查找,如果不存在从内部私服查找,如果不存在最后再从外网central
服务器查找的机制,达到了节省下载带宽,提高开发效率,以及jar包重用的目的。
maven的常用命令
mvn eclipse:clean eclipse:eclipse -Dwtpversion=2.0
mvn clean package
maven的生命周期是独立的,但是生命周期下的阶段是相互关联并且延续的。
maven的生命周期
clean(清理):clean;default(默认):compile,test,packageinstall;site(站点)
1.ssm框架搭建步骤
我们使用的ssm是由springMVC,spring和mybatis组成的。下面我就说一下一个简单的SSM框架搭建步骤,
因为web.xml文件是用来初始化配置信息的,因此首先在web.xml中配置springMVC核心控制器DispatchServlet,加载解析springMVC的核心配置文件spring-mvc.xml,并且配置spring监听,加载spring的核心配置文件spring-commons.xml.
我们在spring-mvc.xml中配置component-scan扫描controller层的注解,视图解析器viewResolver,静态资源放过拦截,文件解析器multipartResolver,拦截器。然后编写controller层类,再类上加上@Controller注解声明控制层,在方法加上@RequestMapping注解配置访问的路径。
spring-commons.xml有两个作用,一个是扫描我们的service层,还有就是初始化我们mybatis所需要的资源,在初始化资源时我们配置了数据库连接池dataSource,声明mybatis的sqlSessionFactory,配置扫描mapper映射文件,装配我们的dao层接口,然后配置事物的传播特性,并用aop完成事物的管理。mybatis使用时需要一个dao层接口和映射文件,映射文件的namespace指向我们的dao层接口,语句块中的ID要和dao层的方法名保持一致。这样我们的SSM框架就算打完了。
2. springmvc的常用注解
i. @RequestMapping("/query") 配置就是请求路径。注意:他可以加到方法上,也可以加到类上面(类似于namespace)
ii. @PathVariable 加在参数上,@RequestMapping(value = “/params4/{username}/{password}”, method = RequestMethod.GET) 注入访问路径中参数
iii. @ModelAttribute 多用于加载参数上,当前后台参数名称不一致时使用
iv. @RequestParam 加载参数上使用时,可以控制该参数是否必须传递。
v. @ResponseBody 加载方法上,返回的是纯数据。(注意他不要和ModelAndView)
3.springMVC的运行原理
springMVC是spring的一个后续产品,其实就是spring在原有基础上,又提供了web应用的MVC模块,可以简单的把springMVC理解为是spring的一个模块(类似AOP,IOC这样的模块),网络上经常会说springMVC和spring无缝集成,其实springMVC就是spring的一个子模块,所以根本不需要同spring进行整合。
1、 用户发送请求至前端控制器DispatcherServlet。
2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、 处理器映射器根据xml配置、注解进行查找 ,找到具体的处理器,生成处理器对象及处理器 拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet调用HandlerAdapter处理器适配器。
5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、 Controller执行完成返回ModelAndView。
7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9、 ViewReslover解析后返回具体View。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、 DispatcherServlet响应用户
4.mvc的理解
M-Model 数据模型(完成业务逻辑:有javaBean构成,service+dao+bean)
V-View 视图(做界面的展示 jsp,html……)
C-Controller 控制器(接收请求—>调用模型—>把数据渲染到指定的视图中,将页面返回给用户)
5.spring的理解
spring的核心ioc的控制反转,DI的依赖注入,AOP的面向切面编程。
DI依赖注入: 处理对象间的依赖关系
IOC也称控制反转,就是把原先控制代码对象的生产由代码转换到IOC容器中去实现。作用是为了解耦,降低类之间的耦合度,其设计思想就是设计模式的工厂模式,我们并不需要知道其生产的具体过程,我们只要其产出的对象即可。其工作流程就是:在Spring容器启动的时候,Spring会把你在applicationContext.xml中配置好的bean都初始化,在你需要调用的时候,把已经初始化的bean分配给你要调用这些bean的类,而不用去创建一个对象的实例。
aop:面向切面编程是软件编程思想发展到一定阶段的产物,是面向对象编程(OOP)的有益补充。AOP一般适用于具有横切逻辑的场合,如安全控制、事务管理、日志记录、性能统计等。面向切面编程简单地说就是在不改变源程序的基础上为代码段增加新的功能,对代码段进行增强处理。
aop的代理通过jdk动态代理,也可以通过cglib实现,默认是通过jdk动态代理实现的。JDK动态代理需要接口的支持,如果没有接口只有类,则使用cglib实现
6.注解的理解
可以看作是对 一个 类/方法的一个扩展的模版,它可以加在类上,方法上,成员变量上,参数上。
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类,我们通过反射获取注解,他有三种元注解:
@Documented –是否将注解包含在JavaDoc中
@Retention –什么时候使用该注解
@Target –注解用于什么地方
其中@Retention定义了注解的生命周期他有三种配置,
1.RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,例如@Override
2. RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
3.RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。
还有@Target声明注解用在什么地方。
常见的参数有
● ElementType.FIELD:成员变量、对象、属性
● ElementType.LOCAL_VARIABLE:用于描述局部变量
● ElementType.METHOD:用于描述方法
● ElementType.PARAMETER:用于描述参数
● ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明
我们如何定义自定注解呢:
1.新建一个java接口类在interface的关键字上加上@符号,目的是自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
2.在类上加上三个元注解,
3.然后编写一个成员变量只能用public或默认(default)这两个访问权修饰
4. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation对象,因为你除此之外没有别的获取注解对象的方法
我们使用自定义注解+aop实现了日志记录
1.首先创建日志记录要使用到的自定义注解,并通过元注解声明只能加载方法上,这个注解主要是进行执行方法的描述,和由该注解采取记录日志。
2.我们使用的是Aspect工具包,因此在spring-mvc.xml的文件中配置aop:aspectj-autoproxy/ 开启aop注解,通过component-scan对aop的相关类进行扫描,让注解生效。新建aop 切面类,再类上加上@Aspect声明是一个切面类,再加上@Component交给spring进行管理。在切面类中通过@Pointcut 声明切点,并配置切点表达式,切点表达式切到的是方法。然后定义通知主要通过@Before声明前置通知,@After声明后置通知, @Around声明环绕通知,@AfterThrowing声明抛出异常后通知,然后再通知一如切面表达式对应的方法。
在通知对应的方法中可以声明连接点JoinPoint,然后通过
joinPoint.getTarget().getClass().getName() 获取类访问的全路径
joinPoint.getSignature().getName() 获取方法名
joinPoint.getArgs(); 获取方法中的参数。
再根据获得的类的全路径使用java的反射技术获取注解和注解内容。具体如下
通过Class.forName(className)获得要操作的类,通过getMethods();获取类中的所有的公共方法。然后循环获取的方法,通过getName()获得方法名,在和连接点中的方法名进行比较,名称相同后再去比较他们的参数,通过method.getParameterTypes();获取方法中的参数,比较成功过后通过method.getAnnotation获取注解和注解描述信息。然后把相关数据保存到数据库中。
自定义注解,然后新建实体对象,添加注解。
String title() default "";
String columnName() default "";
String sheetName() default "";
String mkdir() default "";
(2)封装工具类
1.就是如何获取实体对象中的注解和属性值
因为导出的sheetName,标题,表头行,文件保存路径的注解放在
实体对象的类上,因此我们需要获取这个类,java反射机制获取类的方式
class.forName,
对象.class,
new 对象.getClass,
我们用的是对象.class。通过类.getAnnotation(注解类.class),
获取自定义的注解,判断不为空,将注解中的属性值取出放到一个封装的对象中,
2.获取要导出实体bean字段上的注解,主要是用来表头的展示,和java反射机制
通过get方法获取对应的属性值。
定义了两个string类型的list集合,然后通过clazz.getDeclaredFields(),获取全部
声明的私有属性,进行循环,获取属性Field,然后同field.getAnnotation(注解.class)
从而获取里边列头名称,并且获取要出的字段,放到对应的list集合中,
把list集合放到封装的工具bean中。
3.创建excel
步骤都差不多,都是通过XSSFWorkbook创建工作簿,通过创建的工作簿创建sheet页createSheet()
通过sheet页创建标题行,表行要合并单元格,使用的是CellRangeAddress进行单元格合并设置,
sheet.addMergedRegion(),使其生效。然后创建表头行createRow(),通过循环列头的list集合,
给表头行的单元格赋值。
接着创建数据行,双层循环,先循环要导出的数据List集合,获取数据对象。在去创建数据行,循环
工具bean中的要导出字段的集合,通过java反射机制获取对象的数据,具体步骤是,先拼装出属性对应的get方法名
,再获取get对应的方法,clazz.getMethod(方法名),再对象中执行这个方法,method.invoke(对象),使用Oject接受
返回的值,进行数据类型的判断,然后强转。【date和浮点型的数据类型处理】,然后放到excel的单元格。
4.将生成的excel通过IO流给写道服务器指定的文件夹下,然后返回路径下载。
7.mybatis的运行原理和二级缓存
二级缓存配置
在application.properties中添加配置
#开启mybatis的二级缓存
mybatis.configuration.cache-enabled=true
#配置控制台打印sql语句
logging.level.com.fh.dao=debug
然后在对应的持久层接口上加上注解
@CacheNamespace(size=512)
对于一些查询不想走二级缓存就在该方法上加上
@Options(useCache = false)
mybatis的运行原理:
mybatis底层还是采用原生jdbc来对数据库进行操作的,只是通过 SqlSessionFactory,SqlSessionExecutor,StatementHandler,ParameterHandler,ResultHandler和TypeHandler等几个处理器封装了这些过程
其中StatementHandler通过ParameterHandler与ResultHandler分别进行参数预编译 与结果处理。而ParameterHandler与ResultHandler都使用TypeHandler进行映射。
一级缓存:
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。
二级缓存
当开启一个会话时,一个SqlSession对象会使用一个Executor对象来完成会话操作,MyBatis的二级缓存机制的关键就是对这个Executor对象做文章。如果用户配置了"cacheEnabled=true",那么MyBatis在为SqlSession对象创建Executor对象时,会对Executor对象加上一个装饰者:CachingExecutor,这时SqlSession使用CachingExecutor对象来完成操作请求。CachingExecutor对于查询请求,会先判断该查询请求在Application级别的二级缓存中是否有缓存结果,如果有查询结果,则直接返回缓存结果;如果缓存中没有,再交给真正的Executor对象来完成查询操作,之后CachingExecutor会将真正Executor返回的查询结果放置到缓存中,然后在返回给用户。
8.数据库相关操作
数据库范式
范式的级别
设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次规范,越高的范式数据库冗余越小。
我么常用的是数据库的前三范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)。
范式越高,冗余最低,一般到三范式,再往上,表越多,可能导致查询效率下降。所以有时为了提高运行效率,可以让数据冗余(反三范式,一般某个数据经常被访问时,比如数据表里存放了语文数学英语成绩,但是如果在某个时间经常要得到它的总分,每次都要进行计算会降低性能,可以加上总分这个冗余字段)。
后面的范式是在满足前面范式的基础上,比如满足第二范式的一定满足第一范式。
第一范式(1NF):确保每一列的原子性
如果每一列都是不可再分的最小数据单元,则满足第一范式。
id 地址
1 中 #¥%国广东
2 中国云南
上面的表地址字段其实可以继续分:
id 国家 省份
1 中国 广东
2 中国 云南
但是具体地址到底要不要拆分 还要看具体情形,比如看看将来会不会按国家或者省市进行分类汇总或者排序,如果需要,最好就拆,如果不需要而仅仅起字符串的作用,可以不拆,操作起来更方便。
第二范式:非键字段必须依赖于键字段
如果一个关系满足1NF,并且除了主键以外的其它列,都依赖与该主键,则满足二范式(2NF),第二范式要求每个表只描述一件事。
例如:
字段 例子
订单编号 001
产品编号 a011
订购日期 2017-4-8
价格 ¥30
而实际上,产品编号与订单编号并没有明确的关系,订购日期与订单编号有关系,因为一旦订单编号确定下来了,订购日期也确定了,价格与订单编号也没有直接关系,而与产品有关,所以上面的表实际上可以拆分:
订单表:
单编号 001
日期 2017-4-8
产品表:
产品编号 a011
价格 ¥30
第三范式:在1NF基础上,除了主键以外的其它列都不传递依赖于主键列,或者说:属 任何非主性不依赖于其它非主属性
(在2NF基础上消除传递依赖)
例如:
字段 例子
订单编号 001
订购日期 2017-4-8
顾客编号 a01
顾客姓名 howard
上面的满足第一和第二范式,但是不满足第三范式,原因如下:
通过顾客编号可以确定顾客姓名,通过顾客姓名可以确定顾客编号,即在这个订单表里,这两个字段存在传递依赖,只需要一个就够了。
又如:
主键 学号 姓名 成绩
1 111 howard 90
2 222 tom 90
上面的表,学号和姓名存在传递依赖,因为(学号,姓名)->成绩,学号->成绩,姓名->成绩。所以学号和姓名有一个冗余了,只需要保留一个。
https://www.cnblogs.com/huanongying/p/7021555.html
事务的基本要素(ACID)
1、原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
2、一致性(Consistency):事物从一个一致性状态到另一个一致性状态。
3、隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
4、持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
二、事务的并发问题
1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
三、MySQL事务隔离级别
mysql默认的事务隔离级别为repeatable-read(可重复读)
查看数据库隔离级别的命令:select @@tx_isolation ;
事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted) 是 是 是
不可重复读(read-committed) 否 是 是
可重复读(repeatable-read) 否 否 否
串行化(serializable) 否 否 否
mysql默认的事务隔离级别为repeatable-read
事物的传播特性
Propagation (事务的传播属性)
Propagation : key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。有以下选项可供使用:
PROPAGATION_REQUIRED(Propagation-required)–支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS(Propagation-supports)–支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY(Propagation-mandatory)–支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW(Propagation-requires-new)–新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED(Propagation-not-supported)–以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER(Propagation-never)–以非事务方式执行,如果当前存在事务,则抛出异常。
微服务框架springboot+springcloud
spring cloud config
远程配置服务。
远程配置是每个都必不可少的中间件,远程配置的特点一般需要:多节点主备、配置化、动态修改、配置本地化缓存、动态修改的实时推送等。
config允许配置文件放在git上或者svn上,和spring boot的集成非常容易,但是缺点就是修改了git上的配置以后,只能一个一个的请求每个service的接口,让他们去更新配置,没有修改配置的推送消息。而且,如果要根据配置文件的修改,做一些重新初始化操作的话(如线程池的容量变化等),会需要一些work around的方法,所以建议如果有其他方案,不建议选择spring cloud config。
spring cloud bus
事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化。经常与Spring Cloud Config联合使用。
spring cloud config本身不能向注册过来的服务提供实时更新的推送。比如我们配置放在了git上,那么当修改github上配置内容的时候,最多可以配置webhook到一台config-server上,但是config-server自己不会将配置更新实时推送到各个服务上。
bus的作用就是将大家链接在一条总线上,这条线上的所有server共享状态,当webhook到bus上的某一台server的时候,其他server也会收到相同的hook状态。
但是bus的使用需要依赖于MQ,bus直接继承了RabbitMq & kafka,只需要在spring中直接配置地址即可,但是对于其他类型的MQ,就需要一些手动配置。
最大的问题还是,如果仅仅因为spring cloud bus而让自己的系统引入MQ,显然会有些得不偿失。我理解系统应该在满足现有业务需求的基础上,越简单越好,依赖越少链路越短,越能减少出问题的风险。
eureka
eureka负责服务注册和服务发现,为了高可用,一般需要多个eureka server相互注册,组成集群。Eureka Server的同步遵循着一个非常简单的原则:只要有一条边将节点连接,就可以进行信息传播与同步。
eureka内部对于注册的service主要通过心跳来监控service是否已经挂掉,默认心跳时间是15s。这就意味着,当一个服务提供方挂掉以后,服务订阅者最长可能30s以后才发现。
service启动连上eureka之后,会同步一份服务列表到本地缓存,服务注册有更新时,eureka会推送到每个service。
eureka也会有一些策略防止由于某个服务所在网络的不稳定导致的所有服务心跳停止的雪崩现象。
eureka自带web页面,在页面上能看到所有的服务注册情况 和 eureka集群状态。
eureka支持服务自己主动下掉自己,请求service的下列地址,可以让服务从eureka上下掉自己,同时service进程也会自己停掉自己。
curl -H ‘Accept:application/json’ -X POST localhost:${management.port}/shutdown
ribbon
客户端负载均衡组件。
服务发现以后,每个service在本地知道自己要调用的服务有多少台机器,机器的ip是什么,端口号是多少,那这个service在本地需要有一个负载均衡策略,为每一次请求选择一台目标机器进行调用,而ribbon做的就是负载均衡策略的选择。
feign
声明式、模板化的HTTP客户端。
微服务之间的调用本质还是http请求,如果对于每个请求都需要写请求代码,增加请求参数,同时对请求结果做处理,就会存在大量重复工作,而feign非常优雅的帮助我们解决了这个问题,只需要定义一个interface,fegin就知道http请求的时候参数应该如何设置。
同时,feign也集成了ribbon,只要在微服务中依赖了ribbon,feign默认会使用ribbon定义的负载均衡策略。
最重要的是,feign并不是仅仅只能使用在有eureka或者ribbon的微服务系统中,任何系统中,只要涉及到http调用第三方服务,都可以使用feign,帮我们解决http请求的代码重复编写。
hystrix
断路器,类似于物理电路图中的断路器。
正常情况下,当整个服务环境中,某一个服务提供方由于网络原因、数据库原因或者性能原因等,造成响应很慢的话,调用方就有可能短时间内累计大量的请求线程,最终造成调用方down,甚至整个系统崩溃。而加入hystrix之后,如果hystrix发现某个服务的某台机器调用非常缓慢或者多次调用失败,就会短时间内把这条路断掉,所有的请求都不会再发到这台机器上。
如果某个服务所有的机器都挂了,hystrix会迅速失败,马上返回,保证被调用方不会有大量的线程堆积。
Feign默认集成了hystrix。
上面有提到,使用eureka时,当一个服务提供方挂掉以后,服务订阅者最长可能30s以后才知道,那这30s就会出现大量的调用失败。如果在系统里面集成了hystrix,就会马上把挂掉的这台服务提供方断路掉,让请求不再转发到这台机器上,大量减少调用失败。
hystrix执行断路操作以后,并不表示这条路就永远断了,而是会一定时间间隔内缓慢尝试去请求这条路,如果能请求成功,断路就会恢复。
有一点需要注意的是hystrix在做断路时,默认所有的调用请求都会放在一个的线程池中进行,线程池的作用很明显,有隔离性。比如gateway,集成了5个子业务系统,可能其中一个系统的调用量非常大,而另外四个系统的调用很小,如果没有线程池的话,显然第一个系统的大量调用会影响到后面四个系统的调用性能。hystrix的线程池和java标准线程池一样,可以配置一些参数:coreSize、maximumSize、maxQueueSize、queueSizeRejectionThreshold、allowMaximumSizeToDivergeFromCoreSize、keepAliveTimeMinutes等,如果某一个子系统的调用量突然激增,超过了线程池的容量,也会迅速失败,直接返回,起到降级和保护系统本身的作用。当然hystrix也支持非线程池的方式,在本地请求线程中做调用,即semaphore模式,官方不建议,除非系统qps真的很大。
zuul
是一个网关组件。提供动态路由,监控,弹性,安全等边缘服务的框架。
zuul主需要简单配置一下properties文件,不需要写具体的代码就可以实现将请求转发到相应的服务上去。
还可以定制化一些filter做验证、隔离、限流、文件处理等切面,对于网关来说,使用zuul能减少大量的代码。
现在我们的网关主要还是基于ribbon、hystrix来实现的。zuul默认也集成了这些组件。
Spring Cloud Starters
spring boot热插拔、提供默认配置、开箱即用的依赖。
starter 是spring boot框架非常基础的部分。可以自定义starter。
多线程开发:
https://www.cnblogs.com/iTlijun/p/5860379.html
线程和进程的区别:
1 线程:进程中负责程序执行的执行单元
线程本身依靠程序进行运行
线程是程序中的顺序控制流,只能使用分配给程序的资源和环境
2 进程:执行中的程序
一个进程至少包含一个线程
3 单线程:程序中只存在一个线程,实际上主方法就是一个主线程
4 多线程:在一个程序中运行多个任务
目的是更好地使用CPU资源
线程的声明方式有两种:
第一种继承Thread类
重写run方法(就是来执行业务的)
创建好了自己的线程类之后,就可以创建线程对象了,然后通过start()方法去启动线程。注意,不是调用run()方法启动线程,run方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务
第二中实现Runnable接口
Runnable的中文意思是“任务”,顾名思义,通过实现Runnable接口,我们定义了一个子任务,然后将子任务交由Thread去执行。注意,这种方式必须将Runnable作为Thread类的参数,然后通过Thread的start方法来创建一个新线程来执行该子任务。如果调用Runnable的run方法的话,是不会创建新线程的,这根普通的方法调用没有任何区别。
事实上,查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。
在Java中,这2种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,但是由于Java只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。
线程的状态
在正式学习Thread类中的具体方法之前,我们先来了解一下线程有哪些状态,这个将会有助于后面对Thread类中的方法的理解。
创建(new)状态: 准备好了一个多线程的对象
就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
运行(running)状态: 执行run()方法
阻塞(blocked)状态: 暂时停止执行, 可能将资源交给其它线程使用
终止(dead)状态: 线程销毁
当需要新起一个线程来执行某个子任务时,就创建了一个线程。但是线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内存资源,在前面的JVM内存区域划分一篇博文中知道程序计数器、Java栈、本地方法栈都是线程私有的,所以需要为线程分配一定的内存空间),只有线程运行需要的所有条件满足了,才进入就绪状态。
当线程进入就绪状态后,不代表立刻就能获取CPU执行时间,也许此时CPU正在执行其他的事情,因此它要等待。当得到CPU执行时间之后,线程便真正进入运行状态。
线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。
wait();线程进入等待状态,需要写程序进行唤醒 (notify();)
当由于突然中断或者子任务执行完毕,线程就会被消亡。
下面这副图描述了线程从创建到消亡之间的状态:
在有些教程上将blocked、waiting、time waiting统称为阻塞状态,这个也是可以的,只不过这里我想将线程的状态和Java中的方法调用联系起来,所以将waiting和time waiting两个状态分离出来。
注:sleep和wait的区别:
sleep是Thread类的方法,wait是Object类中定义的方法.
Thread.sleep不会导致锁行为的改变, 如果当前线程是拥有锁的, 那么Thread.sleep不会让线程释放锁.
Thread.sleep和Object.wait都会暂停当前的线程. OS会将执行时间分配给其它线程. 区别是, 调用wait后, 需要别的线程执行notify/notifyAll才能够重新获得CPU执行时间.
停止线程
停止线程是在多线程开发时很重要的技术点,掌握此技术可以对线程的停止进行有效的处理。
停止一个线程可以使用Thread.stop()方法,但最好不用它。该方法是不安全的,已被弃用。
在Java中有以下3种方法可以终止正在运行的线程:
使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用他们可能产生不可预料的结果。
使用interrupt方法中断线程,但这个不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。
线程池
上述代码中Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
创建固定数目线程的线程池。
public static ExecutorService newFixedThreadPool(int nThreads)
创建一个可缓存的线程池
public static ExecutorService newCachedThreadPool()
调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的, 则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
创建一个单线程化的Executor。
public static ExecutorService newSingleThreadExecutor()
创建一个支持定时及周期性的任务执行的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
多数情况下可用来替代Timer类。
创建一个可返回的线程池
ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。
map集合:
hashMap:
hashMap的数据结构是在数组加链表,hashMap在初始化时数据结构是数组,默认长度16,同时会有负载因子是0.75,负载因子的作用是当数组的容量达到0.75时,就需要扩容,每次扩容是原来的2倍,就是在原来数组的元素的基础上进行链式扩容。
我们要操作hashMap是用到的是Entry对象,它有四个属性分别是key,value,hash值,next,key和value是进行put操作时赋值,hash值是根据key算出来的,hash值就是数据存储的位置,因此map中不允许出现相同的key值。
hashMap是不支持并发操作的,原因是在多线程情况下,上一个线程还没有结束,下一个线程就开始执行,他们用的是同一个hash值,就会造成数据丢失.
put方法的调用过程:
当插入第一个元素的时候,需要先初始化数组大小,默认初始化为16,并设置负载因子。 如果 key 为 null,最终会将这个 entry 放到 table[0] 中,获取对应key的hash 值,接着找到对应的数组下标,从而找到数组中的链表,循环遍历链表,看一下是否已经有相同的Key,若果有将原值进行覆盖,并返回原值。不存在重复的 key,将此 entry 添加到链表中,首先判断当前的map集合的长度(size)是否大于等于(map的容量乘以负载因子),条件成立就将现有容量扩大两倍,同时将key和value放到entry对象中,并map的size++;
get方法操作过程:
是否有一个线程安全有效率的得map集合:
有是concurrentHashMap(),在原有的hashMap前加了一个segMent槽,通过给这个槽枷锁和解锁来控制线程安全。在put的时候如果没有初始化segment,首先初始化segMent,接着调用put方法,先获取该segment的锁,打开锁,在将key和value放到对应的链表中去,然后解锁。
jdk1.8的hashMap集合于1.7的区别是:
(1)1.7数组加链表,1.8数组加链表加红黑树(链表长度超过8时会自动转成红黑树)
(2)1.7必须先扩容再放值,1.8允许先放值再扩容。
(3)get的时候1.7循环遍历的是对应数组下的链表,1.8特可能循环的是链表,也可能遍历的红黑树,查询的复杂度降低
一.redis的介绍理解
redis可以理解成一个数据库服务,也可以理解成一个缓存服务。他是一个key-value存储系统,类似于一个map集合,支持五大数据类型分别:string(字符串)、 list(链表)、set(集合)和zset(有序集合),并且可以对这些数类型对应相应的增删改查语句。同时redis为了保证效率,所有的增删改查都是基于计算机的内存,这样可以解决I/O的读写造成的压力问题。他的读写效率特别高(读的速度11万次/s 写的效率8万次/s),同时支持事物的原子性。并且他可以像数据库一样对数据进行持久化,就是将内存中的数据保存到硬盘上。
redis和数据都是两个武功高手,数据库集重家之长,内功心法和招式全都要修炼,因此需要的时间和周期比较长,并且会被名誉所累,要考虑方方面面。而redis不拘泥于形式,只有一招十步一杀,走遍天下。因此数据库再设计时要考虑事物的四大特性,数据库的设计原则(三范式)。而redis没有表的概念,只有数据的读取,见面就是一招读写效率奇快。
二.那我们使用redis来干啥
他可以当成数据库使用来存储一些数据,但是需要配置持久化,也可当成临时数据的缓存存储。
redis 的特点是key-value的存储系统,它有五大数据类型,因此需要做的业务首先要理解这五大数据类型。
1.String:虽然是string数据类型但是也可以存储序列化后的对象(解释一下序列化),但是他有几个特殊功能【1.可以设置过期时间,2.计数器功能】,大部分的业务存储都是选用的是string。(必讲)
基于这样的特点我们可以实现的业务:
(1)可以给数据设置过期时间,并且时间过期后,redis会自动删除该数据,因此可以来存储用户登录过程中产生的session信息
(2)计数器功能,可以按照自己设置的步长(就是每次加1还是加2)。可以用于在线人数统计,在新闻网站中可以用户点赞,投票,微博数,粉丝数。
(3)接口限流,就是在一分钟内只允许同一IP的客户端访问10次。防止恶意刷新
2.list:他的操作分别是push/pop.简单点说就是上车需要排队,后来的push在后面,当然老人孕妇可以插个队push的队列的前面,然后大家通过pop一个个上车。
(1).多用于消息队列应用的场景,比如秒杀,日志收集。
(2).取最新N个数据的操作(比如微博和新闻网站会缓存前五千条品论信息,超过五千条才会去查数据库)
3.hash:和我们的HASHmap几乎一样 (必讲)
1.存储一些部分变更的数据,如用户信息中只修改了生日只需要把该用户对应的生日字段修改。
2.他的存储模式为set(key,key,value)
这种模式可以用来存储购物车,一个用户只有一个购物车,每个商品单独存储。
4.set:概念和java中的set差不多,无序集合,元素唯一性,不可重复。
在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。
5.zset:是有序集合,元素唯一性,不可重复。
应用场景:粉丝充值贡献榜。
游戏排行榜(LOL中的各个大区的玩家段位排名,查询自己在该大区的排名)。
实现缓存数据分页。
三.redis在项目中的使用.
1.使用SSM框架集成redis。
我们的网站后台是使用SSM框架搭建,但是考虑到我们的项目会有一些使用频繁但是更新不频繁的数据,比如商品增加使用到的品牌,分类等数据,我们可以将这些数据存储到redis中。因此需要在框架中集成redis服务,redis集成非常简单,在spring-redis.xml文件中配置redis的连接池信息,比如redis的主机IP,端口号,最大连接数和活跃数等。并且配置redis的key和value 的序列化方式,我用的是stringRedisSerializer,最后配置redis的操作模板redisTemplate,用来操作redis的数据类型。
我就说一下我的品牌列表是如何进行缓存的,我当时用的是hash的数据,为啥用这个,因为可以实现对象的更新操作。首先我们使用监听(或者查询第一次使用时从数据库查询出来发到redis,下一次从redis中查询)在项目启动时将品牌列表缓存到redis中缓中,具体就是使用@Autowired注入redisTemplatem模板,使用redisTemplatem.opsForHash.set(key,hkey,value),第一个可以使我们定义好的常量如shop_brand_key,第二个key是品牌ID,第三个value值是从数据查询出来的品牌对象。如果品牌模块发生了修改操作和新增操作,同时要使用redisTemplatem.opsForHash.set(key,hkey,value)完成换成新增和更新操作,如果将某个品牌设置成不可用,那么要将该品牌从redis中删除,调用的方法是redisTemplatem.opsForHash.delete(key,hkey)。
2.分布式框架中使用redis完成分布式session存储
分布式框架的核心作用就是来完成负载均衡的,因此应用的服务器至少在两台和以上,当时我们用的nginx来做我们web服务的反向代理完成负责均衡。这样的话传统办法的sesion存储在对应的web服务器上,一旦请求访问的服务器发生改变,就会造成session获取不到要重新登录。
我们公司当时解决方案是,用redis存储和管理session。
在用户登录时,首先验证用户的登录信息,验证完成后我们需要将用户的登录信息存储到redis中,使用的是redis的string类型。这一块的重要的是key的生成,当时我们考虑到用户同时在线问题,于是使用的登录业务关键字+用户ID+时间戳组成key,这样的用户在另一台电脑登录成功,就会产生一个新的key,并且将老的session信息给删除。在将存储session的同时设置session的失效时间,可以使用redisTemplate.expire() .这里我们公司要求如果用户的session没有失效,关闭浏览器后可以不用登陆访问,于是我们生成的sessionKey放到cookie中,并且设置和session一样的失效时间,对cookie进行在浏览器上的持久化,这样在拦截器或者过滤器中可以获取cookie中的sessionID,通过sessionID从redis中获取对应的session信息,如果有就放过,同时给session延长失效时间,若果没有就返回到登陆页面让用重新登陆。
3.使用redis实现接口限流
我们的分布式框架中会发布一些接口,这些接口为了防止被恶意访问,我们不仅要做接口安全,还要做接口限流这块我们是结合redis进行实现的。我们当时是对客户端的访问进行限流,允许客户端每分钟访问该接口十次,超过十次就会提示该用户操作频繁,一分钟该限制在redis中自动失效,我们当时在springcloub框架中使用rateLimit组件进行限流,具体操作就是在路由网关中进行配置,首先配置
zuul.ratelimit.enabled=true 开启限流操作,
#访问的代理服务
zuul.ratelimit.behind-proxy=true
#开启redis存储限流的信息
zuul.ratelimit.repository=REDIS
#配置限流的方式默认服务限流 origin(客户端的IP地址限流) user(用户限流) url(针对访问的路径限流)
zuul.ratelimit.policies.orient-service-url.type=origin
#每分钟只允许访问十次,总访问的时长不能超过5秒
zuul.ratelimit.policies.orient-service-url.limit=10
zuul.ratelimit.policies.orient-service-url.quota=5
zuul.ratelimit.policies.orient-service-url.refresh-interval=60
如果我们不使用rateLimite,可以直接使用redis配合过滤器实现接口限流,具体实现就是获取客户端访问的IP地址和访问的服务名组成key,然后存入到redis中,并从配置文件中获取失效时间。并且通过redis的计数器功能给这个IP地址设置访问次数,如果达到次数就开始限流。
四、redis使用过程中的问题
1.缓存穿透
穿透就好像士兵身上的防弹衣,本来可以抵挡子弹,这时候防弹衣失效了,子弹就会直接集中士兵。 redis本来是为了减少数据库的访问压力的问题,但是如果数据库经过查询也没有数据,当然这时在redis中也没有进行存储,这样就造成每次查询还是访问数据库,就会出现穿透问题。解决办法就是在数据库中没有查询数据的时候,也要在redis设置一下,比如设置一个空值或者false,这样就不会访问数据库了。
2.雪崩问题
雪崩很好解释,灾难片中因为在山谷中的一点相声,引起雪山崩塌的连锁反应。那么在redis中,也会出现因为大量的缓存在同一时间同时失效,就会去访问数据库,这样会给应用和数据库造成很大压力,从而系统崩溃了。解决办法就是把不同业务的失效时间分散开来,防止出现这种问题。
redis面试问题:
- 使用Redis有哪些好处?
(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
(2) 支持丰富数据类型,支持string,list,set,sorted set,hash
(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
2.redis最多能容纳多少数据
没有这个指标。我觉得不是记录数的问题,而是实际容量。所以在配置时可以:设置redis.conf中的maxmemory选项,该选项是告诉Redis当使用了多少物理内存后就开始拒绝后续的写入请求,该参数能很好的保护好你的Redis不会因为使用了过多的物理内存而导致swap(物理内存不够用了),最终严重影响性能甚至崩溃。 - redis常见性能问题和解决方案:
(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…
这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。 - Memcache与Redis的区别都有哪些?(你为啥要redis做缓存)
1)、存储方式
Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。
Redis有部份存在硬盘上,这样能保证数据的持久性。
2)、数据支持类型
Memcache对数据类型支持相对简单。
Redis有复杂的数据类型。
3)、使用底层模型不同
它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。
Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
4)value大小
redis最大可以达到1GB,而memcache只有1MB - MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据
相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6种数据淘汰策略:
voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
**
6、Redis如何做内存优化?
尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。
比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面
7.redis中的hash槽:
Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value
时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 取模,
这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大
致均等的将哈希槽映射到不同的节点。
8.redis的集群如何搭建的
我是用的是Redis Cluster方式来搭建redis集群,具体的搭建过程我自己也整理了一个文档。它的特点是一个 Redis Cluster包含16384(0~16383)个哈希槽,存储在Redis Cluster中的所有键都会被映射到这些slot(哈希槽)中,集群中的节点会分配到大致相同的hash槽数量,它只实现了主从库的数据同步和一致性,对于不同的主库存放的数据不同。
MYSQL数据库的讲解
Innodb引擎
Innodb引擎提供了对数据库ACID事务的支持,并且实现了SQL标准的四种隔离级别,关于数据库事务与其隔离级别的内容请见数据库事务与其隔离级别这篇文章。该引擎还提供了行级锁和外键约束,它的设计目标是处理大容量数据库系统,它本身其实就是基于MySQL后台的完整数据库系统,MySQL运行时Innodb会在内存中建立缓冲池,用于缓冲数据和索引。但是该引擎不支持FULLTEXT类型的索引,而且它没有保存表的行数,当SELECT COUNT() FROM TABLE时需要扫描全表。当需要使用数据库事务时,该引擎当然是首选。由于锁的粒度更小,写操作不会锁定全表,所以在并发较高时,使用Innodb引擎会提升效率。但是使用行级锁也不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表。
名词解析:
ACID
A 事务的原子性(Atomicity):指一个事务要么全部执行,要么不执行.也就是说一个事务不可能只执行了一半就停止了.比如你从取款机取钱,这个事务可以分成两个步骤:1划卡,2出钱.不可能划了卡,而钱却没出来.这两步必须同时完成.要么就不完成.
C 事务的一致性(Consistency):指事务的运行并不改变数据库中数据的一致性.例如,完整性约束了a+b=10,一个事务改变了a,那么b也应该随之改变.
I 独立性(Isolation):事务的独立性也有称作隔离性,是指两个以上的事务不会出现交错执行的状态.因为这样可能会导致数据不一致.
D 持久性(Durability):事务的持久性是指事务执行成功以后,该事务所对数据库所作的更改便是持久的保存在数据库之中,不会无缘无故的回滚.
MyIASM引擎
MyIASM是MySQL默认的引擎,但是它没有提供对数据库事务的支持,也不支持行级锁和外键,因此当INSERT(插入)或UPDATE(更新)数据时即写操作需要锁定整个表,效率便会低一些。不过和Innodb不同,MyIASM中存储了表的行数,于是SELECT COUNT() FROM TABLE时只需要直接读取已经保存好的值而不需要进行全表扫描。如果表的读操作远远多于写操作且不需要数据库事务的支持,那么MyIASM也是很好的选择。
两种引擎的选择
大尺寸的数据集趋向于选择InnoDB引擎,因为它支持事务处理和故障恢复。数据库的大小决定了故障恢复的时间长短,InnoDB可以利用事务日志进行数据恢复,这会比较快。主键查询在InnoDB引擎下也会相当快,不过需要注意的是如果主键太长也会导致性能问题,关于这个问题我会在下文中讲到。大批的INSERT语句(在每个INSERT语句中写入多行,批量插入)在MyISAM下会快一些,但是UPDATE语句在InnoDB下则会更快一些,尤其是在并发量大的时候。
数据库索引:
当一次查询是访问量和数据量都过大时,需要考虑对查询条件字段添加索引。添加索引的语法alter table 表名 add index 索引名称 (字段名多个用逗号分开),alter table 表名 drop index 索引名称。
索引不是越多越好,太多索引文件变大,也会影响效率,另外更新频繁的表不适合增加索引,跟新表时会跟新索引,造成额外的开销。索引失效的情况,第一种,添加过索引的查询条件做了运算,第二种模糊查询是用’% %’,第三字段类型不匹配,比如字符串和数字类型比较,第四查询条件加了or。
https://www.cnblogs.com/0201zcr/p/5296843.html
设计模式
1.程序调用的解耦合。
2.减少代码冗余。
3.业务扩展。
工厂模式
1.简单工厂模式:
比如有一个工厂要造汽车,他可以生产不同品牌的汽车。先定义一个生产汽车的几口,然后不同种类的汽车实现该接口,再定义一个工厂类,创建生产不同汽车的对象,对外提供调用服务。
缺点:
(1)容易出现对象无法被创建问题。
(2)每新增一个新的汽车种类,都要修改工厂类。
三种实现方式
1.普通
2.多个方法工厂模式:
3.多个静态方法
2.工厂方法模式
就是在简单工厂的基础上对工厂的创建做了修改,新建了一个工厂的供应者接口,然后不同类型汽车的工厂实现该接口,对外提供服务。
好处:
先增加汽车类型是不用去修改原来的代码,只需增加一个车辆创建的实现类,和车辆工厂实现类就可以了。就满足了设计模式中的对修改关闭,对扩展开放。
3.抽象工厂模式
工厂方法模式: 一个抽象产品类,可以派生出多个具体产品类。 一个抽象工厂类,可以派生出多个具体工厂类。 每个具体工厂类只能创建一个具体产品类的实例。 抽象工厂模式: 多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。 一个抽象工厂类,可以派生出多个具体工厂类。 每个具体工厂类可以创建多个具体产品类的实例,也就是创建的是一个产品线下的多个产品。
4。单例模式
好处:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
3、有些类如交易所的核心交易引擎,控制着交易流程。类似于hash中的put线程安全问题。
创建方式: 1.饿汉式
项目启动类编译完成就会创建。(线程安全,启动效率低,调用效率高,但是不能延时加载)
2.懒汉式
只用第一次调用时才会实例化对象。(线程不安全,启动效率高,但是可以延时加载,第一调用效率低)
3.双重判定加锁的懒汉式(线程安全,启动效率高,但是可以延时加载,第一调用效率低,)
创建对象是首先要进行第一次if判断,提高性能的,
如果需要穿件对象要进行加锁。
接着进行第二次判断:防止多线程情况下重复创建对象。
出现的问题:
JVM底层模型原因,偶尔会出问题,再多线程情况下,new对象是两步,第一步开辟空间给变量,第二步实例化对象。
线程A,走完第一步还没有做第二步,线程B进来,判断出变量不为空,直接返回使用该对象,B线程使用的对象还没有实例化成功,就会报错。
5.代理模式
其实每个模式名称就表明了该模式的作用,代理模式就是多一个代理类出来,替原对象进行一些操作,比如我们在租房子的时候回去找中介,为什么呢?因为你对该地区房屋的信息掌握的不够全面,希望找一个更熟悉的人去帮你做,此处的代理就是这个意思。再如我们有的时候打官司,我们需要请律师,因为律师在法律方面有专长,可以替我们进行操作,表达我们的想法。
6.门面模式(外观模式)
外观模式是为了解决类与类之间的依赖关系的,像spring一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度,该模式中没有涉及到接口,看下类图:(我们以一个计算机的启动过程为例)