Springboot中使用攔截器、過濾器、監(jiān)聽器的流程分析
一、Servlet、Filter(過濾器)、 Listener(監(jiān)聽器)、Interceptor(攔截器)
Javaweb三大組件:servlet、Filter(過濾器)、 Listener(監(jiān)聽器)
SpringBoot特有組件:Interceptor(攔截器)
過濾器、攔截器、監(jiān)聽器、AOP(后續(xù)文章介紹)、全局異常處理器(后續(xù)文章介紹)是搭建系統(tǒng)框架時,經(jīng)常用到的部分,全局異常處理器的作用很明顯,就是處理接口執(zhí)行過程中的異常,而過濾器、攔截器和AOP的作用就很豐富了,日志記錄、性能監(jiān)控、安全認(rèn)證等等可以向上抽取的功能組件,均可以用他們來實現(xiàn)。
傳統(tǒng)基于Servlet容器的程序中,我們可以使用過濾器和監(jiān)聽器,在Java 框架中還可以使用攔截器,而面向切面編程AOP更是作為Spring框架中的核心思想被大家所關(guān)注
Filter和Listener:依賴Servlet容器,基于函數(shù)回調(diào)實現(xiàn)??梢詳r截所有請求,覆蓋范圍更廣,但無法獲取ioc容器中的bean。
Interceptor和aop:依賴spring框架,基于java反射和動態(tài)代理實現(xiàn)。只能攔截controller的請求,可以獲取ioc容器中的bean,這點很重要,在攔截器里注入一個service,可以調(diào)用業(yè)務(wù)邏輯。。
從 Filter -> Interceptor -> aop ,攔截的功能越來越細(xì)致、強(qiáng)大,尤其是Interceptor和aop可以更好的結(jié)合spring框架的上下文進(jìn)行開發(fā)。但是攔截順序也是越來越靠后,請求是先進(jìn)入Servlet容器的,越早的過濾和攔截對系統(tǒng)性能的消耗越少。具體選用哪種方法,就需要開發(fā)人員根據(jù)實際業(yè)務(wù)情況綜合考慮了。
二、過濾器
1、過濾器作用
Filter過濾器是Servlet容器層面的,在實現(xiàn)上基于函數(shù)回調(diào),可以對幾乎所有請求進(jìn)行過濾
過濾器是對數(shù)據(jù)進(jìn)行過濾,預(yù)處理過程,當(dāng)我們訪問網(wǎng)站時,有時候會發(fā)布一些敏感信息,發(fā)完以后有的會用*替代,還有就是登陸權(quán)限控制等,一個資源,沒有經(jīng)過授權(quán),肯定是不能讓用戶隨便訪問的,這個時候,也可以用到過濾器,主要是對用戶的一些請求進(jìn)行一些預(yù)處理,并在服務(wù)器響應(yīng)后再進(jìn)行預(yù)處理,返回給用戶。
過濾器的功能還有很多,例如實現(xiàn)URL級別的權(quán)限控制、壓縮響應(yīng)信息、編碼格式等等。對web服務(wù)器管理所有的web資源,例如jsp,靜態(tài)圖片 過濾敏感詞匯,某些信息用*隱藏。
使用方式有以下三種
2、過濾器執(zhí)行流程
3、過濾器攔截路徑
4、過濾鏈
一個javaweb系統(tǒng)中,可以配置多個過濾器、這多個過濾器就形成了一個過濾鏈,當(dāng)然可以控制每個過濾器順序,見過濾器三種實現(xiàn)方式
5、過濾器三種實現(xiàn)方式
第一種方式:利用Servlet3.0的WebFilter注解配置
@WebFilter是Servlet3.0新增加的注解,在servlet3.0之前,我們需要在web.xml文件中進(jìn)行過濾器的配置,而現(xiàn)在可以通過此注解進(jìn)行配置,當(dāng)項目啟動時,會自動掃描自動注冊
通過 @WebFilter 注解來標(biāo)記一個過濾器,這種方式相信大家很容易想到。這是將 Servlet 中的那一套東西直接拿到 Spring Boot 上用。
具體做法就是通過 @WebFilter 注解來標(biāo)記一個 Filter,如下:
//一個是filter的名字,一個是url為什么時用此過濾器,其他的看源碼,Filter,必須要有名字,所有的過濾器執(zhí)行順序是根據(jù)Filter的名字字母順序來執(zhí)行的 @WebFilter(filterName = "filter1",urlPatterns = {"/hello/*"}) public class TimeFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("=======初始化過濾器========="); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { long start = System.currentTimeMillis(); filterChain.doFilter(request, response); System.out.println("filter 耗時:" + (System.currentTimeMillis() - start)); } @Override public void destroy() { System.out.println("=======銷毀過濾器========="); } }
這個注解要生效,還需要我們在項目啟動類上配置 @ServletComponentScan 注解,
@ServletComponentScan 注解雖然名字帶了 Servlet,但是實際上它不僅僅可以掃描項目中的 Servlet 容器,也可以掃描 Filter 和 Listener。
這是我們在 Spring Boot 中使用過濾器的第一種方式,在實際項目中,這種方式使用較少,因為這種方式有一個很大的弊端就是無法指定 Filter 的優(yōu)先級,如果存在多個 Filter 時,無法通過 @Order 指定優(yōu)先級。
@SpringBootApplication @ServletComponentScan public class SysoaApplication { public static void main(String[] args) { SpringApplication.run(SysoaApplication.class, args); } }
第二種方式:利用SpringBoot的配置類來添加過濾器
這種方式最簡單,直接實現(xiàn)Filter接口,并使用@Component注解標(biāo)注為組件自動注入bean,
Filter接口有 init、doFilter、destroy 三個方法,但 init、destroy 是有默認(rèn)方法實現(xiàn),可以不重寫。
@Component public class TimeFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("=======初始化過濾器========="); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { long start = System.currentTimeMillis(); filterChain.doFilter(request, response); System.out.println("filter 耗時:" + (System.currentTimeMillis() - start)); } @Override public void destroy() { System.out.println("=======銷毀過濾器========="); } }
但是缺點是沒辦法設(shè)置過濾的路徑,默認(rèn)是 /* 過濾所有(當(dāng)然代碼中可以加入相關(guān)判斷)。
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; String requestUri = request.getRequestURI().replaceFirst(contextPath, ""); String[] urlList = {"/login","/loginPage","logOut"}; for (String uriItem : urlList){ if(requestUri.contains(uriItem)){ filterChain.doFilter(request,response); return; } } //判斷session中是否存在用戶,這里還可以做其他業(yè)務(wù)邏輯 比如ip黑名單等 用戶是否被禁,禁止多人在線等 if (request.getSession().getAttribute(SystemConstants.USER) == null) { //沒有登錄跳轉(zhuǎn)到登錄頁面 response.sendRedirect(contextPath+SystemConstants.LOGIN_PAGE_URL); return; } filterChain.doFilter(request, response); }
這種方式看起來很方便,一個注解將 Filter 納入到 Spring 容器中即可。而且這種方式還有一個優(yōu)勢,就是如果存在多個 Filter,可以通過 @Order 注解指定多個 Filter 的優(yōu)先級,像下面這樣:
@Component @Order(-1) public class MyFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("-----doFilter-----"); chain.doFilter(request, response); } }
第三種方式:Configuration+FilterRegistrationBean
還是將 Filter 封裝成一個 Bean,但這個 Bean 是 FilterRegistrationBean,通過 FilterRegistrationBean 我們既可以配置 Filter 的優(yōu)先級,也可以配置 Filter 的攔截規(guī)則。
public class AFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("=======初始化過濾器A========="); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { long start = System.currentTimeMillis(); filterChain.doFilter(request, response); System.out.println("filter 耗時:" + (System.currentTimeMillis() - start)); } @Override public void destroy() { System.out.println("=======銷毀過濾器========="); } }
然后使用SpringBoot提供的FilterRegistrationBean來對Filter進(jìn)行配置
@Configuration public class FilterConfig { /*過濾器注解bean: FilterRegistrationBean, 注冊過濾器, 添加過濾器*/ @Bean public FilterRegistrationBean<AFilter> createFilterRegistrationBean() { //1.創(chuàng)建FilterRegistrationBean這個對象, 一個過濾器注冊器,注冊一個過濾器 FilterRegistrationBean<AFilter> filterRegistrationBean = new FilterRegistrationBean<>(); //注冊一個過濾器 filterRegistrationBean.setFilter(new AFilter()); //過濾器的配置, 設(shè)置攔截的url filterRegistrationBean.addUrlPatterns("/*"); //給過濾器起名字 filterRegistrationBean.setName("AFilter"); //設(shè)置過濾器的執(zhí)行順序 filterRegistrationBean.setOrder(1); return filterRegistrationBean; }
當(dāng)然也可以配置多個并且控制順序
@Configuration public class FilterConfig { @Bean FilterRegistrationBean<AFilter> myFilterFilterRegistrationBean() { FilterRegistrationBean<AFilter> bean = new FilterRegistrationBean<>(); bean.setFilter(new AFilter()); bean.setOrder(-1); bean.setUrlPatterns(Arrays.asList("/*")); return bean; } @Bean FilterRegistrationBean<BFilter> myFilterFilterRegistrationBean2() { FilterRegistrationBean<BFilter> bean = new FilterRegistrationBean<>(); bean.setFilter(new BFilter()); bean.setOrder(-2); bean.setUrlPatterns(Arrays.asList("/hello")); return bean; } }
FilterRegistrationBean 擴(kuò)展
Spring Boot 為了方便大家向 Servlet 容器中注冊 Servlet、Filter 以及 Listener,提供了一個 Bean 注冊的抽象類 RegistrationBean,如下:
public abstract class RegistrationBean implements ServletContextInitializer, Ordered { private int order = Ordered.LOWEST_PRECEDENCE; private boolean enabled = true; @Override public final void onStartup(ServletContext servletContext) throws ServletException { String description = getDescription(); if (!isEnabled()) { logger.info(StringUtils.capitalize(description) + " was not registered (disabled)"); return; } register(description, servletContext); } protected abstract String getDescription(); protected abstract void register(String description, ServletContext servletContext); public void setEnabled(boolean enabled) { this.enabled = enabled; } public boolean isEnabled() { return this.enabled; } public void setOrder(int order) { this.order = order; } @Override public int getOrder() { return this.order; } }
RegistrationBean 實現(xiàn)了 ServletContextInitializer 接口,在 Servlet 啟動時,RegistrationBean#onStartup 方法會被調(diào)用,進(jìn)而完成 Filter、Servlet 以及 Listener 的注冊。
enabled 屬性可以理解為一個開關(guān),設(shè)置為 false 相當(dāng)于關(guān)閉組件注冊。
RegistrationBean 有眾多的實現(xiàn)類,我們之前使用的 FilterRegistrationBean 只是其中之一:
實現(xiàn)類的作用一目了然:
ServletListenerRegistrationBean 用來注冊監(jiān)聽器。
ServletRegistrationBean 用來注冊 Servlet。
DispatcherServletRegistrationBean 用來注冊 DispatcherServlet。
FilterRegistrationBean 用來注冊過濾器。
DelegatingFilterProxyRegistrationBean 則用來注冊 DelegatingFilterProxy,DelegatingFilterProxy 在 Spring Security、Spring Session、Shiro 等整合時非常有用。
6、過濾器實際項目場景使用舉例
使用filter實現(xiàn)登錄校驗
三、監(jiān)聽器
Listener監(jiān)聽器也是Servlet層面的,可以用于監(jiān)聽Web應(yīng)用中某些對象、信息的創(chuàng)建、銷毀和修改等動作發(fā)生,然后做出相應(yīng)的響應(yīng)處理??梢栽谶@些事件發(fā)生前和發(fā)生后進(jìn)行處理。
用途:
1、用于統(tǒng)計在線人數(shù)和在線用戶,
2、系統(tǒng)啟動時加載初始化信息,
3、統(tǒng)計網(wǎng)站訪問量,
4、記錄用戶訪問路徑
根據(jù)監(jiān)聽對象,將監(jiān)聽器分為3類:
第一類:ServletContext:對應(yīng)application,實現(xiàn)接口ServletContextListener。在整個Web服務(wù)中只有一個,在Web服務(wù)關(guān)閉時銷毀。可用于做數(shù)據(jù)緩存,例如結(jié)合redis,在Web服務(wù)創(chuàng)建時從數(shù)據(jù)庫拉取數(shù)據(jù)到緩存服務(wù)器。
第二類:HttpSession:對應(yīng)session會話,實現(xiàn)接口HttpSessionListener。在會話起始時創(chuàng)建,一端關(guān)閉會話后銷毀??捎米鳙@取在線用戶數(shù)量。
第三類:ServletRequest:對應(yīng)request,實現(xiàn)接口ServletRequestListener。request對象是客戶發(fā)送請求時創(chuàng)建的,用于封裝請求數(shù)據(jù),請求處理完畢后銷毀??捎米鞣庋b用戶信息。
在寫Listener的類時,有兩種方式。
第一種是只加@Component;
第二種是 @WebListener 和 @ServletComponentScan 配合使用。
不過實現(xiàn)接口則根據(jù)監(jiān)聽對象區(qū)分,如:ServletContextListener、HttpSessionListener和ServletRequestListener。
import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; @WebListener() public class MyListener implements HttpSessionListener{ public static int online = 0; @Override public void sessionCreated(HttpSessionEvent se) { System.out.println("創(chuàng)建session,在線用戶數(shù):" + (++online)); } @Override public void sessionDestroyed(HttpSessionEvent se) { System.out.println("銷毀session,在線用戶數(shù):" + (--online)); online--; } }
啟動類
@SpringBootApplication @ServletComponentScan("com.demo.listener") //需要掃描包 public class SpringBoot1Application { public static void main(String[] args) { SpringApplication.run(SpringBoot1Application.class, args); } }
這里我們再補(bǔ)充一下常用的監(jiān)聽器接口:
1.ServletContextListener -- 監(jiān)聽servletContext對象的創(chuàng)建以及銷毀
1.1 contextInitialized(ServletContextEvent arg0) -- 創(chuàng)建時執(zhí)行
1.2 contextDestroyed(ServletContextEvent arg0) -- 銷毀時執(zhí)行
2.HttpSessionListener -- 監(jiān)聽session對象的創(chuàng)建以及銷毀
2.2 sessionCreated(HttpSessionEvent se) -- 創(chuàng)建時執(zhí)行
2.2 sessionDestroyed(HttpSessionEvent se) -- 銷毀時執(zhí)行
3.ServletRequestListener -- 監(jiān)聽request對象的創(chuàng)建以及銷毀
3.1 requestInitialized(ServletRequestEvent sre) -- 創(chuàng)建時執(zhí)行
3.2 requestDestroyed(ServletRequestEvent sre) -- 銷毀時執(zhí)行
4.ServletContextAttributeListener -- 監(jiān)聽servletContext對象中屬性的改變
4.1 attributeAdded(ServletContextAttributeEvent event) -- 添加屬性時執(zhí)行
4.2 attributeReplaced(ServletContextAttributeEvent event) -- 修改屬性時執(zhí)行
4.3 attributeRemoved(ServletContextAttributeEvent event) -- 刪除屬性時執(zhí)行
5.HttpSessionAttributeListener --監(jiān)聽session對象中屬性的改變
5.1 attributeAdded(HttpSessionBindingEvent event) -- 添加屬性時執(zhí)行
5.2 attributeReplaced(HttpSessionBindingEvent event) -- 修改屬性時執(zhí)行
5.3 attributeRemoved(HttpSessionBindingEvent event) -- 刪除屬性時執(zhí)行
6.ServletRequestAttributeListener --監(jiān)聽request對象中屬性的改變
6.1 attributeAdded(ServletRequestAttributeEvent srae) -- 添加屬性時執(zhí)行
6.2 attributeReplaced(ServletRequestAttributeEvent srae) -- 修改屬性時執(zhí)行
6.3 attributeRemoved(ServletRequestAttributeEvent srae) -- 刪除屬性時執(zhí)行
四、攔截器
1、攔截器作用
過濾器是攔截所有請求,而攔截器是攔截在進(jìn)入到前端控制器之后的請求,
Interceptor和Filter、Listener有本質(zhì)上的不同,F(xiàn)ilter、Listener都是依賴于Servlet容器,而Interceptor則是依賴于Spring框架,是aop的一種表現(xiàn),當(dāng)某個方法或字段被訪問時進(jìn)行攔截,在之前 和 在之后 加入某些操作
用途:權(quán)限驗證,判斷用戶是否登錄,或者再做某一操作時要確定滿足某一定的條件,基于Java的動態(tài)代理實現(xiàn)的。
2、攔截器執(zhí)行流程
3、攔截器攔截路徑
攔截器可以根據(jù)需求配置不同的攔截路徑
4、攔截器的使用
首先通過實現(xiàn) HandlerInterceptor接口聲明攔截器的類,實現(xiàn)preHandle、postHandle和afterCompletion方法。然后通過配置類WebMvcConfigurer接口,實現(xiàn)addInterceptors方法配置攔截器
import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class InterceptorTest implements HandlerInterceptor { /** * 在請求處理之前進(jìn)行調(diào)用(Controller方法調(diào)用之前) * 預(yù)處理回調(diào)方法,實現(xiàn)處理器的預(yù)處理 * 返回值:true表示繼續(xù)流程;false表示流程中斷,不會繼續(xù)調(diào)用其他的攔截器或處理器 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("開始攔截........."); String name = MyCookie.getCookieByKey(request,response,"name"); String password = MyCookie.getCookieByKey(request,response,"password"); //如果session中沒有user,表示沒登陸 if (password == null|| name == null){ //這個方法返回false表示忽略當(dāng)前請求,如果一個用戶調(diào)用了需要登陸才能使用的接口,如果他沒有登陸這里會直接忽略掉 //當(dāng)然你可以利用response給用戶返回一些提示信息,告訴他沒登陸 request.getRequestDispatcher("/interceptor/to_login").forward(request, response); return false; }else { return true;//放行 } } /** * 請求處理之后進(jìn)行調(diào)用,但是在視圖被渲染之前(Controller方法調(diào)用之后) * 后處理回調(diào)方法,實現(xiàn)處理器(controller)的后處理,但在渲染視圖之前 * 此時我們可以通過modelAndView對模型數(shù)據(jù)進(jìn)行處理或?qū)σ晥D進(jìn)行處理 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // TODO Auto-generated method stub System.out.println("return前"); } /** * 在整個請求結(jié)束之后被調(diào)用,也就是在DispatcherServlet 渲染了對應(yīng)的視圖之后執(zhí)行(主要是用于進(jìn)行資源清理工作) * 整個請求處理完畢回調(diào)方法,即在視圖渲染完畢時回調(diào), * 如性能監(jiān)控中我們可以在此記錄結(jié)束時間并輸出消耗時間, * 還可以進(jìn)行一些資源清理,類似于try-catch-finally中的finally, * 但僅調(diào)用處理器執(zhí)行鏈中 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // TODO Auto-generated method stub System.out.println("操作完之后,可以用于資源清理"); } }
然后將這個組件加入到boot中,在boot1版本中 通過繼承WebmvcConfigureAdapter實現(xiàn)一個web配置,例如我們配置上面的攔截器
@Configuration //聲明這是一個配置 public class LoginInterceptorConfig extends WebMvcConfigurerAdapter { @Resource private LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor).addPathPatterns("/admin/**").excludePathPatterns("/admin").excludePathPatterns("/admin/login"); } }
或者直接使用匿名類的方式
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 自定義一個登陸攔截器 */ @Configuration //聲明這是一個配置 public class LoginInterceptor extends WebMvcConfigurerAdapter { /* 用來添加攔截器的方法 InterceptorRegistry registry攔截器注冊 */ @Override public void addInterceptors(InterceptorRegistry registry) { //使用匿名內(nèi)部類創(chuàng)建要給攔截器 HandlerInterceptor loginInterceptor = new HandlerInterceptor() { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception { //判斷session中是否存在用戶 if (request.getSession().getAttribute("user") == null) { response.sendRedirect("/admin"); return false; } return true; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { } }; registry.addInterceptor(loginInterceptor).addPathPatterns("/admin/**").excludePathPatterns("/admin").excludePathPatterns("/admin/login"); } }
對于Sprinboot2版本,第一步還是定義一個攔截器組件。
第二不再是通過繼承WebmvcConfigureAdapter實現(xiàn)一個web配置,而是實現(xiàn)接口WebMvcConfigurer增加一個配置
@Configuration public class WebConfig implements WebMvcConfigurer { //引入我們的攔截器組件 @Resource private LoginInterceptor loginInterceptor; //實現(xiàn)攔截器配置方法 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor).addPathPatterns("/admin/**").excludePathPatterns("/admin").excludePathPatterns("/admin/login"); } }
踩坑5--springboot2.xl登陸攔截器中排除靜態(tài)資源的幾種情況整理 - 百度文庫 (baidu.com)
5、攔截器使用場景舉例
Filter與Interceptor區(qū)別
1.接口范圍不同:過濾器需要實現(xiàn)Filter接口,而攔截器需要實現(xiàn)HandlerInterceptor接口。
2.攔截范圍不同:過濾器Filter會攔截所有的資源,而Interceptor只會攔截Spring環(huán)境中的資源
到此這篇關(guān)于Springboot中使用攔截器、過濾器、監(jiān)聽器的文章就介紹到這了,更多相關(guān)Springboot使用攔截器、過濾器、監(jiān)聽器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot+Mybatis中typeAliasesPackage正則掃描實現(xiàn)方式
這篇文章主要介紹了Springboot+Mybatis中typeAliasesPackage正則掃描實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07Java中增強(qiáng)for循環(huán)在一維數(shù)組和二維數(shù)組中的使用方法
下面小編就為大家?guī)硪黄狫ava中增強(qiáng)for循環(huán)在一維數(shù)組和二維數(shù)組中的使用方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-10-10解讀CommandLineRunner和@PostConstruct區(qū)別與應(yīng)用場景
這篇文章主要介紹了解讀CommandLineRunner和@PostConstruct區(qū)別與應(yīng)用場景,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12