JDK中的观察者和被观察者
JDK提供了两个观察者和被观察者用途的类:
Observer
接口
Observable
类
如果是观察者
, 实现 Observer
接口
如果是被观察者
,继承 Observable
类
以教师,学生为例:教师布置作业,学生观察到老师布置作业。这个模式中,教师是被观察者,学生是观察者。
创建观察者–学生
/**
* 观察者 -- 学生
*
* Observer 接口中只有一个update的定义
*/
public class Student implements java.util.Observer {
private String name;
public Student(String name){
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
Teacher teacher = (Teacher) o;
System.out.printf("学生%s观察到(实际是被通知)%s布置了作业《%s》 \n", this.name, teacher.getName(), arg);
}
}
创建被观察者–教师
/**
* 继承了被观察者类 的教师
*/
public class Teacher extends java.util.Observable {
private String name;
private List<String> homeworks;
public String getName() {
return this.name;
}
public Teacher(String name) {
this.name = name;
homeworks = new ArrayList<String>();
}
public void setHomework(String homework) {
System.out.printf("%s布置了作业%s \n", this.name, homework);
homeworks.add(homework);
//只有设置了changed(表示有通知了,有新的变化了) , 才能够成功通知观察者(或者说被观察)
setChanged();
//通知观察者们
notifyObservers(homework);
}
}
写一个main方法简单测试
public class Client {
public static void main(String[] args) {
Student student1= new Student("学生1");
Student student2 = new Student("学生2");
Teacher teacher1 = new Teacher("张云鹏");
//注册了观察者
teacher1.addObserver(student1);
teacher1.addObserver(student2);
teacher1.setHomework("事件机制第一天作业");
}
}
实现观察靠的就是Observable类。
Observable类中其实很简单:
- 有一个Vector obs,保存对应的所有的观察者。
- 有obs的增删改方法
- 有个boolean changed , 标识当前是否是有新的通知
- 发布新的通知的方法 notifyObservers。这个方法很关键,说到底就是挨个运行obs中的update方法。
EventObject,EventListener
这个概念和前面的观察者非常像,实现思路复杂一点点。
EventObject
类 包含一个Object source,以及它的修改
EventListener
接口 没有任何方法
继续以学生、教师为例
//先创建自己的监听者 接口(模仿Observer)
public interface WorkListener extends EventListener {
void update(WorkEvent e);
}
//创建一个事件,监听者触发的事件
public class WorkEvent extends EventObject {
/**
*
*/
public WorkEvent(Object source) {
super(source);
}
public void print(){
System.out.println("homework is " + source);
}
}
//创建事件源 也就是被观察者 (完全模仿Observable)
/**
* 事件源
*/
public class EventSource {
//注册的监听者集合
private Vector<WorkListener> listeners;
//是否发生了改变
private boolean changed;
public EventSource(){
//创建
listeners = new Vector<>();
}
/**
* 注册监听者
* @param listener
*/
public void addListeners(WorkListener listener){
listeners.add(listener);
}
/**
* 删除一个监听者
* @param listener
*/
public void removeListeners(WorkListener listener){
listeners.remove(listener);
}
/**
* 通知所有的监听者
*/
public void notifyListeners(WorkEvent event){
Object[] objs = null;
//多线程下保证安全
synchronized (this){
if(!changed)
return;
objs = listeners.toArray();
clearChanged();
}
for(Object obj : objs){
((WorkListener) obj).update(event);
}
}
/**
* 标记当前的状态为 已经发生了改变
*/
public void makeChanged() {
this.changed = true;
}
/**
* 标记当前的状态为 未发生改变
*/
public void clearChanged(){
this.changed = false;
}
}
//创建 具体的 观察者(Student) 和被观察者(Teacher)
/**
*
*/
@Data
@AllArgsConstructor
public class Student implements WorkListener {
String name;
@Override
public void update(WorkEvent e) {
System.out.print(name + "=====收到作业-----");
e.print();
}
}
@Data
@AllArgsConstructor
public class Teacher extends EventSource {
String name;
public void setHomework(String homework){
System.out.println("教师--" + name + "发布作业== " + homework);
//标记
makeChanged();
notifyListeners(new WorkEvent(homework));
}
}
最后编写一个测试方法
public class Client {
public static void main(String[] args) {
Student student1= new Student("学生1");
Student student2 = new Student("学生2");
Teacher teacher1 = new Teacher("张云鹏");
//注册了观察者
teacher1.addObserver(student1);
teacher1.addObserver(student2);
teacher1.setHomework("事件机制第一天作业");
}
}
运行结果
这个观察者模式还是挺有意思的,如果实际操作中一个操作下有很多的事需要同时完成,可以使用观察者模式。
观察者模式的确是一个大招,以前做登录功能,登录成功了还得去显示调用一些记录。
现在倒好,只需要弄几个监听配合就行了。只要你登录了,我自己去做记录。好处就是,加功能也方便。大程度上保证了扩展性。
使用Spring中的事件机制
有一说一,事件机制哪家强,还是spring提供的最好用,直接用就行。
两个重点类
1、ApplicationListener
监听者触发的事件
2、ApplicationEvent
推送的事件/被监听的事件
Demo:
创建两个自定义的事件:
/**
* demoevent
*/
public class DemoEvent extends ApplicationEvent {
private String text;
public DemoEvent(Object source, String text) {
super(source);
this.text = text;
}
public void print() {
System.out.println("print event content:" + this.text);
}
}
/**
* 登录事件
*/
public class LoginEvent extends ApplicationEvent {
public LoginEvent(Object source) {
super(source);
}
}
推送事件的方式
第一种:使用ApplicationContext的 publishEvent方法。
@SpringBootApplication
public class BootEventApplication {
public static void main(String[] args) {
ApplicationContext context =
SpringApplication.run(BootEventApplication.class, args);
//发布事件
context.publishEvent(new DemoEvent(new Object(), "通过main方法显示发布的事件."));
}
}
实际使用,实现ApplicationContextAware接口,其实依然是调用了ApplicationContext 的publishEvent方法。
@Service
public class LoginService implements ApplicationContextAware {
private ApplicationContext applicationContext;
public String login(String name) {
if (StringUtils.isEmpty(name)) {
return "什么东西进来了?";
}
//...校验
//发布事件
publish(new LoginEvent(name));
return "欢迎" + name + "登录";
}
public void publish(LoginEvent loginEvent) {
applicationContext.publishEvent(loginEvent);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
添加监听者的方式:
1、最常用最方便的方式:
@Component
public class DemoListenerHandler {
/**
*eventListener 修饰的方法参数必须包含event
* 相应的事件被发布时, 就会触发这个方法。
* 注册监听者的方式 2
*/
@EventListener
public void handlerListener(DemoEvent event){
System.out.println("这个是通过 @EventListener 注册的监听者");
event.print();
}
@EventListener
public void loginListener1(LoginEvent event){
System.out.println(event.getSource() + "的登录时间记录监听");
}
@EventListener
public void loginListener2(LoginEvent event){
System.out.println(event.getSource() + "的登录地点记录监听");
}
}
这几个@EventListener注解标识的方法,参数中的Event被推送的时候,这个@EventListener
修饰的方法就会被触发。原理是项目启动的时候所有被@EventListener修饰的方法都会生成
相应的Listener,并且这种方法参数必须是一个event,不然启动会报错。
2、第二种方式,自己实现ApplicationListener接口。
@Component
public class PrintListener implements ApplicationListener<DemoEvent> {
@Override
public void onApplicationEvent(DemoEvent event) {
System.out.println("调用DemoEvent的print方法输出其内容:");
event.print();
}
}
或者----
@Bean
public PrintListener printListener() {
return new PrintListener();
}
注册一个ApplicationListener类型的bean就行。逻辑根据实际实现,简单粗暴。
4、第四种,通过applicationContext的addApplicationListener方法显示添加
context.addApplicationListener(new PrintListener());