Java for Web学习笔记(八七):消息和集群(2)应用内的publish和subscribe

学习内容

Spring框架很强大,已经自带app内部的消息发布订阅了。我们甚至不需要在配置文件(代码)中加入任何内容。 我们将学习如何在一个spring应用内进行消息的发布和订阅

定义事件

事件需继承Spring的ApplicationEvent。在发布事件后,broker(spring framework)将发送给订阅的用户。如果订阅了这类事件,相关的子类事件也会监听到。下面是一个例子:TopEvent <- CenterEvent;TopEvent <- MiddleEvent <- BottomEvent。

public class TopEvent extends ApplicationEvent{
    private static final long serialVersionUID = 1L;
    public TopEvent(String event) {
        super(event);
    }
}

public class CenterEvent extends TopEvent{
    private static final long serialVersionUID = 1L;
    public CenterEvent(String event) {
        super(event);
    }
}

public class MiddleEvent extends TopEvent {
    ... ...
}

public class BottomEvent extends MiddleEvent{
    ... ...
}

如果我们订阅了一个TopEvent,将会监听到TopEvent,CenterEvent,MiddleEvent和BottomEvent;如果订阅了MiddleEvent,将会监听到MiddleEvent和BottomEvent,如果订阅了CenterEvent,则监听CenterEvent。可以说Spring是根据类型匹配来确定订阅者的,而发送给订阅者的属性是从最匹配的发送开始,不过根据发布/订阅模式松耦合的开发特性,这个顺序没有意义。

我们再看一个小例子,监听用户login事件和logout事件,而这两个时间都属于认证事件。

//在这个小例子中,我们重点看看一个小技巧,AuthenticationEvent是LoginEvent和LogoutEvent的基类,但是我们并不希望作为一个事件发布,采用了abstract,也就是发布者必须要具体给出是login还是logout事件。
public abstract class AuthenticationEvent extends ApplicationEvent{
    private static final long serialVersionUID = 1L;
    public AuthenticationEvent(Object source) {
        super(source);
    }    
}

public class LoginEvent extends AuthenticationEvent{
    private static final long serialVersionUID = 1L;
    public LoginEvent(String username) {
        super(username);
    }
}

public class LogoutEvent extends AuthenticationEvent{
    ... ...
}

发布事件

@Controller
public class HomeController {
    private static final Logger log = LogManager.getLogger();
    //1】inject spring的publisher
    @Inject ApplicationEventPublisher publisher;

    @RequestMapping("")
    public String login(HttpServletRequest request){
        log.info("Publish LOGIN event");
        //2】发布事件
        this.publisher.publishEvent(new LoginEvent(request.getRemoteAddr()));
        return "login";
    }

    @RequestMapping("/logout")
    public String logout(HttpServletRequest request){
        log.info("Publish LOGOUT event");
        //2】发布事件
        this.publisher.publishEvent(new LogoutEvent(request.getRemoteAddr()));
        return "logout";
    } 
}

订阅事件

订阅某个事件

//订阅某个事件很简单,只要实现ApplicationListener<xxxEvent>即可,由于我们使用的是Spring的发布/订阅,因此必须要在spring框架内,类需要加上@Component,此处,使用了@Service
@Service
public class AuthenticationInterestedParty implements ApplicationListener<AuthenticationEvent>{
    private static final Logger log = LogManager.getLogger();

    @Override
    public void onApplicationEvent(AuthenticationEvent event) {
        log.info("Authentication event for IP address {}.", event.getSource());        
    }
}

同样的,我们可以通过public class LoginInterestedParty implements ApplicationListener<LoginEvent>{...}监听LoginEvent。我们看看log输出:

