基於spring的web項目啟動時預加載數據到ServletContext


1、要在web啟動時預加載數據到ServletContext,實現方法有很多,一種比較簡單的方案就是:

  1)新建一個bean,定義其初始化方法:

    <bean id="beanId" class="beanClassName" init-method="初始化方法" />或者使用@PostConstruct注解到初始化方法上面

  2)獲取ServletContext實例對象,如何獲取呢?

    方法1:

      @Autowired
      private ServletContext application;

    方法2:

      實現 ServletContextAware接口,重寫setServletContext(ServletContext)方法

  3)在初始化方法里面寫代碼,調用service層或dao層方法,查詢數據庫得到數據;將數據設置給ServletContext實例對象;

  

2、案例

  jdk: 1.8.0_111;

  tomcat: apache-tomcat-9.0.13;

  依賴jar包:

  項目結構:

 

  在service層定義一個類InitComponent,定義初始化方法init()。在spring容器加載這個bean時,會調用init()方法。

package com.oy.service.impl;

import javax.annotation.PostConstruct;
import javax.servlet.ServletContext;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.oy.service.BookService;

@Component
public class InitComponent {
    @Autowired
    private BookService bookService;

    @Autowired
    private ServletContext application;

    @PostConstruct
    public void init() {
        System.out.println("向application域中添加數據。。。");
        application.setAttribute("bookList", bookService.findAll());
        System.out.println(application.getAttribute("bookList"));
    }
}

   為了測試方便,dao層不查詢數據庫,而是使用模擬數據:

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;
    
    @Override
    public List<Book> findAll() {
        return bookDao.findAll();
    }
}


@Repository
public class BookDaoImpl implements BookDao {
    private List<Book> bookList = new ArrayList<>();
    
    @Override
    public List<Book> findAll() {
        Book book1 = new Book();
        book1.setId(1);
        book1.setName("java編程思想");
        bookList.add(book1);
        
        Book book2 = new Book();
        book2.setId(2);
        book2.setName("java從入門到精通");
        bookList.add(book2);
        
        return bookList;
    }
}

 

  以后數據改變了,想要更新緩存怎么辦?寫一個controller,調用InitComponent#init()。在頁面上定義一個按鈕,點擊訪問 initController#refreshCache(),即可更新緩存。記得更新前先刪除緩存。

package com.oy.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.oy.service.impl.InitComponent;

@Controller
public class InitController {
    @Autowired
    private InitComponent initComponent;
    
    @RequestMapping("/refreshCache")
    @ResponseBody
    public String refreshCache() {
        initComponent.init();
        return "controller刷新緩存成功!";
    }
}

 

  配置文件web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

    <!-- spring配置文件 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>

    <!-- spring監聽器,初始化spring IoC容器 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- springmvc前端控制器 -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 如果不配置springmvc配置文件路徑,默認在/WEB-INF/[servlet-name]-servlet.xml -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>*.do</url-pattern>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- post請求的編碼過濾器 -->
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>*.do</url-pattern>
        <url-pattern>/</url-pattern>
    </filter-mapping>
</web-app>

 

  配置文件applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>    
<beans xmlns="http://www.springframework.org/schema/beans"    
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
    xmlns:p="http://www.springframework.org/schema/p"  
    xmlns:aop="http://www.springframework.org/schema/aop"   
    xmlns:context="http://www.springframework.org/schema/context"  
    xmlns:jee="http://www.springframework.org/schema/jee"  
    xmlns:tx="http://www.springframework.org/schema/tx"  
    xsi:schemaLocation="    
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-4.0.xsd     http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd     http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.0.xsd     http://www.springframework.org/schema/jee
    http://www.springframework.org/schema/jee/spring-jee-4.0.xsd     http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"
> <bean class="com.oy.controller.BeanPostProcessorImpl"/> <!-- 自動掃描 --> <context:component-scan base-package="com.oy.service,com.oy.dao" /> </beans>

 

  配置文件springmvc.xml

<?xml version="1.0" encoding="UTF-8"?>    
<beans xmlns="http://www.springframework.org/schema/beans"    
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
    xmlns:p="http://www.springframework.org/schema/p"  
    xmlns:aop="http://www.springframework.org/schema/aop"   
    xmlns:context="http://www.springframework.org/schema/context"  
    xmlns:jee="http://www.springframework.org/schema/jee"  
    xmlns:tx="http://www.springframework.org/schema/tx"  
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="    
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-4.0.xsd     http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd     http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd     http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.0.xsd     http://www.springframework.org/schema/jee
    http://www.springframework.org/schema/jee/spring-jee-4.0.xsd     http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"
> <!-- 加載properties文件 --> <!-- <context:property-placeholder location="classpath:params.properties"/> --> <!-- 使用注解的包,包括子集 --> <context:component-scan base-package="com.oy.controller" /> <!-- 處理器映射器,處理器適配器 --> <!-- <mvc:annotation-driven conversion-service="conversionService"/> --> <mvc:annotation-driven /> <!-- Converter轉換器工廠 注: 需要在 適配器 中進行配置 --> <!-- <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> 日期 去掉前后空格 <property name="converters"> <set> <bean class="com.oy.converter.CustomConverter"></bean> </set> </property> </bean> --> <!-- 靜態資源映射 <mvc:resources mapping="/static/**" location="/static/"/> --> <!-- 如果使用了RESTful形式的攔截,那么對於靜態資源的處理上,就需要加上此句,靜態資源(沒有映射的)
     就會交給默認的web容器中的servlet進行處理
