Spring源码剖析设计模式


1 观察者模式

基础知识:
Java常用设计模式

定义:

对象之间存在一对多或者一对一依赖,当一个对象改变状态,依赖它的对象会收到通知并自动更新。
MQ其实就属于一种观察者模式,发布者发布信息,订阅者获取信息,订阅了就能收到信息,没订阅就收不到信息。

优点:

1、观察者和被观察者是抽象耦合的。
2、建立一套触发机制。

缺点:

1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。

1.1 Spring观察者模式

ApplicationContext 事件机制是观察者设计模式的实现,通过 ApplicationEvent 类和 ApplicationListener 接口,可以实现 ApplicationContext 事件处理。
如果容器中有一个 ApplicationListener Bean ,每当 ApplicationContext 发布 ApplicationEvent 时,ApplicationListener Bean 将自动被触发。这种事件机制都必须需要程序显示的触发。其中spring有一些内置的事件,当完成某种操作时会发出某些事件动作。比如监听 ContextRefreshedEvent 事件,当所有的bean都初始化完成并被成功装载后会触发该事件,实现ApplicationListener<ContextRefreshedEvent> 接口可以收到监听动作,然后可以写自己的逻辑。同样事件可以自定义、监听也可以自定义,完全根据自己的业务逻辑来处理。
对象说明:

1、ApplicationContext容器对象
2、ApplicationEvent事件对象(ContextRefreshedEvent容器刷新事件)
3、ApplicationListener事件监听对象

1.2 ApplicationContext事件监听

当ApplicationContext内的Bean对象初始化完成时,此时可以通过监听ContextRefreshedEvent 得到通知!我们来模拟一次。
创建监听对象: ApplicationContextListener

public class ApplicationContextListener implements
ApplicationListener<ContextRefreshedEvent> {
  @Override
  public void onApplicationEvent(ContextRefreshedEvent event) {
    System.out.println("ContextRefreshedEvent事件,容器内对象发生变更");
 }
}

将对象添加到容器中:

<bean class="com.oldlu.event.ApplicationContextListener" id="appListener"/>

测试:

public static void main(String[] args) throws InterruptedException {
  ApplicationContext act = new ClassPathXmlApplicationContext("spring.xml");
}

此时会打印如下信息:ContextRefreshedEvent事件,容器内对象发生变更

应用场景:程序启动,初始化过程中,需要确保所有对象全部初始化完成,此时在从容器中获取指定对象做相关初始化操作。例如:将省、市、区信息初始化到缓存中。

1.3 自定义监听事件

自定义监听事件可以监听容器变化,同时也能精确定位指定事件对象,我们编写一个案例演示自定义监听事件实现流
程。
定义事件监听对象: MessageNotifier

public class MessageNotifier implements ApplicationListener {
  @Override
  public void onApplicationEvent(ApplicationEvent event) {
    System.out.println("事件对象:"+event.getClass().getName());
 }
}

定义事件对象: MessageEvent

public class MessageEvent extends ApplicationEvent {
  private String phone;
  private String message;
  /**
  * 创建事件
  */
  public MessageEvent(Object source,String phone,String message) {
    super(source);
    this.phone=phone;
    this.message=message;
 }
  /**
  * 创建事件
  */
  public MessageEvent(Object source) {
    super(source);
 }
  //get..set..
}

将对象添加到容器中:

<bean class="com.oldlu.event.MessageNotifier" id="messageNotifier"/>

添加事件测试:

public static void main(String[] args) throws InterruptedException {
  ApplicationContext act = new ClassPathXmlApplicationContext("spring.xml");
  MessageEvent messageEvent = new MessageEvent("beijing","13670000000","hello!");
  act.publishEvent(messageEvent);
}

测试打印结果如下:

事件对象:org.springframework.context.event.ContextRefreshedEvent
事件对象:com.oldlu.event.MessageEvent

2 Spring AOP-动态代理

基于SpringAOP可以实现非常强大的功能,例如声明式事务、基于AOP的日志管理、基于AOP的权限管理等功能,利用AOP可以将重复的代码抽取,重复利用,节省开发时间,提升开发效率。Spring的AOP其实底层就是基于动态代理而来,并且支持JDK动态代理和CGLib动态代理,动态代理的集中体现在 DefaultAopProxyFactory类中,
我们来解析下 DefaultAopProxyFactory 类。
在这里插入图片描述
如果我们在spring的配置文件中不配置 <aop:config proxy-target-class="true"> ,此时默认使用的将是JDK动态代理,如果配置了,则会使用CGLib动态代理。JDK动态代理的创建
这里都会有疑问我查阅资料看到:

Spring 5.x 中 AOP 默认依旧使用 JDK 动态代理。
SpringBoot 2.x开始,为了解决使用 JDK 动态代理可能导致的类型转化异常而默认使用 CGLIB
在 SpringBoot 2.x 中,如果需要默认使用 JDK 动态代理可以通过配置项spring.aop.proxy-target-class=false来进行修改,proxyTargetClass配置已无效。

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 BeanFactory工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。它负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法以被外界直接调用,创建所需的产品对象。