16:18:21.479 [http-nio-8080-exec-3] [DEBUG] (Spring) DispatcherServlet - DispatcherServlet with name 'springWebDispatcher' processing GET request for [/chapter18/]
16:18:21.479 [http-nio-8080-exec-3] [DEBUG] (Spring) RequestMappingHandlerMapping - Looking up handler method for path /
16:18:21.479 [http-nio-8080-exec-3] [DEBUG] (Spring) RequestMappingHandlerMapping - Returning handler method [public java.lang.String cn.wei.chapter18.site.publish_subscribe.HomeController.login(javax.servlet.http.HttpServletRequest)]
16:18:21.479 [http-nio-8080-exec-3] [DEBUG] (Spring) DefaultListableBeanFactory - Returning cached instance of singleton bean 'homeController'
16:18:21.479 [http-nio-8080-exec-3] [DEBUG] (Spring) DispatcherServlet - Last-Modified value for [/chapter18/] is: -1
16:18:21.512 [http-nio-8080-exec-3] [INFO ] HomeController:24 login() - Publish LOGIN event
16:18:21.512 [http-nio-8080-exec-3] [DEBUG] (Spring) DefaultListableBeanFactory - Returning cached instance of singleton bean 'authenticationInterestedParty'
16:18:21.512 [http-nio-8080-exec-3] [DEBUG] (Spring) DefaultListableBeanFactory - Returning cached instance of singleton bean 'loginInterestedParty'
16:18:21.512 [http-nio-8080-exec-3] [INFO ] AuthenticationInterestedParty:14 onApplicationEvent() - Authentication event for IP address 0:0:0:0:0:0:0:1.
16:18:21.512 [http-nio-8080-exec-3] [INFO ] LoginInterestedParty:16 onApplicationEvent() - Login event for IP address 0:0:0:0:0:0:0:1.

可以看到发布和订阅者的处理以及servlet的处理是在同一个线程的。在发布/订阅模式中,发布者并不理会订阅者会如何处理,如果订阅者的处理时间很长,就必定会影响发布者正常servlet的处理。因此,订阅者更适合采用异步处理方式。

@Service
public class LogoutInterestedParty implements ApplicationListener<LogoutEvent>{
    private static final Logger log = LogManager.getLogger();

    @Override
    @Async
    public void onApplicationEvent(LogoutEvent event) {
        log.info("Logout event for IP address {}.", event.getSource());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        log.info("Logout done");
    }
}

输出为(将spring的输出级别上调为INFO):

16:25:17.440 [http-nio-8080-exec-6] [INFO ] AuthenticationInterestedParty:14 onApplicationEvent() - Authentication event for IP address 0:0:0:0:0:0:0:1.
16:25:17.519 [task-1] [INFO ] LogoutInterestedParty:16 onApplicationEvent() - Logout event for IP address 0:0:0:0:0:0:0:1.
16:25:20.521 [task-1] [INFO ] LogoutInterestedParty:21 onApplicationEvent() - Logout done

订阅多个事件

在实际应用中,很可能一个Service订购了好几个事件,但是Java是不能多次实现某个接口的:

public class MyService implements ApplicationListener<OneEvent>,ApplicationListener<TwoEvent>{} //这是不允许的

我们可以采用下面的方式:

/**
 * 这里演示如何在同一个类中(某个服务中),采用内部类的方式,订阅多个事件。   
 * 在Spring框架中,要求某个实例作为Spring的框架管理,需要添加@Bean的标识,如同我们在配置文件的做法。
 * 我们将Spring ApplicationListener的具体实现(采用内部类)作为@Bean在此标记,纳入Spring框架中,正如作为的类时采用的@Component
 */
@Service
public class EventService {
    private static final Logger logger = LogManager.getLogger();

    @Bean
    public ApplicationListener<BottomEvent> bottomEventListener(){
        return new ApplicationListener<BottomEvent>(){
            @Async
            public void onApplicationEvent(BottomEvent event) {
                logger.info("Bottom event for {}.", event.getSource());
            };
        };        
    }

    @Bean
    public ApplicationListener<CenterEvent> centerEventListener(){
        return new ApplicationListener<CenterEvent>(){
            @Async
            public void onApplicationEvent(CenterEvent event) {
                logger.info("Center event for {}.", event.getSource());
            };
        };        
    }

    @Bean
    public ApplicationListener<MiddleEvent> middleEventListener(){
        return new ApplicationListener<MiddleEvent>(){
            @Async
            public void onApplicationEvent(MiddleEvent event) {
                logger.info("Middle event for {}.", event.getSource());
            };
        };        
    }

    @Bean
    public ApplicationListener<TopEvent> topEventListener(){
        return new ApplicationListener<TopEvent>(){
            @Async
            public void onApplicationEvent(TopEvent event) {
                logger.info("Top event for {}.", event.getSource());
            };
        };        
    }
}

相关链接: 我的Professional Java for Web Applications相关文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值