Spring中常见的设计模式
Ⅰ、23种经典设计模式分类
通常来说,设计模式都是混合使用,不会独立应用。利用穷举法充分理解设计模式的应用场景。在平时的应用中,不是用设计模式去生搬硬套,而是根据具体业务问题需要时借鉴。
Ⅱ、设计模式在应用中遵循六大原则
开闭原则
里氏交换原则
依赖倒转原则
接口隔离原则
迪米特法则(最少知道原则)
合成复用原则
Ⅲ、设计模式之间的关系
Ⅳ、设计模式由来
1、简单工厂模式(Factory)
也叫静态工厂模式
解决:用户与产品之间的问题
小作坊式的生产模式
用户本身不关心生产的过程,只关心生产这个结果。
Spring应用
<BeanFactory> <bean scope=“singleton/prototype”> </bean> </BeanFactory>
总结
2、工厂方法模式(Factory Method)
工厂方法模式:更私人的。
抽象工厂模式:
(子类继承抽象工厂类重写方法)
是用户的主入口、对用户更简单。
在Spring中应用最为广泛。
3、单例模式(保证独一无二)
单例模式:一个类,在整个系统运行过程中,只允许产生一个实例。(有且只有一个 New)
应用: 工厂本身、IOC容器、配置文件、日历、Listener本身是单例的。
解决一个并发访问的时候线程安全问题,保证单例的技术方案
饿汉式:线程安全
在实例使用之前,不管你用不用,我都先new出来再说,避免线程安全问题。
饿汉式天生线程安全,类加载的时候初始化一次对象,效率比懒汉式高。
缺点:类加载时就实例,占茅坑不拉屎。
public class SingletonEH {
//类加载的时候初始化一次
private static final SingletonEH instance = new SingletonEH();
//私有的构造函数,保证外类不能实例化本类
private SingletonEH() {}
//功能描述:(对外暴露的方法)
public static SingletonEH getInstance(){
return instance;
}
}
懒汉式:线程不安全(延时加载)
默认加载的时候不实例化,在需要这个实例的时候才实例化。
懒汉式线程不安全,需要加上 synchronize同步锁,同步锁影响了程序执行效率。
public class SingletonLHsyn {
private static SingletonLHsyn instance;
private SingletonLHsyn() {}
//必须加锁 synchronized 才能保证单例,但加锁会影响效率。
public static synchronized SingletonLHsyn getInstance() {
if (instance == null) {
instance = new SingletonLHsyn();
}
return instance;
}
}
注册登记式:Spring最常用的
每使用一次,都往一个固定的容器中注册并且将使用过的对象进行缓存,下次取对象,直接从缓存中取,以保持每次获取的对象都是同一个。
IOC中的单例模式就是经典的登录注册式单例。
枚举式:也是注册登记的一种,不过使用枚举容器。
序列化与反序列化:重写JVM的readResolve()。
总结
4、原型模式(孙悟空吹毫毛)
原型模式:首先有个原型,数据内容相同,但对象实例不同(完全两个个体)。
应用:DTO VO POJO entry
DTO与VO之间存在一些属性名称、类型都相同。
数据库中表查询出来的对象会赋值给DTO,不会再赋值给MVC中Model,而是把DTO的值赋值给VO,再VO的值传到View中去。
复制
Scope=”prototype”
Apache 反射去实现(原型模式)
实现cloneable接口
Clone()克隆
浅拷贝(成员属性)
深拷贝(连成员对象List<>()复制)
总结
5、代理模式(Proxy)
定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。
应用:AOP实现、拦截器、中介、黄牛、媒婆、解耦、专人做专事、增强、自己不想做的是又不得不做。
角色:代理类、目标类(被代理的类)
例如:找对象但决策权在自己
Controller动态代理
jdk和Cglib代理模式底层实现:字节码重组
JDK动态代理:接口实现
CGLib代理:继承
分类:
静态代理
静态代理,在代理之前,所有东西都已知(人工)。
动态代理动态代理,在代
理之前,所有东西都无知(智能化、自动化)
JDK动态代理
UserManager.java
package www.huihex.pattern.proxy.jdkcglibproxy;
/**
* Created by llx on 2019年11月20日
* 用户管理接口
*/
public interface UserManager {
//新增用户抽象方法
void addUser(String userName,String password);
//删除用户抽象方法
void delUser(String userName);
}
UserManagerIMpl.java
package www.huihex.pattern.proxy.jdkcglibproxy;
/**
* Created by llx on 2019年11月20日
* 用户管理实现类,实现用户管理接口
*/
public class UserManagerImpl implements UserManager{
//重写新增用户方法
@Override
public void addUser(String userName, String password) {
System.out.println("调用了新增的方法!");
System.out.println("传入参数为 userName: "+userName+" password: "+password);
}
//重写删除用户方法
@Override
public void delUser(String userName) {
System.out.println("调用了删除的方法!");
System.out.println("传入参数为 userName: "+userName);
}
}
JdkProxy.java
package www.huihex.pattern.proxy.jdkcglibproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Created by llx on 2019年11月20日
* JDK动态代理实现InvocationHandler接口
*/
public class JdkProxy implements InvocationHandler {
//需要代理的目标对象
private Object target ;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK动态代理,监听开始!");
Object result = method.invoke(target, args);
System.out.println("JDK动态代理,监听结束!");
return result;
}
//定义获取代理对象方法
private Object getJDKProxy(Object targetObject){
//为目标对象target赋值
this.target = targetObject;
//JDK动态代理只能针对实现了接口的类进行代理,newProxyInstance 函数所需参数就可看出
//底层用 字节码重组实现的
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
}
public static void main(String[] args) {
JdkProxy jdkProxy = new JdkProxy();//实例化JDKProxy对象
UserManager user = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl());//获取代理对象
user.addUser("admin", "123123");//执行新增方法
}
}
CGlib动态代理
Cglib动态代理(需要导入两个jar包,asm-5.2.jar,cglib-3.2.5.jar。版本自行选择)
CglibProxy.java
package www.huihex.pattern.proxy.jdkcglibproxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* Created by llx on 2019年11月20日
* Cglib动态代理,实现MethodInterceptor接口
* Cglib动态代理(需要导入两个jar包,asm-5.2.jar,cglib-3.2.5.jar。版本自行选择)
*/
public class CglibProxy implements MethodInterceptor {
//需要代理的目标对象
private Object target;
//重写拦截方法
@Override
public Object intercept(Object obj, Method method, Object[] arr, MethodProxy proxy) throws Throwable {
System.out.println("Cglib动态代理,监听开始!");
//方法执行,参数:target目标对象、 arr参数数组
Object invoke = method.invoke(target, arr);
System.out.println("Cglib动态代理,监听结束!");
return invoke;
}
//定义获取代理对象方法
public Object getCglibProxy(Object objectTarget){
//为目标对象target赋值
this.target = objectTarget;
Enhancer enhancer = new Enhancer();
//设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
enhancer.setSuperclass(objectTarget.getClass());
enhancer.setCallback(this);// 设置回调
Object result = enhancer.create();//创建并返回代理对象
return result;
}
public static void main(String[] args) {
//实例化CglibProxy对象
CglibProxy cglib = new CglibProxy();
//获取代理对象
UserManager user = (UserManager) cglib.getCglibProxy(new UserManagerImpl());
//执行删除方法
user.delUser("admin");
}
}
总结
1、原理区别:
利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,
在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
2、何时使用JDK还是CGLIB?
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 。
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP 。
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换。
3、如何强制使用CGLIB实现AOP?
(1)添加CGLIB库,SPRING_HOME/cglib/*.jar
(2)在spring配置文件中加入
<aop:aspectj-autoproxy proxy-target-class="true"/>
4、JDK动态代理和CGLIB字节码生成的区别?
(1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 因为是继承,所以该类或方法最好不要声明成final 。
5、CGlib比JDK快?
1)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,
在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,
因为CGLib原理是动态生成被代理类的子类。
2)在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,
总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。
6、Spring如何选择用JDK还是CGLIB?
1)当Bean实现接口时,Spring就会用JDK的动态代理。
2)当Bean没有实现接口时,Spring使用CGlib是实现。
3)可以强制使用CGlib
(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)。
6、策略模式(Strategy)
最终执行结果是固定的。执行过程和执行逻辑不一样。
Spring 中在实例化对象的时候用到 Strategy 模式,在 SimpleInstantiationStrategy 有使用。
应用:
比较器、固定算法、商城的支付方式、登录方式选择、旅行路线的选择。
优点:
符合开闭原则
避免使用多重条件转移语句
比如省去大量的 if/else 和 switch 语句,降低代码的耦合
提高算法的保密性和安全性
只需知道策略的作用,而不关心内部实现
缺点:
客户端必须知道所有的策略类,并自行决定使用哪一个策略类
产生很多策略类
总结
7、模板方法模式(Template 流程标准化,原料自己加。)
JdbcTemplate 完美
总结
8、委派模式(干活是你的(普通员工),功劳是我的(项目经理))
DispatcherServlet
总结
9、适配器模式((兼容)Adapter 需要一个转换头)
不改变原来的代码,也要兼容新的需求。
总结
10、装饰者模式(Decorator 也叫包装器模式)
需要包装,但不改变本质(同宗同源)。
特点
- 注重覆盖、扩展。
- 装饰器和被装饰器都实现同一个接口,主要目的是为了扩展之后依旧保留 OOP 关系(同宗同源)。
- 满足 is-a 的关系。
应用
IO 流包装、数据源包装、简历包装
总结
11、观察者模式(完成时通知我)
发布者(Publish)和订阅者(Subscribe)
但观察者模式只是订阅者来解耦 然后做出相应
应用
监听器 鼠标事件 短信邮箱通知
Java回调机制
用一句话讲明回调机制就是: 在A类里面拥有一个类B的对象,调用B类的某个方法并把自身引用传入,在B类的这个方法里面又通过传进来的A的引用来调用A类的某个方法(这个最后调用的A类的方法就叫做回调方法)。
总结
各设计模式对比及编程思想总结Spring AOP OOP BOP IOC DI/DL
AOP(面向切面编程)
AOP:面向切面编程思想
动态代理只是AOP的一种实现技术的手段。
Aspect(切面)
Expretion(切面表达式)
作用:
解耦、专人干专事、增强、规则。
OOP(面向对象编程)
BOP(面向Bean编程)
一切从Bean 开始。
IOC(控制反转)
IOC容器是一个Map。
DI/DL(依赖注入)
依赖注入、依赖查找,Spring 不仅保存自己创建的对象,而且保存对象与对象之间的关系。
注入即赋值,主要三种方式构造方法、set 方法、直接赋值。
Spring 的加载步骤
定位、载入、注册:再确定要不要初始化Spring