优点:

1、一个调用者想创建一个对象,只要知道其名称就可以了。
2、屏蔽产品的具体实现,调用者只关心产品的接口。
3、降低了耦合度

在这里插入图片描述

Spring内部源码也有工厂模式的实现
Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
BeanFactory源码:

public interface BeanFactory {
//根据参数获取指定对象的实例
Object getBean(String name) throws BeansException;
<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
}

在BeanFactory接口中,有多个getBean方法,该方法其实就是典型的工厂设计模式特征,在接口中定义了创建对象的方法,而对象如何创建其实在接口的实现类中实现 DefaultListableBeanFactory 。我们用Spring的工厂对象BeanFactory来解决上面工厂模式案例所带来的问题。

案例:
xml:

<bean id="car" class="com.oldlu.factory.Car"/>
<bean id="mobile" class="com.oldlu.factory.Mobile"/>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

测试:

    public void testBeanFactory(){
//加载核心配置文件 beas.xml
        Resource resource = new ClassPathResource("beans.xml");
//创建容器对象BeanFactory
        BeanFactory beanFactory = new XmlBeanFactory(resource);
//使用Car对象
        Product car = (Product) beanFactory.getBean("car");
//使用Mobile对象
        Product mobile = (Product) beanFactory.getBean("mobile");
        car.show();
        mobile.show();
    }

结果:

国产车 BYD
HUWEI Mate 30 Pro

使用Spring的BeanFactory,以后要新增一个产品,只需要创建产品对应的xml配置即可,而不需要像ProductFactory 那样硬编码存在,相当于spring帮我们完成以下操作.
在这里插入图片描述

4 适配器模式

定义:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

优点:

1、可以让任何两个没有关联的类一起运行。
2、提高了类的复用。
3、灵活性好。

缺点:过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。

4.1 Spring Aop适配器+代理模式案例

Spring架构中涉及了很多设计模式,本文来介绍下Spring中在AOP实现时Adapter模式的使用。AOP本质上是Java动态代理模式的实现和适配器模式的使用。
我们基于Spring的前置通知来实现一个打卡案例,再基于前置通知讲解前置适配模式。
创建打卡接口: PunchCard 定义打卡方法,代码如下:

public interface PunchCard {
  //打卡记录
  void info(String name);
}

定义打卡实现: PunchCardImpl 实现打卡操作,代码如下:

public class PunchCardImpl implements PunchCard {
  @Override
  public void info(String name) {
    System.out.println(name+" 打卡成功!");
 }
}

前置通知创建: PunchCardBefore 实现在打卡之前识别用户身份,代码如下:

public class PunchCardBefore implements MethodBeforeAdvice {
  @Override
  public void before(Method method, Object[] args, Object target) throws Throwable {
    System.out.println("身份识别通过!");
 }
}

spring.xml配置前置通知:

<!--打卡-->
<bean id="punchCard" class="com.oldlu.adapter.PunchCardImpl"></bean>
<!--前置通知-->
<bean id="punchCardBefore" class="com.oldlu.adapter.PunchCardBefore"></bean>
<!--代理配置-->
<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
  <!-- 指定目标对象 -->
  <property name="target" ref="punchCard"/>
  <!-- 指定目标类实现的所有接口 -->
  <property name="interfaces" value="com.oldlu.adapter.PunchCard"/>
  <!-- 指定切面 -->
  <property name="interceptorNames" >
    <list>
      <value>punchCardBefore</value>
    </list>
  </property>
</bean>

测试效果如下:

身份识别通过!
王五打开成功!

4.2 Spring AOP适配器体系

在这里插入图片描述
前置通知其实就是适配器模式之一,刚才我们编写的前置通知实现了接口MethodBeforeAdvice 。Spring容器将每个具体的advice封装成对应的拦截器,返回给容器,这里对advice转换就需要用到适配器模式。我们来分析下适配器
的实现:

如下代码实现了接口 BeforeAdvice ,而 BeforeAdvice 继承了 Advice 接口,在适配器接口 AdvisorAdapter 里面
定义了方法拦截。

public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
}

AdvisorAdapter :定义了2个方法,分别是判断通知类型是否匹配,如果匹配就会获取对应的方法拦截。

public interface AdvisorAdapter {
    // 判断通知类型是否匹配
    boolean supportsAdvice(Advice advice);
    // 获取对应的拦截器
    MethodInterceptor getInterceptor(Advisor advisor);
}

MethodBeforeAdviceAdapter :实现了 AdvisorAdapter ,代码如下:

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
    @Override
    public boolean supportsAdvice(Advice advice) {
        return (advice instanceof     );
    }
    @Override
    public MethodInterceptor getInterceptor(Advisor advisor) {
        MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
    // 通知类型匹配对应的拦截器
        return new MethodBeforeAdviceInterceptor(advice);
    }
}

刚才我们在spring.xml中配置了代理类,代理类通过DefaultAdvisorAdapterRegistry类来注册相应的适配器,我们可以在
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赵广陆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值