首页 范文大全 古典文学 职场知识 中国文学 公文书信 外国名著 寓言童话 百家讲坛 散文/诗歌 美文欣赏 礼仪知识 民俗风情
  • 工作总结
  • 工作计划
  • 心得体会
  • 竞聘演讲
  • 会议发言
  • 爱国演讲
  • 就职演说
  • 开业开幕
  • 思想学习
  • 征文演讲
  • 经验材料
  • 述职报告
  • 调研报告
  • 工作汇报
  • 年终总结
  • 申报材料
  • 学习体会
  • 企划方案
  • 活动方案
  • 技巧经验
  • 模板范例
  • 思想宣传
  • 经济工作
  • 工作报告
  • 组织人事
  • 反腐倡廉
  • 慰问贺电
  • 先进事迹
  • 思想汇报
  • 入党申请书
  • 党会发言
  • 先进性教育
  • 入团申请书
  • 个人简历
  • 演讲稿
  • 调查报告
  • 实习报告
  • 和谐社会
  • 观后感
  • 读后感
  • 作文范文
  • 自我鉴定
  • 讲话稿
  • 自查报告
  • Java,for,Web学习笔记(七一):Service和Repository(6)Spring框架中使用Listenerx

    时间:2020-09-22 11:46:04 来源:蒲公英阅读网 本文已影响 蒲公英阅读网手机站

    相关热词搜索:学习笔记 框架 Web

     Java for Web 学习笔记(七一):Service 和 和 Repository (6 )在 在 Spring 框架中使用 Listener 目的

      Listener 是 Servlet 的,不属于 Spring framework,也就是说我们无法在Listener 中主动注入 Spring bean。本学习将解决这个问题。

     进一步了解 Spring 的 的 bean 注入

      在解决之前,我们先进一步了解 Spring 的注入机制。在 Spring 中,我们可以使用@Inject,@Anwired,@Resource 等方式实现对自动扫描和自动注入。同一 同一上下文环境中,bean 只实例化一次,在不同类中注入的,都是同一个 bean (同一对象)。我们通常在根上下文中进行扫描,即使我们在不同的类中都进行注入,实际是注入的是同一个对象的。

      我们将通过小测试来验证这点。

     小测试:设置 Service

      设置一个简单的 Service,打印对象地址,同时在构造函数中给出 log,看看在哪个阶段进行实例化。

     public interface MyTestService {

     public void whoAmI(String className);

     }

     @Service

     public class MyTestServiceImpl implements MyTestService{

     private static final Logger log = LogManager.getLogger();

      public MyTestServiceImpl(){

     log.info("MyTestServiceImpl instance is created, address is " + this);

     }

      @Override

     public void whoAmI(String className) {

     log.info("{} : {}" , className,this);

      }

     }

     小测试:注入该 Service

      在 AuthenticationController 中

     @Controller

     public class AuthenticationController {

     @Inject private AuthenticationService authenticationService;

      @RequestMapping(value="login",method=RequestMethod.GET)

     public ModelAndView login(Map<String,Object> model,HttpSession session){

     myTestService.whoAmI(this.getClass().getName());

     ... ...

     }

     ... ...

     }

     在 TicketController 中 @Controller

     @RequestMapping("ticket")

     public class TicketController {

     @Inject private MyTestService myTestService;

      @RequestMapping(value = {"", "list"}, method = RequestMethod.GET)

     public String list(Map<String,Object> model){

     this.myTestService.whoAmI(this.getClass().getName());

     ... ...

     }

     }

     输出结果:

     14:19:19.985 [localhost-startStop-1] [INFO ] (Spring) ContextLoader - Root WebApplicationContext: initialization started

     ... ...

     14:19:20.633 [localhost-startStop-1] [INFO ] (Spring) AutowiredAnnotationBeanPostProcessor - JSR-330 "javax.inject.Inject" annotation found and supported for autowiring

     14:19:20.934 [localhost-startStop-1]

     [INFO ] MyTestServiceImpl:12 <init>() - MyTestServiceImpl instance is created, address is cn.wei.flowingflying.customer_support.site.test.MyTestServiceImpl@407cec

     ... ...

     六月 23, 2017 2:19:21 下午 org.apache.catalina.core.ApplicationContext log

     信息: Initializing Spring FrameworkServlet "springDispatcher"

     ... ...

     14:19:23.217 [http-nio-8080-exec-5]

     [INFO ] MyTestServiceImpl:16 whoAmI() - cn.wei.flowingflying.customer_support.site.AuthenticationController : cn.wei.flowingflying.customer_support.site.test.MyTestServiceImpl@407cec

     ... ...

     14:19:36.195 [http-nio-8080-exec-8] wei [INFO ] MyTestServiceImpl:16 whoAmI() - cn.wei.flowingflying.customer_support.site.TicketController :

     cn.wei.flowingflying.customer_support.site.test.MyTestServiceImpl@407cec

     我们看到在 AuthenticationController 和 TicketController 中注入的对象实际地址一样,都是 407cec,即为同一对象,是在 Root Context 中被实例化,且只实例化一次。了解这点非常重要,不同 Controller 对某个注入的 Service 进行操作,是可能相互影响的。

     在 在 Listener 中实现注入实例 无法直接在 Listener 中自动注入

      Listener 是 Serlvet container 的,不是 Spring framework 的,不是任何的Spring Component,不在自动扫描的范围内,我们在里面标记的任何@Inject 不会被注入。

      我们创建一个 Session Listener 作测试 @WebListener

     public class WeiTempListener implements HttpSessionListener {

     private static final Logger log = LogManager.getLogger();

     @Inject private MyTestService myTestService;

      public WeiTempListener() { }

      public void sessionCreated(HttpSessionEvent se)

     {

     log.info("------------------------------------");

     this.myTestService.whoAmI(this.getClass().getName());

     }

      public void sessionDestroyed(HttpSessionEvent se)

     { }

     }

     14:50:31.164 [http-nio-8080-exec-4]

     [INFO ] WeiTempListener:32 sessionCreated() - ------------------------------------

     六月 23, 2017 2:50:31 下午 org.apache.catalina.session.StandardSession tellNew

     严重: Session event listener threw exception

     java.lang.NullPointerException

     at cn.wei.flowingflying.customer_support.site.WeiTempListener.sessionCreated(WeiTempListener.java:33)

      实现方式

      前面已经看到注入的实例化是在 Root Context 中进行。我们需要在 Listener的初始化过程中,想办法从 Root Context 中获得实例。我们需要:

     1. 跟踪发现,Listener 的初始化是 RootContext 的初始化之前,这时是无法获取bean 的。因此 删除 @WebListener 的标记,否则无法确保初始化的顺序 – 在 BootStrap 中,在 Root Context 的初始化后加载 Listener,确保能够获取在 Root Context 中实例化的 bean 2. HttpSessionListener 封装很好,不开放初始化接口,因此需要增加继承ServletContextListener,以便暴露初始化的方法,在初始化中作为 bean。

     3. 使用 org.springframework.beans.factory.annotation.Configurable 标注对于非Spring 管理的 bean。

     public class BootStrap implements WebApplicationInitializer{

     @Override

     public void onStartup(ServletContext container) throws ServletException {

     container.getServletRegistration("default").addMapping("/resource/*");

      AnnotationConfigWebApplicationContext rootContext =

     new AnnotationConfigWebApplicationContext();

     rootContext.register(RootContextConfiguration.class);

     container.addListener(new ContextLoaderListener(rootContext));

     //【1】设置 Listener 的加载位置,在完成 Root Context 之后

     container.addListener(WeiTempListener.class);

     ... ...

     }

     }

     我们再看看 WeiTempListener //【1】删除@WebListener 标记,采用手动在 BootStrap 中加入

     //【2】增加 ServletContextListener 接口,以获得初始化入口

     public class WeiTempListener implements HttpSessionListener,ServletContextListener {

     private static final Logger log = LogManager.getLogger();

     @Inject private MyTestService myTestService;

     public WeiTempListener() {

     // 这在 Root Context 初始化之前执行,因此我们不能在构造函数中进行设置

     log.info("-----------------WeiTempListener-------------------");

     }

      public void sessionCreated(HttpSessionEvent se)

     {

     this.myTestService.whoAmI(this.getClass().getName());

     // 测试

     }

      public void sessionDestroyed(HttpSessionEvent se)

     {

     }

     //【3】在 contextInitialized()中获得 Spring 的 rootContext 实例

     @Override

     public void contextInitialized(ServletContextEvent event) {

     // 根据 BoorStrap 的执行顺序,这时 RootContext 的初始化已经完成,包括Service 的实例化,可以注入。

     // 无法自动注入是因为 Listerner 并不是 Spring 的 bean(如不是@Controller),我们要想办法手动让 Listerner 成为 bean。

     // (1)获取 Spring 的 root WebApplicationContext

     WebApplicationContext rootContext =

     WebApplicationContextUtils.getRequiredWebApplicationContext(event.getServletContext());

     // (2)获取根上下文扫描和注入 bean 的 factory

     AutowireCapableBeanFactory factory = rootContext.getAutowireCapableBeanFactory();

     // (3)无法扫描是因为 Listener 不是 Spring 的 bean,类上没有加 spring 的 annotation,我们需要手动设置这个对象(this)作为 Factory 中的一个 bean,这样才能对里面的属性进行注入

     factory.autowireBeanProperties(this, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE,true);

     // (4)在 factory 中对这个新的 bean 进行初始化。

     factory.initializeBean(this,"WeiTempListener");

     log.info(this.myTestService); //测试一下注入情况

     }

      public void contextDestroyed(ServletContextEvent sce) { }

     }

     限制说明

      虽然我们将 Listener 手动设置为 fatory 可以认识的 bean,但仍不是 spring 下一个真正意义的 bean。其他的 bean 中不能将其注入,部分地我们可以通过factory 的 registerSingleton(),将其设置为 singleton bean 来解决(即确保注入的都是同一的 bean),但依然收到限制,有些内容仍无法正常执行,如计划执行,构造后和注销前的回调函数。

     SessionListener 的具体应用例子 webSocket chat 例子

      这个小例子场景,我们在下一学习中继续使用,再此作个说明,用户请求帮助(通过 websocket 发其 chat),客服(另一用户)选择需要帮助的用户(加入chat),双方之间进行通话:

     • 用户和 web app 之间建立 web socket 连接 A,客服(另一用户)和 web app之间建立 web socket 连接 B,web app 关联这两段连接之间的消息收发。

     • web app 在 web socket 中定时向浏览器发送 ping 消息,并监听响应的 pong消息。

     • 用户的名字将根据登录信息自动获取,用户退出,chat 也将关闭 通过 SessionRegisterService 来维护所有的在线 http session – 将存放在 session 中的 username 方在请求的 principal 中,方便获取

      在 webSocket chat 中我们通过 SessionRegisterService 打算维护在线的session。对用户退出登录(主动退出,session 超时而被删除)时,如果该用户在chat 中,需要行 chat close 动作,可以利用 Consumer 进行触发。

     SessionRegisterService public interface SessionRegistryService {

     public void addSession(HttpSession session);

     public void updateSessionId(HttpSession session, String oldSessionId);

     public void removeSession(HttpSession session);

     /** 注册回调函数 用户开启 chat 进行回掉函数或者触发函数的注册 */

     public void registerOnRemoveCallback(Consumer<HttpSession> callback);

     /** 注销回掉函数 用户关闭 chat 进行注销 */

     public void deregisterOnRemoveCallback(Consumer<HttpSession> callback);

     }

     SessionListener

      SessionListener 没有什么特别:

     1 允许 SessionRegisterService 的注入,前面刚刚学习 2. 对 create/change Id/remove session 是调用 service 的 add/update 和 remove接口 public class SessionListener implements HttpSessionListener, ServletContextListener {

     @Inject private SessionRegistryService sessionRegistryService;

      public void sessionCreated(HttpSessionEvent event)

     {

     this.sessionRegistryService.addSession(event.getSession());

     }

      public void sessionIdChanged(HttpSessionEvent event, String oldSessionId)

     {

     this.sessionRegistryService.updateSessionId(event.getSession(), oldSessionId);

     }

      public void sessionDestroyed(HttpSessionEvent event)

     {

      this.sessionRegistryService.removeSession(event.getSession());

     }

      @Override

     public void contextInitialized(ServletContextEvent event) {

     .... 见前面 ....

     }

     ... ...

     }

     SessionRegistryService 的实现 @Service

     public class DefaultSessionRegistryService implements SessionRegistryService{

     private final Map<String, HttpSession> sessions = new Hashtable<>();

     /** Consumer 的具体操作是:如果 httpSession 相同,则删除,里面已经进行了判断,所以就不需要 Predicate */

     private final Set<Consumer<HttpSession>> callbacks = new HashSet<>();

     /** callbacksAddesWhileLocked 是个比较有意思的处理,需要学习:

      *

     我们几乎同时收到了同一个用户(同一个 httpSession)要求退出登录 和 chat 申请的两个操作,一般来讲虽然不会如此,多页面的请求有可能会造成几乎同时到达,由或者 session 到期的瞬间。callbacksAddedWhileLocked 用于对这个时间差的 session 进行处理,即请求加入,然后马上推出登录,即 removeSession()和 registerOnRemoveCallback()几乎同时操作。理想顺序是有先后,而不是同时进行,但实际多线程运行的顺序无法保证。callbacksAddedWhileLocked 来避免同时运行的问题。

     */

     private final Set<Consumer<HttpSession>> callbacksAddedWhileLocked = new HashSet<>();

      @Override

     public void addSession(HttpSession session) {

     this.sessions.put(session.getId(), session);

      }

      @Override

     public void updateSessionId(HttpSession session, String oldSessionId) {

     synchronized(this.sessions) {

     this.sessions.remove(oldSessionId);

     addSession(session);

     }

     }

      @Override

     public void removeSession(HttpSession session) {

     this.sessions.remove(session.getId());

     synchronized(this.callbacks){

     this.callbacksAddedWhileLocked.clear();

     this.callbacks.forEach(c -> c.accept(session));

     try {

     this.callbacksAddedWhileLocked.forEach(c -> c.accept(session));

     } catch(ConcurrentModificationException ignore) { }

     }

      }

      @Override

     public void registerOnRemoveCallback(Consumer<HttpSession> callback) {

     this.callbacksAddedWhileLocked.add(callback);

      synchronized(this.callbacks){

     this.callbacks.add(callback);

     }

      }

      @Override

     public void deregisterOnRemoveCallback(Consumer<HttpSession> callback) {

     synchronized(this.callbacks){

     this.callbacks.remove(callback);

     }

      }

     }

     相关链接:

     我的 Professional Java for Web Applications 相关文章

    • 范文大全
    • 职场知识
    • 精美散文
    • 名著
    • 讲坛
    • 诗歌
    • 礼仪知识