--> <mvc:default-servlet-handler /> <!-- 視圖解析器 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/" /> <property name="suffix" value=".jsp"></property> </bean> </beans>

 

  另外,貼一個BeanPostProcessor的實現類,這個類可以幫助我們查看spring容器中加載了哪些bean。利用這個類還可以實現bean的代理,這里就不詳細講了。如果不想使用這個類,為了避免報錯,需要將applicationContext.xml中<bean class="com.oy.controller.BeanPostProcessorImpl"/>這一句刪掉。

package com.oy.controller;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

/**
 * @author oy
 * @version 1.0
 * @date 2018年12月29日
 * @time 下午9:30:53
 */
public class BeanPostProcessorImpl implements BeanPostProcessor {

    public BeanPostProcessorImpl() {
        System.out.println();
        System.out.println("==========調用BeanPostProcessorImpl無參構造==========");
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("前處理方法,beanName為:" + beanName);
        return bean;
    }

    /**
     * 參數bean:目標對象,即被代理的bean對象 參數beanName:被代理對象的名字,即bean的id屬性值
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后處理方法,bean的類型為:" + bean.getClass().getName());

        // 通過beanName過濾,對不同的bean進行處理
        if ("userService".equals(beanName)) {
            // 生成jdk代理
            return Proxy.newProxyInstance(BeanPostProcessorImpl.class.getClassLoader(), bean.getClass().getInterfaces(),
                    new InvocationHandler() {

                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.println("===開啟事務===");
                            Object obj = method.invoke(bean, args);
                            System.out.println("===提交事務===");
                            return obj;
                        }

                    });
        } else if ("people".equals(beanName)) {
            // 生成jdk代理
            return Proxy.newProxyInstance(BeanPostProcessorImpl.class.getClassLoader(), bean.getClass().getInterfaces(),
                    new InvocationHandler() {

                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                            // System.out.println(proxy.getClass().getName()); // com.sun.proxy.$Proxy6

                            long start = System.currentTimeMillis();
                            Object obj = method.invoke(bean, args);
                            long time = System.currentTimeMillis() - start;

                            System.out.println("方法" + method.getName() + "()共耗時:" + time + "毫秒");
                            return obj;
                        }

                    });
        } else {
            // 直接返回bean,不生成代理對象
            return bean;
        }
    }
}

  

  在項目根路徑下新建一個index.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <h1>index頁面</h1>
    <c:forEach items="${bookList}" var="book">
        ${book.id} -- ${book.name}
    </c:forEach>
</body>
</html>

 

3、測試

  3.1、項目啟動,控制台打印結果:

    ==========調用BeanPostProcessorImpl無參構造==========
    前處理方法,beanName為:bookDaoImpl
    后處理方法,bean的類型為:com.oy.dao.impl.BookDaoImpl
    前處理方法,beanName為:bookServiceImpl
    后處理方法,bean的類型為:com.oy.service.impl.BookServiceImpl
    前處理方法,beanName為:initComponent
    向application域中添加數據。。。
    [Book [id=1, name=java編程思想], Book [id=2, name=java從入門到精通]]
    后處理方法,bean的類型為:com.oy.service.impl.InitComponent
    前處理方法,beanName為:initController
    后處理方法,bean的類型為:com.oy.controller.InitController
    前處理方法,beanName為:org.springframework.context.event.internalEventListenerProcessor
    后處理方法,bean的類型為:org.springframework.context.event.EventListenerMethodProcessor
    前處理方法,beanName為:org.springframework.context.event.internalEventListenerFactory
    后處理方法,bean的類型為:org.springframework.context.event.DefaultEventListenerFactory

 

  3.2、瀏覽器訪問:http://localhost:8080/04_Demo001/refreshCache, 控制台打印結果:

    向application域中添加數據。。。
    [Book [id=1, name=java編程思想], Book [id=2, name=java從入門到精通], Book [id=1, name=java編程思想], Book [id=2, name=java從入門到精通]]

 

4、在上面案例中將啟動初始數據和更新數據寫在兩個類中,其實沒有必要。

  4.1、首先要知道spring可以有多個容器,spring主容器加載applicationContext.xml中配置或掃描的bean,springmvc子容器加載springmvc.xml中配置或掃描的bean;父子容器的關系這里不詳細講。可以參考:

  spring與springMVC的細節問題:父子容器關系,加載controller,404錯誤

  Spring的父子容器問題和坑

  spring父子容器----子容器不能獲取父容器的屬性

  4.2、第一次懲罰:默認情況下,服務器是在第一次收到對controller#handler的請求時,會初始化DispatcherServlet,創建springmvc子容器,加載springmvc.xml中配置或掃描的bean;我上面的案例就是這種情況。

  4.3、如果在web.xml配置DispatcherServlet時添加了<load-on-startup>1</load-on-startup>,則服務器啟動時就會初始化DispatcherServlet,創建springmvc子容器,加載springmvc.xml中配置或掃描的bean;

  4.4、所以在web.xml配置DispatcherServlet時添加了<load-on-startup>1</load-on-startup>后,可以將案例中initComponent類和initController類合並成一個類:

package com.oy.controller;

import javax.annotation.PostConstruct;
import javax.servlet.ServletContext;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.oy.service.BookService;

@Controller
public class InitController {
    @Autowired
    private BookService bookService;

    @Autowired
    private ServletContext application;

    @PostConstruct
    public void init() {
        System.out.println("向application域中添加數據。。。");
        application.setAttribute("bookList", bookService.findAll());
        System.out.println(application.getAttribute("bookList"));
    }

    @RequestMapping("/refreshCache")
    @ResponseBody
    public String refreshCache() {
        init();
        return "controller refreshCache ok!";
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM