写了五年web还不懂事件机制?

java中的事件机制

发布订阅模式
public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;

    /** Construct an Observable with zero Observers. */

    public Observable() {
        obs = new Vector<>();
    }

java.util.Observable 是一个a发布者
java.util.Observable 是一个订阅者

发布者和订阅者的关系可以是一对多,也可以是多对多

我们先来初始化一个类

/**
 *@Description TODO
 *@author apdoer
 *@CreateDate 2019/6/15-20:20
 *@Version 1.0
 *===============================
**/
public class Demo {
    public static void main(String[] args) {
        //创建发布者
        Observable observable = new Observable();

        //添加订阅者
        observable.addObserver(new Observer() {
            /**
             * @param o     发布者对象
             * @param value     订阅的内容
             */
            @Override
            public void update(Observable o, Object value) {
                System.out.println(value);
            }
        });
        //发布消息
        observable.notifyObservers("hello,apdoer");
    }
}

运行结果发现什么都没有发生 , 为什么呢 ? 明明创建了发布者也添加了订阅者啊,而且也发布了消息啊

因为我们没有触发机制

怎么触发?

  • 要么发布者去推送 (推的模式)
  • 要么订阅者主动去拉取 (拉的模式)
推送

回过头去看, Observable发布者有个成员变量 changed,这个变量控制着发布者是否发生了改变,这也是正常的思维,改变了就去推送,默认是没有改变

    /**
     * Marks this <tt>Observable</tt> object as having been changed; the
     * <tt>hasChanged</tt> method will now return <tt>true</tt>.
     */
    protected synchronized void setChanged() {
        changed = true;
    }

但是通过源码我们可以看到这个方法的级别我们在自己的测试demo中应该是调用不到的,那么怎么办呢,我们提升一下这个方法的访问权限

/**
 * @author apdoer
 * @Description TODO
 * @CreateDate 2019/6/15-20:20
 * @Version 1.0
 * ===============================
 **/
public class Demo {
    public static void main(String[] args) {
        //创建发布者
        MyObservable observable = new MyObservable();

        //添加订阅者
        observable.addObserver(new Observer() {
            /**
             *
             * @param o     发布者对象
             * @param value     订阅的内容
             */
            @Override
            public void update(Observable o, Object value) {
                System.out.println(value);
            }
        });
        
        observable.setChanged();
        //发布消息
        observable.notifyObservers("hello,apdoer");
    }

    static class MyObservable extends Observable {

        @Override
        public void setChanged() {
            super.setChanged();
        }
    }
}

这样一来,就可以提升了setChanged方法的访问权限,这是一种思想,大家在别的地方可以举一反三
重要的事情只说一遍

我们再次启动程序,发现订阅者是拿到了发布者发布的消息的

这种模式对订阅者来说是被动感知的

拉取
 List<Integer> values = Arrays.asList(1, 2, 3, 4, 5);
        Iterator<Integer> iterator = values.iterator();
        while (iterator.hasNext()){//通过循环,去主动获取
            System.out.println(iterator.next());
        }

这里只是举一个简单的类似,主动获取,也叫迭代器模式,

事件监听模式

public class EventObject implements java.io.Serializable {

    private static final long serialVersionUID = 5516075349620653480L;

    /**
     * The object on which the Event initially occurred.
     */
    protected transient Object  source;

java.util.EventObject : 事件对象

  • 事件对象始终关联着事件源source
    java.util.EventListener : 事件监听接口(标记接口)

spring 事件/监听

public abstract class ApplicationEvent extends EventObject {
    private static final long serialVersionUID = 7099057708183571937L;
    private final long timestamp = System.currentTimeMillis();

    public ApplicationEvent(Object source) {
        super(source);
    }

    public final long getTimestamp() {
        return this.timestamp;
    }
}

ApplicationEvent : 应用事件,继承自 EventObject

通过下面一段代码,测试spring事件监听

/**
 * @Description Spring 自定义 事件/监听器
 * @Author apdoer
 * @Date 2019/5/4 23:42
 * @Version 1.0
 */
public class SpringListenerDemo {
    public static void main(String[] args) {
        // Annotation驱动的spring上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //注册监听器,只关注MyApplicationEvent事件
        context.addApplicationListener(new ApplicationListener<MyApplicationEvent>() {
            /**
             * 监听器得到事件
             * @param applicationEvent
             */
            @Override
            public void onApplicationEvent(MyApplicationEvent applicationEvent) {
                System.out.println("【接收到事件】"+applicationEvent.getSource()+"@"+applicationEvent.getApplicationContext());
            }
        });

        //监听器得到事件
        context.refresh();
        //发布事件
        context.publishEvent(new MyApplicationEvent(context,"hello world"));
        context.publishEvent(new MyApplicationEvent(context,1));
        context.publishEvent(new MyApplicationEvent(context,new Integer(100)));

    }

    //自定义事件
    private static class MyApplicationEvent extends ApplicationEvent{

