一、简介
Spring框架中常见的设计模式有:
- 工厂模式
- 单例模式
- 策略模式
- 代理模式
- 模版方法
- 适配器模式
二、工厂模式
Spring通过Ioc控制反转将创建对象的控制权交给Spring框架并管理各个bean对象之间的依赖关系。之后Spring框架再使用工厂模式,通过BeanFactory或ApplicationContext来创建对象。
BeanFactory(父接口) :延迟注入(使用到某个 bean 的时候才会注入),相比ApplicationContext来说会占用更少的内存,程序启动速度更快。通过getBean方法调用容器帮助我们实例化对象(多例)。
ApplicationContext (子接口):容器启动的时候,不管用没用到,一次性创建所有 bean 。BeanFactory 仅提供了最基本的依赖注入支持,ApplicationContext 扩展了 BeanFactory ,除了有BeanFactory的功能还有额外更多功能,一般开发使用ApplicationContext会更多。(单例)
常见ApplicationContext的实现类:
ClassPathXmlApplicationContext=======>通过相对路径加载主配置"文件"
FileSystemXmlApplicationContext======>通过绝对路径加载主配置"文件"
AnnotationConfigApplicationContext===>加载主配置"类"
WebApplicationContext===>Web容器下按照配置文件加载bean
使用工厂模式创建对象的流程:
1、创建实体类
public class Student {
private int sid;
private String sname;
private String ssex;
public Student() {
}
public Student(int sid, String sname, String ssex) {
this.sid = sid;
this.sname = sname;
this.ssex = ssex;
}
public int getSid() {
return sid;
}
public void setSid(int sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public String getSsex() {
return ssex;
}
public void setSsex(String ssex) {
this.ssex = ssex;
}
@Override
public String toString() {
return "Student{" +
"sid=" + sid +
", sname='" + sname + '\'' +
", ssex='" + ssex + '\'' +
'}';
}
}
2、将bean通过xml文件注入容器
<bean id="stu" class="com.ape.bean.Student"></bean>
3、使用工厂模式创建bean对象并通过getbean( )方法获取
ClassPathXmlApplicationContext ApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
Student stu = (Student) ApplicationContext.getBean("stu");
三、单例模式
Bean的默认作用域为单例,通过单例设计模式进行设计实现。
单例(singleton)是指Spring只会bean对象创建唯一实例。
除此之外,作用域还有:
prototype(多例):每次获取bean,Spring都会创建一个新的bean实例。
request:每一次HTTP请求,Spring都会创建一个新的bean实例。
session:不同的HTTP会话,Spring都会创建不同的bean实例。
使用单例模式(默认作用域):
<bean id="stu" class="com.ape.bean.Student" scope="singleton"></bean>
ClassPathXmlApplicationContext ApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
Student stu1 = (Student) ApplicationContext.getBean("stu");
Student stu2 = (Student) ApplicationContext.getBean("stu");
System.out.println("stu1和stu2内存地址是否相等:"+(stu1==stu2));
使用多例模式:
<bean id="stu" class="com.ape.bean.Student" scope="prototype"></bean>
ClassPathXmlApplicationContext ApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
Student stu1 = (Student) ApplicationContext.getBean("stu");
Student stu2 = (Student) ApplicationContext.getBean("stu");
System.out.println("stu1和stu2内存地址是否相等:"+(stu1==stu2));
四、策略模式
策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,提高了代码的可拓展性,降低了耦合度。
1、定义策略接口
public interface IStarategy {
public void doSomething();
}
2、定义具体的策略实现类
public class StarategyImp1 implements IStarategy{
@Override
public void doSomething() {
System.out.println("这是具体的实现策略的方法1");
}
}
public class StarategyImp2 implements IStarategy{
@Override
public void doSomething() {
System.out.println("这是具体的实现策略的方法2");
}
}
public class StarategyImp3 implements IStarategy{
@Override
public void doSomething() {
System.out.println("这是具体的实现策略的方法3");
}
}
3、封装决策
public class Context {
private IStarategy iStarategy;
public Context(IStarategy iStarategy) {
this.iStarategy = iStarategy;
}
public void toDo(){
iStarategy.doSomething();
}
}
4、使用不同策略
public class Test {
public static void main(String[] args) {
IStarategy iStarategy1 = new StarategyImp1();
IStarategy iStarategy2 = new StarategyImp2();
IStarategy iStarategy3 = new StarategyImp3();
Context context1 = new Context(iStarategy1);
Context context2 = new Context(iStarategy2);
Context context3 = new Context(iStarategy3);
context1.toDo();
context2.toDo();
context3.toDo();
}
}
结果如下:
综上,我们可以对策略模式进行总结:
优点:
- 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
- 策略模式提供了管理相关的算法簇的办法。
- 策略模式提供了可以替换继承关系的办法。
- 使用策略模式可以避免使用多重条件转移语句。
缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将产生很多策略类。
适用场景:
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
- 一个对象有很多的行为,如果不进行恰当的管理,这些行为就只能使用多重的条件选择语句来实现。
- 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。
五、代理模式
使用代理模式进行方法的增强和通知,Spring-AOP的实现就依赖动态代理,又分为静态AOP和动态AOP,静态AOP是指AspectJ实现的AOP,是将切面代码直接编译到Java类文件中。动态AOP是指将切面代码进行动态织入实现的AOP。当Spring的AOP为动态AOP时,实现的技术又分为:JDK提供的动态代理技术 和 CGLIB。尽管实现技术不一样,但都是基于代理模式,都是生成一个代理对象。
静态代理:
1、定义接口
public interface IWoman {
public void sendLoveByEyes();
}
2、定义被代理对象和代理对象
public class PanjinlianImp implements IWoman{
@Override
public void sendLoveByEyes() {
System.out.println("============回眸一笑百媚生=============");
}
}
public class WangpoImp implements IWoman{
private IWoman woman;
public WangpoImp() {
}
public WangpoImp(IWoman woman) {
this.woman = woman;
}
@Override
public void sendLoveByEyes() {
System.out.println("王婆卖瓜自卖自夸");
woman.sendLoveByEyes();
}
}
3、定义实现类
public class Ximenqing {
public static void main(String[] args) {
PanjinlianImp panjinlian = new PanjinlianImp();
WangpoImp wangpoImp = new WangpoImp(panjinlian);
wangpoImp.sendLoveByEyes();
}
}
运行结果如下:
动态代理:
- 使用JDK Proxy
1、定义接口
public interface Isinger {
public void sing();
}
2、定义被代理对象
public class JaychouImp implements Isinger{
@Override
public void sing() {
System.out.println("====七里香====");
}
}
3、定义代理对象并实现
public class Manager {
public static void main(String[] args) {
JaychouImp JaychouImp = new JaychouImp();
/**
* 基于接口的动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 涉及的类:Proxy
* 提供者:JDK官方
* 如何创建代理对象:
* 使用Proxy类中的newProxyInstance方法
* 创建代理对象的要求:
* 被代理类最少实现一个接口,如果没有则不能使用
* newProxyInstance方法的参数:
* ClassLoader:类加载器
* 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
* Class[]:字节码数组
* 它是用于让代理对象和被代理对象有相同方法。固定写法。
* InvocationHandler:用于提供增强的代码
*
*/
Isinger manager = (Isinger) Proxy.newProxyInstance(JaychouImp.getClass().getClassLoader(), JaychouImp.getClass().getInterfaces(), new InvocationHandler() {
/***
* Object proxy=====》被代理对象的引用===(周杰伦对象)
* Method method====》执行的方法========(周杰伦唱歌方法)
* Object[] args====》执行方法的参数=====(周杰伦唱歌方法的参数)
* Object===========》执行方法的返回值===(周杰伦唱歌方法的返回值)
* */
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* 方法参数的含义
* proxy 代理对象的引用
* method 当前执行的方法
* args 当前执行方法所需的参数
* Object 和被代理对象方法有相同的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("变魔术");
Object obj = method.invoke(JaychouImp, args);
return obj;
}
});
manager.sing();
}
}
运行结果如下:
- 使用Cglib
1、定义接口
public interface ISinger {
public void sing();
}
2、定义被代理对象
public class JJImp implements ISinger {
@Override
public void sing() {
System.out.println("===美人鱼===");
}
}
3、定义代理对象并实现
public class Manager {
public static void main(String[] args) {
JJImp jjImp = new JJImp();
/**
* 基于子类的动态代理
* 涉及的类:Enhancer
* 提供者:第三方cglib库
* 开发环境:添加cglib依赖坐标
* 如何创建代理对象:
* 使用Enhancer类中的create方法
* 创建代理对象的要求:
* 被代理类不能是最终类
* create方法的参数:
* Class:字节码
* 它是用于指定被代理对象的字节码。
*
* Callback:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
* 我们一般写的都是该接口的子接口实现类:MethodInterceptor
*
*/
ISinger manager = (ISinger) Enhancer.create(jjImp.getClass(), new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("====郭冬临登场!====");
Object obj = method.invoke(jjImp, objects);
return obj;
}
});
manager.sing();
}
}
运行结果如下:
六、模版方法
模板方法模式是在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。使用模版方法模式的目的是避免编写重复代码,以便开发人员可以专注于核心业务逻辑的实现。
下述以请假为例解释模版方法的概念:
1、定义请假的流程:
public abstract class AskForLeaveFlow {
// 领导审批
protected abstract void firstGroupLeader(String name);
// 部门负责人审批
protected void secondGroupLeader(String name) {
}
// 告知HR有人请假了
private final void notifyHr(String name) {
System.out.println("有人请假了,请假人:" + name);
}
// 请假模版
public void askForLeave(String name) {
firstGroupLeader(name);
secondGroupLeader(name);
notifyHr(name);
}
}
2、制定请假方式1
public class Statery1 extends AskForLeaveFlow {
@Override
protected void firstGroupLeader(String name) {
System.out.println("有人请假,请假人:" + name);
}
}
3、制定请假方式2
public class Statery2 extends AskForLeaveFlow {
@Override
protected void firstGroupLeader(String name) {
System.out.println("有人请假,请假人:" + name);
}
@Override
protected void secondGroupLeader(String name){
System.out.println("有人请假,请假人:" + name);
}
}
4、具体请假实现
public class Test {
public static void main(String[] args) {
// 请假方法1流程模版
AskForLeaveFlow Statery1= new Statery1();
Statery1.askForLeave("小明");
// 结果:有人请假,请假人:小明
// 有人请假了,请假人:小明
// 请假方法2流程模版
AskForLeaveFlow Statery2= new Statery2();
Statery2.askForLeave("小明");
// 结果:有人请假,请假人:小明
// 有人请假,请假人:小明
// 有人请假了,请假人:小明
}
}
在Spring框架中,Spring提供了许多模版对象,例如JDBCTemplate、RedisTemplate等
七、适配器模式
适配器模式指的是将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作。Spring AOP的增强或通知就使用到了适配器模式,Spring MVC也使用到了适配器模式。
Spring AOP通知使用适配器模式:
1、定义业务层接口和实现类
public interface Iservice {
public void add();
public void delete();
public void update();
public void find();
}
@Service
public class ServiceImp implements Iservice{
@Override
public void add() {
System.out.println("这是service的新增方法");
}
@Override
public void delete() {
System.out.println("这是service的删除方法");
}
@Override
public void update() {
System.out.println("这是service的修改方法");
}
@Override
public void find() {
System.out.println("这是service的查询方法");
}
}
2、定义工具类模拟日志测试通知
@Component
@Aspect
public class logger {
@Pointcut(value = "execution(* com.ape.service.*.* (..))")
public void dian(){}
@Around(value = "dian()")
public Object aroundLogger(ProceedingJoinPoint joinPoint){
Object obj = null;
try {
System.out.println("这是around通知的前置通知");
Object[] args = joinPoint.getArgs();
obj = joinPoint.proceed(args);
System.out.println("这是around通知的返回通知");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("这是around通知的异常通知");
} finally {
System.out.println("这是around通知的后置通知");
}
return obj;
}
}
3、编写xml文件
<context:component-scan base-package="com.ape"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
4、测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application.xml")
public class Test01 {
@Autowired
Iservice iservice;
@Test
public void show1(){
iservice.add();
}
}
运行结果如下: