观察者设计模式
主题/观察者模式,简称为观察者模式(Observer Pattern)是一种行为设计模式,也被称为发布/订阅(Publisher/Subscriber)模式。在该模式中,对象之间存在依赖关系,其中"被观察者"对象(Subject)维护一个观察者对象的列表,并在自身状态发生改变时通知所有观察者对象。观察者对象在接收到通知后会执行相应的操作。这种模式有助于实现松耦合的代码结构,特别是在需要处理对象状态改变并通知相关对象的场景中。
原理
-
被观察者(Subject):这是目标对象,可以有多个观察者订阅它的状态改变。
-
观察者(Observer):这是订阅被观察者状态的对象,当被观察者状态改变时,观察者会收到通知并执行相应的操作。
实例代码
下面是一个简单的Java实现,使用了接口和抽象类来简化观察者模式的实现。
java
import java.util.ArrayList;
import java.util.List;
// 被观察者(Subject)抽象类
abstract class Subject {
protected List<Observer> observers = new ArrayList<>();
// 注册观察者
public void registerObserver(Observer observer) {
observers.add(observer);
}
// 删除观察者
public void removeObserver(Observer observer) {
observers.remove(observer);
}
// 通知所有观察者
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
// 实际状态改变的方法
protected void changeState() {
System.out.println("状态改变");
notifyObservers(); // 告知所有观察者状态已改变
}
}
// 具体被观察者实现类
class ConcreteSubject extends Subject {
// 状态
private String state;
// 其他状态管理方法...
}
// 观察者(Observer)抽象类
abstract class Observer {
protected Subject subject;
// 观察者初始化方法
public Observer(Subject subject) {
this.subject = subject;
this.subject.registerObserver(this); // 注册观察者
}
// 被观察者状态改变时将调用此方法
protected abstract void update();
}
// 具体观察者实现类
class ConcreteObserver extends Observer {
// 具体的更新方法实现...
}
public class ObserverPatternDemo {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
ConcreteObserver observer1 = new ConcreteObserver(subject);
ConcreteObserver observer2 = new ConcreteObserver(subject);
// 状态改变后,所有观察者都会收到通知并执行 update 方法
// 状态改变模拟,实际应用中可以是业务逻辑导致的状态改变
subject.changeState();
}
}
在这个例子中,ConcreteSubject
类是被观察者,它维护一个观察者列表,并在状态改变时通过调用 notifyObservers()
方法通知所有观察者。ConcreteObserver
类是观察者,它实现了 update
方法来处理状态改变。ObserverPatternDemo
类展示了如何使用这个模式。当 ConcreteSubject
的状态改变时,所有注册的观察者都会收到通知并执行相应的更新逻辑。
java
package com.example.constraint.util.study;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
/**
* 观察者模式测试类
*/
public class ObserverMode {
public static void main(String[] args) {
Event event = new Event();
EventListener eventListener1 = new EventListener(event);
EventListener eventListener2 = new EventListener(event);
event.changeStatus(1);
event.changeStatus(100);
}
}
interface Subject {
int getStatus();
default void changeStatus(int status) {
notifyObservers();
}
void addObserver(Observer observer);
void notifyObservers();
}
interface Observer {
void publish();
}
class Event implements Subject {
private int status;
private List<Observer> observers = new ArrayList<>();
@Override
public int getStatus() {
return status;
}
@Override
public void addObserver(Observer observer) {
observers.add(observer);
}
@Override
public void changeStatus(int status) {
this.status = status;
// 主题状态发生改变,通知观察者
notifyObservers();
}
@Override
public void notifyObservers() {
observers.forEach(Observer::publish);
}
}
@Slf4j
class EventListener implements Observer {
private Subject subject;
public EventListener(Subject subject) {
this.subject = subject;
this.subject.addObserver(this);
}
@Override
public void publish() {
log.info("event status changed -> {}", this.subject.getStatus());
}
}
动态代理
JDK动态代理
java
package com.example.constraint.util.study;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* jdk动态代理
*/
public class DynamicProxy {
public static void main(String[] args) {
LogManager logManager = new LogManager();
LogUtil proxyInstance = (LogUtil) Proxy.newProxyInstance(
LogManager.class.getClassLoader(),
LogManager.class.getInterfaces(),
new LogProxy(logManager));
proxyInstance.printInfo();
System.out.println(StringUtils.repeat("*", 12));
proxyInstance.getThread();
}
}
interface LogUtil {
void printInfo();
void getThread();
}
@Slf4j
class LogManager implements LogUtil {
@Override
public void printInfo() {
log.info("log info");
}
@Override
public void getThread() {
log.info("thread - {}", Thread.currentThread().getId());
}
}
@Slf4j
class LogProxy implements InvocationHandler {
private Object target; // 被代理对象
public LogProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("before log...");
Object result = method.invoke(this.target);
log.info("after log...");
return result;
}
}
text
21:00:18.797 [main] INFO com.example.constraint.util.study.LogProxy - before log...
21:00:18.800 [main] INFO com.example.constraint.util.study.LogManager - log info
21:00:18.800 [main] INFO com.example.constraint.util.study.LogProxy - after log...
************
21:00:18.802 [main] INFO com.example.constraint.util.study.LogProxy - before log...
21:00:18.803 [main] INFO com.example.constraint.util.study.LogManager - thread - 1
21:00:18.803 [main] INFO com.example.constraint.util.study.LogProxy - after log...
CGLib动态代理
在Java中,CGlib是一个常用的类库,用于实现动态代理,尤其对于那些声明了接口(继承了某个接口或实现了某个接口)的类,因为Java语言不支持在运行时动态创建接口的实现类。下面是一个使用CGlib实现动态代理的简单示例:
引入依赖
xml
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.9</version> <!-- 或者你使用的版本 -->
</dependency>
</dependencies>
java
package com.example.constraint.util.study;
import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.apache.commons.lang.StringUtils;
import java.lang.reflect.Method;
/**
* CGLib动态代理实现
*/
public class DynamicProxyCGLib {
public static void main(String[] args) {
InfoUtil infoUtil = new InfoUtil();
InfoUtilProxy infoUtilProxy = new InfoUtilProxy(infoUtil);
InfoUtil instance = (InfoUtil) infoUtilProxy.getInstance();
instance.logInfo();
System.out.println(StringUtils.repeat("*", 12));
instance.getTime();
}
}
@Slf4j
class InfoUtil {
void logInfo() {
log.info("info logging...");
}
void getTime() {
log.info("currentTimeMillis {}", System.currentTimeMillis());
}
}
@Slf4j
class InfoUtilProxy implements MethodInterceptor {
private Object target;
public InfoUtilProxy(Object target) {
this.target = target;
}
public Object getInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
log.info("before log...");
Object result = method.invoke(this.target);
log.info("after log...");
return result;
}
}
text
21:25:15.722 [main] INFO com.example.constraint.util.study.InfoUtilProxy - before log...
21:25:15.724 [main] INFO com.example.constraint.util.study.InfoUtil - info logging...
21:25:15.724 [main] INFO com.example.constraint.util.study.InfoUtilProxy - after log...
************
21:25:15.725 [main] INFO com.example.constraint.util.study.InfoUtilProxy - before log...
21:25:15.726 [main] INFO com.example.constraint.util.study.InfoUtil - currentTimeMillis 1723987515726
21:25:15.726 [main] INFO com.example.constraint.util.study.InfoUtilProxy - after log...
Spring事件监听
Spring Boot的事件监听机制主要利用了Spring的ApplicationEvent和ApplicationListener接口。以下是使用代码说明:
创建事件类:
首先,我们需要创建一个事件类,该类需要继承自ApplicationEvent。
java
import org.springframework.context.ApplicationEvent;
public class GreetingCreatedEvent extends ApplicationEvent {
private String message;
public GreetingCreatedEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
注册监听器:
在Spring Boot的配置类中,我们可以使用@Component和@EventListener注解来注册监听器。
java
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
@Configuration
public class GreetingEventListenerConfig {
@EventListener
public void handleGreetingCreatedEvent(GreetingCreatedEvent event) {
String message = event.getMessage();
System.out.println("Event handler called: " + message);
}
}
触发事件:
在你的业务逻辑中,你可以创建一个事件并调用publish方法来触发事件。
java
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@Service
public class GreetingService {
private final ApplicationEventPublisher publisher;
public GreetingService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void createGreeting(String message) {
GreetingCreatedEvent event = new GreetingCreatedEvent(this, message);
publisher.publishEvent(event);
}
}
以上就是使用Spring Boot事件监听机制的完整代码说明,通过这种方式,可以创建和处理各种事件,实现系统之间的异步通信。
SpringBoot事件监听机制使用了什么设计模式?
Spring Boot事件监听机制主要使用了以下几种设计模式:
-
发布/订阅(Publisher/Subscriber)模式 :也称为主题/观察者模式。在这个模式中,事件(主题)的发布者(Publisher)不直接引用事件的订阅者(Subscriber)。一旦有事件发生,发布者会将事件广播给所有的订阅者。在Spring Boot中,
ApplicationEventPublisher
作为发布者,事件监听器作为订阅者,当事件被触发时,事件监听器接收到通知并进行相应的处理。 -
代理(Proxy)模式 : Spring Boot中的
SimpleApplicationEventMulticaster
作为代理类,实现了ApplicationEventMulticaster
接口,负责将事件发送给相应的事件监听器。这种设计模式有助于实现事件的多播和实现类的封装。 -
接口隔离原则(ISP) :通过定义
ApplicationEvent
接口以及更多的专门的事件接口(如GreetingCreatedEvent
),遵循了接口隔离原则。这样可以使得监听器只处理它感兴趣的具体事件,而不是被所有的事件类型所绑定,从而提高了系统的灵活性和扩展性。 -
依赖注入(Dependency Injection,DI) :在Spring Boot中,监听器通过在配置类中使用
@Component
和@EventListener
注解进行依赖注入,使得事件处理逻辑的注入和管理变得更加简单和灵活。这符合依赖注入的设计模式,有助于实现松耦合和易于维护的代码结构。 -
单例(Singleton)模式:Spring Boot中的事件监听器通常是单例的,这样可以确保在整个应用程序生命周期中只有一份实例,且所有需要处理事件的组件都可以通过依赖注入获取到这个实例。这有助于减少资源消耗和简化事件处理逻辑的管理。
通过这些设计模式的综合使用,Spring Boot事件监听机制实现了高效、灵活、可扩展的事件处理机制。
SpringBoot事件监听机制原理
Spring Boot事件监听机制是基于Spring的事件驱动架构和消息传递模型实现的。具体来说,它基于以下原理:
-
事件(ApplicationEvent) :Spring框架提供了一个基础的事件类
ApplicationEvent
,它是一个抽象类,所有事件都必须继承它。事件类包含一个对象参数,通常是触发事件的对象的引用。 -
监听器(ApplicationListener) :这是一个接口,所有处理事件的类都需要实现该接口。监听器接口中的一个方法
onApplicationEvent
会被调用,当有事件发生时,任何实现了这个接口的对象的这个方法都会被触发。 -
事件广播器(ApplicationEventPublisher) :这个接口允许你发出事件,然后所有注册的监听器都会被触发。Spring Boot应用程序上下文中缺省提供了一个实现这个接口的类
ApplicationEventMulticaster
。 -
事件多播器(ApplicationEventMulticaster) :它负责将事件传递给所有注册的监听器。在Spring Boot中,这个多播器通常会使用
SimpleApplicationEventMulticaster
,它是一个实现简单,线程安全的事件多播器。 -
事件注册 :监听器注册通常在配置类中使用@Component和@EventListener注解实现。当一个类被标注为
@Component
,且包含一个实现ApplicationListener
接口的方法,并且方法被@EventListener注解标注,那么这个类和方法就会被注册为事件监听器。 -
事件触发 :事件可以通过
ApplicationEventPublisher
的publishEvent
方法触发。你可以使用publishEvent
来触发由特定对象生成的特定事件。
通过这样的架构,可以实现事件驱动的设计,使得系统中的各个部分可以根据事件进行异步通信和协调。Spring Boot通过其内部实现方便地支持了这一模式,使得开发者可以很容易地在应用中引入事件处理机制。
@EventListener原理是什么
@EventListener
是Spring框架中的一个注解,用于将类实例或方法标记为事件监听器。当事件(如ApplicationEvent
的子类)被发布时,如果该事件类型与标注了@EventListener
的方法的类型相匹配,那么Spring会调用该方法来处理事件。这种机制允许应用程序在事件发生时自动执行某些操作,而无需显式调用特定的方法。
@EventListener
原理的简要说明:
-
事件发布 :事件通常是由Spring的
ApplicationEventPublisher
类或其他实现了ApplicationEventPublisher
接口的类发布的。当需要触发事件时,这些发布者会创建一个事件对象,并使用publishEvent
方法发布事件。 -
事件监听 :当事件被发布时,Spring会查找所有被
@EventListener
注解标记的方法。这些方法可以是类方法,也可以是静态方法。如果这些方法的类型与发布的事件类型匹配,那么它们就会被调用。 -
方法签名匹配 :Spring用来匹配事件和方法的规则是"最小匹配原则"。这意味着方法的参数列表(除了返回类型)必须与事件的类型完全匹配。例如,如果事件是
MyEvent
,那么被监听的方法的参数必须是MyEvent
类型,或者可以是ApplicationEvent
类型,因为MyEvent
是ApplicationEvent
的子类。 -
事件处理:一旦找到匹配的方法,Spring会调用该方法,并传入事件对象作为参数。方法执行完毕后,事件处理过程结束。
示例代码:
java
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
public class MyEventListener implements ApplicationListener<ContextRefreshedEvent> {
private final ApplicationEventPublisher publisher;
public MyEventListener(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 在这里处理事件,例如:
System.out.println("Context has been refreshed.");
}
}
在这个例子中,MyEventListener
类中的onApplicationEvent
方法被@EventListener
注解标注,当应用上下文刷新时,这个方法会被调用。
通过使用@EventListener
,开发者可以方便地实现事件驱动的编程模式,使得应用程序更加灵活和模块化。
@EventListener注解本身并未直接使用AOP(面向切面编程)。@EventListener是一个用于监听特定事件的注解,主要与Spring的事件发布和处理机制相结合。它允许开发者在特定事件发生时执行特定的代码逻辑,通常在事件处理逻辑与业务逻辑分离的场景下使用。
然而,@EventListener
注解的实现和使用,确实可以间接依赖于AOP机制。这是因为Spring框架内部在处理事件时,可能使用了AOP技术来增强事件监听方法的行为。
在Spring中,事件处理通常涉及到以下几个环节:
-
事件发布 :当需要触发一个事件时,Spring会使用
ApplicationEventPublisher
或其子类来发布事件。 -
事件监听 :当事件被发布后,Spring会查找所有被
@EventListener
注解标记的方法以匹配发布的事件。这里的查找和匹配过程,从宏观上看,是Spring框架在进行的一种"增强"操作,实际上是在执行AOP的逻辑。 -
方法调用:一旦找到匹配的方法,Spring会调用这些方法来处理事件。这实际上是由Spring的容器在事件处理链中进行方法调用,这可以被视为AOP机制的一个应用实例,因为它涉及到方法调用的控制和增强。
然而,需要明确的是,@EventListener本身并不是实现AOP的API。而是Spring框架利用其内部的AOP机制来实现事件处理逻辑的透明性和耦合性降低。在Spring的事件处理机制中,AOP的主要作用在于动态地为事件监听方法添加处理逻辑,而不仅仅是作为事件监听的注解本身。
@EventListener
的实现通常涉及到反射(Reflection)。在Spring框架中,当使用@EventListener
注解时,Spring会通过反射来动态查找和匹配事件监听方法,并在特定的事件触发时调用这些方法。反射是Java语言的一个核心特性,允许程序在运行时通过其类的元数据信息(如类名、方法名、属性等)来动态地操作类和对象。
具体到@EventListener
的使用情况,以下是对反射原理的简要说明:
-
事件监听类的扫描 :Spring在启动时会扫描所有被
@EventListener
注解的类,查找这些类中被注解的方法。 -
方法的类型匹配:对于每个被注解的方法,Spring会检查其参数类型以及方法签名,以确定该方法是否可以处理特定的事件类型。这里涉及到对元数据的直接操作,即通过反射来获取方法签名,包括参数类型和返回类型。
-
方法调用:一旦确定了方法可以处理某个事件,Spring会通过反射调用这个方法。反射在此处的应用允许在运行时动态地获取方法的参数,并将实际事件对象作为参数传递给方法。
这种使用反射的方式来处理事件监听,使得Spring能够实现动态的依赖注入、事件匹配和方法调用,而这些操作在运行时进行,增强了框架的灵活性和扩展性。通过反射,Spring能够在事件处理过程中实现高度的代码分离和模块化,使得开发者能够更加专注于业务逻辑,而无需担心事件处理细节。
Spring在查找所有被@EventListener
注解的方法时,主要通过以下步骤和机制来实现:
-
扫描和加载:Spring框架在启动时,会进行组件扫描(Component Scan)以查找所有符合配置的bean定义。这个扫描过程是基于Java的反射机制进行的。
-
切片扫描 :在扫描范围内的类会被检查,Spring使用
@Component
、@Service
、@Repository
等注解标记的类作为组件候选。对于@EventListener
注解,Spring同样会检查是否有这个注解被使用。 -
注解处理 :一旦发现带有
@EventListener
的类,Spring框架会读取这个注解,并根据注解的值(如果有的话)来查找特定的事件类型。例如,如果注解中指定了eventTypes
的值,Spring会查找与这些事件类型匹配的方法。 -
方法匹配 :对于每个带有
@EventListener
注解的方法,Spring会检查方法签名和参数类型,以确定它是否能够处理特定的事件类型。如果有多个事件类型,Spring会尝试找到与所有事件类型相匹配的方法。 -
事件处理:当特定事件发生时,Spring会调用匹配到的方法。这里可能涉及到事件的序列化、方法的执行以及异常的处理。
-
依赖注入 :在执行方法之前,Spring会使用依赖注入机制来注入方法所需的任何依赖。如果方法中使用了
@Autowired
或其他依赖注入注解,Spring会负责为这些变量提供正确的值。
通过上述步骤,Spring能够高效地查找并执行所有被@EventListener
注解的方法,从而实现事件驱动的设计模式。这种方式不仅使得代码更加模块化和易于维护,也使得事件处理逻辑更加灵活。