        private final ApplicationContext applicationContext;//这样就可以通过事件获取上下文
        public MyApplicationEvent(ApplicationContext applicationContext, Object source) {
            super(source);
            this.applicationContext = applicationContext;
        }

        public ApplicationContext getApplicationContext() {
            return applicationContext;
        }
    }
}

接下来 重点来了

springboot 核心事件

  • ApplicationEnvironmentPreparedEvent
  • ApplicationPreparedEvent
  • ApplicationStartingEvent
  • ApplicationReadyEvent
  • ApplicationFailedEvent

Springboot 事件/监听器

  • ConfigFileApplicationListener : 管理配置文件,比如application.properties,以及application.yml springboot默认对propertiesyml也是在这个类里面有对应的加载器
    • application-{profile}.yml中的 profile 存在时,会被先加载
    • 顺序从前往后application-{profile}.yml ,application.yml

Springboot 在对于ClassPath: /META-INF/spring.factories
- 这里是Spring的SPI
- 不是java的SPI : java.util.ServiceLoader

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor

# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer

# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter

这里就好比是前面的demo 中,把所有的事件一个个的塞进去,

如何控制顺序的
  • 实现Ordered以及@Order注解,可以看到,Spring中的很多事件监听都实现了这个接口
    • 在Spring 中 数值越小,优先级越高

同样的,我们看一看 springcloud 的spring.factories,可以发现BootstrapApplicationListener

/**
* A listener that prepares a SpringApplication (e.g. populating its Environment) by
* delegating to {@link ApplicationContextInitializer} beans in a separate bootstrap
* context. The bootstrap context is a SpringApplication created from sources defined in
* spring.factories as {@link BootstrapConfiguration}, and initialized with external
* config taken from "bootstrap.properties" (or yml), instead of the normal
* "application.properties".
*
* @author Dave Syer
*
*/
public class BootstrapApplicationListener
   	implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {

   /**
    * Property source name for bootstrap.
    */
   public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = "bootstrap";

   /**
    * The default order for this listener.
    */
   public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 5;

   /**
    * The name of the default properties.
    */
   public static final String DEFAULT_PROPERTIES = "defaultProperties";

   private int order = DEFAULT_ORDER;

在springcloud 中的事件/监听器

  • BootstrapApplicationListener : 根事件,负责加载bootstrap.yml 或者bootstrap.properties
  • BoostrapApplicationListener加载的优先级高于ConfigFileApplicationListener,所以application.properties即使定义也配置不到,原因在于:前者始终比后者优先级高5级
    • 负责加载boostrap.yml或者boostrap.properties
    • 负责初始化boostrap ApplicationContext id=“boostrap”
    ConfigurableApplicationContext context = builder.run();
    
    • boostrap是一个根spring上下文,parent=null
    • 联想到classloader

加入actuator依赖,启动项目,访问localhost:8080/beans,可以看到项目启动加载的所有bean

springcloud上下文

springcloud不同于springboot,有两个上下文

  • application
  • bootstrap

  • configurableApplicationcontext标准实现类AnnotationConfigApplicationContext
  • Env端点: EnvironmentEndPoint
    • environment关联多个带名称的propertySource
    • 源码如下:
    protected void initPropertySource(){
        ConfigurableEnvironment env = getEnvironment();
        if(env.instanceof ConfigurableWebEnvironment){
            (ConfigurableWebEnvironment)env.initPropertySource(this.servletContext,this.servletConfig);
        }
    }
    
  • Environment有两种实现方式
    • 普通类型 :StandardEnvironment
    • web类型 : StandardServletEnvironment
  • environment关联着一个propertySources实例
  • propertySources关联着多个propertySource,并且有优先级
  • 实现自定义配置
    • 实现PropertySourceLocator
    • 暴露该实现作为一个springbean
    • 实现PropertySource
    public static class MyPropertySourceLocator implements PropertySourceLocator{
        public PropertySource<?>locate(Environment environment){
            Map<String,Object>source = new HashMap<>();
            source.put("server.port","9090");
            MapPropertySource PropertySource = new MapPropertySource("my-property-source",source);
            return propertySource;
        }
    }
    
    • 定义且配置/META-INF/spring.factories:
org.springframework.cloud.boostrap.BoostrapConfiguration=\org.apdoer.springcloudconfigClient.SpringCloudConfigClienApplication.MyPropertySourceLocator
  • environment允许同名的,有先后
  • 问题
    • yml和yaml的区别
      • 没有区别,扩展名不同而已
    • 自定义配置在哪里用到
      • 用的不多,一般用于中间件开发
    • spring有个@EventListerApplicationListener区别
      • 没有区别,前者是annotation编程模式,后者是接口
    • /env端点的使用场景是什么
      • 用于排查问题,比如要分析@value里面占位符的具体的值
    • 怎么样防止order一样
      • springbootspringcloud中没办法,SpringSecurity中通过异常实现
    • boostrapApplicationListener是引入cloud组件来使用的吗
      是的
    • pom.xml引入哪个cloud组件了
      • spring-cloud-starter-config
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值