简单工厂模式(又名:静态工厂方法模式):
定义:
-
由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。
-
简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
-
父类定义了创建对象的接口,但是由子类来具体实现,工厂方法让类把实例化的动作推迟到了子类当中,对象的创建由子类来完成。
作用:
实例化对象的时候不再使用 new Object()形式,可以根据用户的选择条件来实例化相关的类。对于客户端来说,去除了具体的类的依赖。只需要给出具体实例的描述给工厂,工厂就会自动返回具体的实例对象。
使用场景:对某一类对象总是要执行相同的流程,但是并不在意这些对象之间的微小差异。
场景对应:公共的父类决定了怎么去处理这一类对象,而子类决定了如何创建这些有着微小差异的不同对象。
简单工厂模式例子:
1、抽象产品类(为子类提供方法)
public abstract class Animal{
//抽象方法,具体实现让子类去实现
public abstract void eat();
}
2、具体实现类
public class Cat extends Animal{
@Override
public void start() {
System.out.println("我要吃猫粮");
}
}
public class Dog extends Animal{
@Override
public void start() {
System.out.println("我要吃狗粮");
}
}
3、工厂类
public class AnimalFactory {
public static Animal createAnimal(String type){
Animal animal = null;
switch (type) {
case "cat":
animal = new Cat();
break;
case "dog":
animal = new Dog();
break;
}
return animal ;
}
}
4、客户端
public class CreatAnimal {
public static void main(String[]args){
AnimalFactory.createAnimal("dog").start();
}
}
缺点:你看工厂类,你应该就知道缺点在哪里了,不做过多名词性解释。
注意事项:
工厂模式分三种:简单工厂模式、工厂模式、抽象工厂模式,简单工厂模式只是其中一种。
网上很多人把简单工厂模式和工厂模式给混淆了,这里需要给出区分:简单工厂模式,通过参数。工厂模式,通过继承。
spring中的例子:
-
BeanFactory:根据传入一个唯一的标识来获得bean对象。
- BeanFactory的实现原理:
- 第一步:spring中配置的bean会被注册到BeanFactory中,BeanFactory会将bean存到concurrenthashmap中。
- 第二步:通过反射或者CGLIB去实例化bean。
BeanFactory和FactoryBean的区别:BeanFactory不解释了,FactoryBean就是一个能生产或修饰对象生成的工厂Bean,类似于设计模式中的工厂模式和装饰器模式。它能在需要的时候生产一个对象,且不仅仅限于它自身,它能返回任何Bean的实例。
jdk1.7、1.8、1.9不同版本中接口和抽象类的区别及用法
接口:一个类可以实现多个接口,该子类如果不是抽象类,那么实现了接口就必须重写接口中全部方法。
jdk1.7的接口:
- 接口中的方法都会默认加上 abstract 进行修饰成抽象方法,不能用static进行修饰。
- 接口中的常量都要加上 static final进行修饰,固接口没有变量,只能定义常量。
- 接口中没有构造方法,不能进行实例化对象。
jdk1.8的接口:
- 接口里可以有方法体,方法可以用static、default进行修饰。
- 静态方法可以直接通过接口名加“.”进行方法调用,不能通过接口实现类对象来调用接口静态方法。
- 默认方法能够解决接口如果添加抽象方法,而实现类不去重写的话会报错这个问题。
- 实现类实现接口中的默认方法,可以选择不进行重写,保持接口中的默认方法体,如果要重写,会覆盖原本的实现。
- 实现类对象可以调用自己重写的方法,也可以调用接口的默认方法。
- 如果实现类重写实现了接口默认方法,那么调用的就是实现的方法。
jdk1.9的接口:
- 接口可以定义私有方法。
- 普通私有方法:接口中如果多个默认方法出现了重复的代码,我们喜欢抽取到私有方法里面,就是这个用法。
- 私有静态方法:和上面一样,只不过这里解决的是静态方法的重复代码问题。
注意事项:重写也叫覆写,两者一样。
重写与重载的区别:
- 重载:只能在同一个类中,同一个方法,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
- 重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。
抽象类:一个子类继承了抽象类,那么抽象类中的非抽象方法不用重写,其他必须重写。
jdk1.8的抽象类:
- 抽象类不能进行实例化,只能通过子类进行继承来可以被使用。
- 抽象类可以有非抽象方法。
- 抽象类是一个类,可以和类一样具有很多东西,构造、成员等等。
- 含有抽象方法的类只能定义为抽象类。
- 抽象类中的抽象方法要被使用,必须由子类复写所有的抽象方法后,建立子类对象调用,如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。
接口和抽象类的区别:接口解决了一个类只能继承一个抽象类的问题。
- 1.8过后的接口与抽象类的区别越来越不那么明显,只不过抽象类提供的东西比接口多。
- 正是接口和抽象类的使用使得多态能够体现。
- Java集合中用的就是:接口->抽象类->类,模式。
- 类是对事物的抽象,抽象类是对类的抽象,接口是对抽象类的抽象。
什么时候选择接口和抽象类:抽象类更多使用于类层级有共同性的情况,而接口更多使用于方法层级有共同性的情况。
接口只提供方法,不管实现得怎么样,而抽象类方法的实现最好符合父子类之间的关联特征。
jdk1.7、1.8 HashMap和ConcurrentHashMap的点点滴滴
为什么要用:可以存键值对,key不能重复,访问速度快,插入删除快,就这么简单。
怎么用:Map<K, V> map=new HashMap<>(); map.put("K","V"); (多态)
需要注意什么:多线程下安全么?不安全。网上看一些博客专家写的关于hashmap为什么非线程安全,说的头头是道,看到最后都没有给你讲怎么去解决,服了,这不是耍流氓吗?
怎么解决:
- 使用collections.synchroizedMap()返回一个线程安全的map。
- 使用ConcurrentHashMap。
jdk1.7:hashmap源码分析结果(原代码不打算贴出来了,读者可自行根据分析对应到源码中去)
- 结构:
- hashmap是初始化时是一个数组,数组是一块连续内存空间,数组上的每个位置都是一个哈希桶bucket(由Entry实体组成的链表),且数组上的每一个元素都是链表头,next指向下一个节点,形成一条链表,当链表的长度大于8的时候会转化成红黑树,当长度再次变得小于6的时候会又转化成链表结构。
- hashmap的默认初始化大小为1 << 4也就是16,最大容量是1 << 30也就是2的30次方。
- 添加:
- 添加是在链表头进行添加的。
- hashmap中就算按我们规定的顺序去添加元素,最终元素的存储位置顺序也是无序的,可以存在null键值对,但不允许key重复。
- hash冲突就是添加的key-value的时候key的hash值与其他key-value键值对中key的hash值相同,发生碰撞,但是equals结果不同,可以利用链地址法将该位置的entry<k,v>节点再延申出一条链表,next指向冲突的那个entryk<k,v>。
- 如果两者hash值相同,equals结果为true也就是对象相同的,那么新值会覆盖旧值。
- 变量modCount用来计算hashmap中结构被修改的次数,添加和删除操作会导致结构发生改变,每次+1,在并发操作下,快速失败机制正是利用此来进行判断的,固hashmap是线程不安全的。
- 扩容:
- hashmap通过threshold = capacity * loadFactor来计算是否进行扩容,threshold变量是一个临界值,loadFactor是负载因子值为0.75。当hashmap数组长度大于这个临界值的时候会进行扩容,会创建新数组,大小为原来的两倍,再将原来的内容给放进去。
- 扩容后新数组中的内容都是重新进行分配的,固很耗性能。
- 查找:
- 前面的添加查找也需要先计算key的hash,再根据hash值去进行存储,也是包含了一个查询索引操作。
- hashmap会首先通过hashcode查找key的hash值来查找数组上的bucket,找到该bucket,再用equals在链表找到该key对应的value。
- get是按顺序查找的,要查的entry刚好是最后一个,那性能就不太好了。复杂度是O(n)。
jdk1.8:hashmap源码分析结果
- 结构:
- 在jdk1.7的基础上进行改进,当链表长度大于8的时候,会转化成红黑树,之后红黑树如果长度变得小于6,就会转化为链表。
- 在1.8之前,新插入的元素都是放在了链表的头部位置,但是这种操作在高并发的环境下容易导致死锁,所以1.8之后,新插入的元素都放在了链表的尾部。
jdk1.8:concurrenthashmap源码分析结果
- 在ConcurrentHashMap中,大量使用了U.compareAndSwapXXX的方法,这个方法是利用一个CAS算法实现无锁化的修改值的操作,他可以大大降低锁代理的性能消耗。这个算法的基本思想就是不断地去比较当前内存中的变量值与你指定的一个变量值是否相等,如果相等,则接受你指定的修改的值,否则拒绝你的操作。因为当前线程中的值已经不是最新的值,你的修改很可能会覆盖掉其他线程修改的结果。这一点与乐观锁,SVN的思想是比较类似的。
-
在ConcurrentHashMap中定义了三个原子操作,用于对指定位置的节点进行操作。这三种原子操作被广泛的使用在ConcurrentHashMap的get和put等方法中,正是这些原子操作保证了ConcurrentHashMap的线程安全。
// 获取tab数组的第i个node
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
// 利用CAS算法设置i位置上的node节点。在CAS中,会比较内存中的值与你指定的这个值是否相等,如果相等才接受
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
// 利用volatile方法设置第i个节点的值,这个操作一定是成功的。
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
- ConcurrentHashMap的实现使用的是锁分离思想,在putVal()方法源码可以看到只是锁住的是一个node节点,可以理解为hash值相同组成的链表的头节点,锁的粒度为头节点,而锁住Node之前的操作是基于在volatile和CAS之上无锁并且线程安全的。
public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
本文涉及其他知识点:
数组、链表、红黑树、hashcode、equals、CAS、死锁、乐观锁、SVN、锁分离思想、volatile原子性。
Spring反射
反射其实就是通过对class这个类进行展开来获取实例化对象。例子: spring中的bean。
Spring动态代理
有两种方式:
-
第一种也就是JDK自带的动态代理,利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,需要指定一个类加载器,然后生成的代理对象实现类的接口或类的类型,接着处理额外功能,JDK是基于接口。
-
第二种也就是Cglib的动态代理,Cglib是动态代理利用asm的开源包,对代理对象的Class文件加载进来,通过修改其字节码生成的子类来处理,Cglib是基于继承父类生成的代理类。
在Spirng当中动态代理的使用:
-
如果目标对象实现了接口,默认情况下会采用JDK的动态代理来实现AOP。
-
如果目标对象实现了接口,也可以强制使用CGlib来实现AOP。
-
如果目标对象没有实现接口,必须采用Cglib库,Spirng会自动在JDK和CGlib用切换。
如何强制使用CGlib来实现AOP:
-
添加CGlibjar包:SPRING_HOME/cglib/*.jar。
-
在Spring的配置文件中加入 //默认是false 也就是用JDK的 改为true就是用Cglib的。
JDK和动态代理和CGlib字节码的生成区别:
-
JDK动态代理制能对实现了接口的类生成代理,而不是针对类。
-
CGLIB是针对类实现代理,主要对指定的类生成一个子类,覆盖其中的方法,添加额外功能,因为是继承,所以该类方法不能用final来声明。
springioc :
例如我们在启动spring工程的时候,bean工厂applicationContext会负责创建实例bean,往各个bean注入依赖。 applicationContext会去调用一个叫refresh()方法,这个方法内部是重量级锁synchronized ,在里面会去根据配置来准备容器,完后会解析配置bean,通过类加载器来加载bean,然后全部注册到bean工厂(hashmap)里面,然后做一些初始化,会去初始化所有的bean。bean都是单例的。在除非是懒加载。
spring DI:
getBean方法会从容器中获取bean,然后注入实例。
springaop:
作用:可以在不影响核心业务的情况下,加入一些其他功能。
业务注解: @Aspect @Component @Pointcut(注解中的值便是切点的表达式) @AfterReturning()
aop指示器:
- execution指示器——用来匹配方法执行连接点,即匹配哪个方法执行。
- within指示器——用来匹配一个类中所有方法的调用。
- 如果目标对象实现了任何接口,Spring AOP 会创建基于CGLIB 的动态代理,这时候需要使用—— target 指示器。
- 如果目标对象没有实现任何接口,Spring AOP 会创建基于JDK的动态代理,这时候需要使用 ——this 指示器。
- args指示器——用来匹配具体的方法参数。
- @target指示器——不要和 target 指示器混淆,该指示器用于匹配连接点所在的类是否拥有指定类型的注解。
- @annotation指示器——用于匹配连接点的方法是否有某个注解。
Advice 通知: 在连接点出要执行的代码。
- 开启 Advice:如果要在 Spring 中使用 Spring AOP 需要开启 Advice,使用 @EnableAspectJAutoProxy 注解就可以了。
- Before Advice: 用来执行在方法调用之前的操作。
- After Advice:该 Advice 会在方法被调用之后被执行。
- Around Advice:可以控制方法在执行前后的行为。
自定义Annotation:
第一步:创建 Annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CalculateExecuteTime {
第二步:创建切面
@Aspect
@Component
public class CalculateExecuteTimeAspect {
第三步:创建切点和通知
@Aspect
@Component
public class CalculateExecuteTimeAspect {
@Around("@annotation(CalculateExecuteTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
//这里的 ProceedingJoinPoint 代表连接的的方法
第四步:在方法上加上自定义注解
@Service
public class WeixinService {
@CalculateExecuteTime
public void share(@NotNull String articleUrl) {
涉及知识:spring单例模式、懒加载。
spring事务:
-
隔离级别:default、read_uncommit、read_commit、repeatable_read、serializable
-
传播行为:required、supports、mandatory、requires_new、not_supported、never、nested
原理:事务就是去看哪个对象有加上事务注解,找到了就对象注解进行解析,当该操作出现异常的时候,会去执行通知器和拦截器中的方法,拦截器内部会根据事务属性来获取事务管理器,接下来就是在事务管理器里面去做判断和执行了,最后根据异常去调用管理器的回滚方法。回滚首先判断异常是不是RuntimeException和error,接着判断事务对象是不是和异常匹配,匹配就回滚。如果事务没有异常就进行提交,提交就想要去判断事务的状态,也就是判断有没有异常发生,没有就进行jdbc数据库提交。事务其实就是建立在aop之上的。
什么时候需要用到事务:
- 读数据不需要,添加、修改、删除操作需要。
基于注解的事务使用方法:
事务的传播性:@Transactional(propagation=Propagation.REQUIRED)
- 如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
事务的超时性:@Transactional(timeout=30) //默认是30秒
- 注意这里说的是事务的超时性而不是Connection的超时性,这两个是有区别的
事务的隔离级别:@Transactional(isolation = Isolation.READ_UNCOMMITTED)
- 读取未提交数据(会出现脏读, 不可重复读) 基本不使用
回滚:
- 指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)
- 指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
- 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。
只读:@Transactional(readOnly=true)
- 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。
SpringMVC工作流程:
用户发送请求给前端控制器,控制器调用映射器来获取controller上的url,生成controller对象返回给前端控制器,前端控制器调用适配器来执行controller方法,将生成的模型视图返回给前端控制器,控制器调用视图解析器,解析成视图,返回给前端控制器,然后渲染给用户。
-
前端控制器(dispatcherservlet)
-
控制器映射器(handlermapping)
-
控制器(controller)
-
适配器(handleradapter)
-
视图解析器(viewreslover)
springmvc线程安全问题:是单例模式的问题
当有多个线程同时调用controller方法的时候尽量不要定义成员变量在controller里面,如果要使用到,可以利用scope=”prototype”进行声明,这样就把单例变成了多例,也可以使用ThreadLocal来定义变量。
常用注解:
@RequestBody:接收http请求的json数据,json转对象。
@ResponseBody:将controller返回对象转化为json对象响应给客户。
注解原理:
注解本身是annotation的扩展,spring反射会获取注解,返回生成的动态代理对象,调用代理对象invoke方法从常量池中获取索引对应值。
涉及知识:单例模式。