国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Spring Security實現原理剖析(一):filter的構造和初始化

caige / 1621人閱讀

摘要:前言我們知道的核心實現原理都是從開始的,通過構造層層來實現登錄跳轉權限驗證,角色管理等功能。本章通過剖析的核心源碼來說明的是如何開始構造并運行的。

前言

我們知道Spring Security的核心實現原理都是從filter開始的,Spring Security通過構造層層filter來實現登錄跳轉、權限驗證,角色管理等功能。本章通過剖析Spring Security的核心源碼來說明Spring Security的filter是如何開始構造并運行的。

從最初開始

往往我們定義一個Spring Security程序都是通過配置一個WebSecurityConfig類開始的,簡單代碼如下:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and().formLogin();
    }
    
}

通過以上代碼一個簡單的Spring Security應用程序就能成功執行了,該程序能攔截除了/login路徑以外的所有請求到登錄頁面。
我們可以看到以上代碼并沒有任何顯示聲明filter的語句,那么Spring Security是如何通過上述代碼生成filter的呢?下面就由我來一層層解剖Spring Security的源碼來說明。

@EnableWebSecurity注解

我們注意到如上代碼有個@EnableWebSecurity注解,進入該注解查看

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
#注意這里!
@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class})
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
    boolean debug() default false;
}
WebSecurityConfiguration類

我們可以看到如上該注解導入了WebSecurityConfiguration類,進入該類查看:

@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
    private WebSecurity webSecurity;
    private Boolean debugEnabled;
    private List> webSecurityConfigurers;
    private ClassLoader beanClassLoader;
    @Autowired(
        required = false
    )
    private ObjectPostProcessor objectObjectPostProcessor;

    public WebSecurityConfiguration() {
    }

    @Bean
    public static DelegatingApplicationListener delegatingApplicationListener() {
        return new DelegatingApplicationListener();
    }
    ........
}

WebSecurityConfiguration類是作為一個Spring配置源,同時定義了許多bean,這里重點看如下這個方法:

    
     @Autowired(
        required = false
    )
    public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor objectPostProcessor, 
    @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") 
    List> webSecurityConfigurers) throws Exception {
    
        // 這段代碼初始化webSecurity
        this.webSecurity = (WebSecurity)objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
        if (this.debugEnabled != null) {
            this.webSecurity.debug(this.debugEnabled);
        }
        
        // webSecurityConfigurers該屬性是通過@Value注解注入的
        Collections.sort(webSecurityConfigurers, WebSecurityConfiguration.AnnotationAwareOrderComparator.INSTANCE);
        Integer previousOrder = null;
        Object previousConfig = null;

        Iterator var5;
        SecurityConfigurer config;
        for(var5 = webSecurityConfigurers.iterator(); var5.hasNext(); previousConfig = config) {
            config = (SecurityConfigurer)var5.next();
            Integer order = WebSecurityConfiguration.AnnotationAwareOrderComparator.lookupOrder(config);
            if (previousOrder != null && previousOrder.equals(order)) {
                throw new IllegalStateException("@Order on WebSecurityConfigurers must be unique. Order of " + order + " was already used on " + previousConfig + ", so it cannot be used on " + config + " too.");
            }

            previousOrder = order;
        }
        
        // 將webSecurityConfigurers依次放入webSecurity
        var5 = webSecurityConfigurers.iterator();
        while(var5.hasNext()) {
            config = (SecurityConfigurer)var5.next();
            this.webSecurity.apply(config);
        }

        this.webSecurityConfigurers = webSecurityConfigurers;
    }

總結下該方法所做的主要操作:

首先初始化了webSecurity屬性,該屬性對應WebSecurity類

注入了webSecurityConfigurers屬性,該屬性是一個List集合

遍歷webSecurityConfigurers集合,調用webSecurity的apply方法,該方法參數為SecurityConfigurer接口

這里有一個重要的接口SecurityConfigurer接口,該接口代碼如下:

public interface SecurityConfigurer> {
    void init(B var1) throws Exception;

    void configure(B var1) throws Exception;
}

回顧上面我們編寫的WebSecurityConfig配置類,也有一個configure方法,那么我們猜測WebSecurityConfig類是不是也實現了SecurityConfigurer接口呢?答案是是的,我們可以看WebSecurityConfig類的類圖

可以看到WebSecurityConfig類實現了SecurityConfigurer接口。
因此webSecurityConfigurers屬性通過依賴注入包含了WebSecurityConfig類,通過上述第3條操作將我們配置的WebSecurityConfig類和WebSecurity類關聯起來。

WebSecurity類

到這里我們知道了WebSecurityConfiguration類調用上述方法將我們配置的WebSecurityConfig類用WebSecurity類的apply方法關聯起來,那么我們詳細看看WebSecurity類的apply方法:

public > C apply(C configurer) throws Exception {
        configurer.addObjectPostProcessor(this.objectPostProcessor);
        configurer.setBuilder(this);
        // 繼續調用該類的add方法
        this.add(configurer);
        return configurer;
    }
    
private > void add(C configurer) throws Exception {
        Assert.notNull(configurer, "configurer cannot be null");
        // 獲取class屬性
        Class> clazz = configurer.getClass();
        // 獲取LinkedHashMap
        LinkedHashMap var3 = this.configurers;
        synchronized(this.configurers) {
            if (this.buildState.isConfigured()) {
                throw new IllegalStateException("Cannot apply " + configurer + " to already built object");
            } else {
                List> configs = this.allowConfigurersOfSameType ? (List)this.configurers.get(clazz) : null;
                if (configs == null) {
                    configs = new ArrayList(1);
                }

                ((List)configs).add(configurer);
                // 將configurer放入一個LinkedHashMap中
                this.configurers.put(clazz, configs);
                if (this.buildState.isInitializing()) {
                    this.configurersAddedInInitializing.add(configurer);
                }

            }
        }
    }

從上述代碼可知,實際上就是將WebSecurityConfig類放入了WebSecurity類的一個LinkedHashMap中,該LinkedHashMap在WebSecurity中屬性名為configurers。

我們繼續回到WebSecurityConfiguration類,查看它的另外一個重要的方法:

 @Bean(
        name = {"springSecurityFilterChain"}
    )
    public Filter springSecurityFilterChain() throws Exception {
        boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
        if (!hasConfigurers) {
            WebSecurityConfigurerAdapter adapter = (WebSecurityConfigurerAdapter)this.objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() {
            });
            this.webSecurity.apply(adapter);
        }

        return (Filter)this.webSecurity.build();
    }

該方法即為Spring Security構建Filter的核心方法,通過webSecurity的build方法構建了Spring Security的Filter。
我們繼續查看WebSecurity類的build方法:

 public final O build() throws Exception {
        if (this.building.compareAndSet(false, true)) {
            this.object = this.doBuild();
            return this.object;
        } else {
            throw new AlreadyBuiltException("This object has already been built");
        }
    }
    
    

實際上調用了上層的doBuild:

protected final O doBuild() throws Exception {
        LinkedHashMap var1 = this.configurers;
        synchronized(this.configurers) {
            this.buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING;
            this.beforeInit();
            this.init();
            this.buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING;
            this.beforeConfigure();
            this.configure();
            this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;
            O result = this.performBuild();
            this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT;
            return result;
        }
    }

這里主要看WebSecurity的init方法和performBuild方法,首先看init方法

 private void init() throws Exception {
        // this.getConfigurers()該方法實際上獲取WebSecurity中LinkedHashMap中的Value值集合
        Collection> configurers = this.getConfigurers();
        Iterator var2 = configurers.iterator();

        SecurityConfigurer configurer;
        while(var2.hasNext()) {
            configurer = (SecurityConfigurer)var2.next();
            // 調用SecurityConfigurer的init方法
            configurer.init(this);
        }

        var2 = this.configurersAddedInInitializing.iterator();

        while(var2.hasNext()) {
            configurer = (SecurityConfigurer)var2.next();
            configurer.init(this);
        }

    }

通過該代碼可知,該方法首先獲取WebSecurity中的LinkedHashMap中的Value值集合,再對Value值進行遍歷并執行其中的init方法,從上面的代碼分析我們知道WebSecurity中的LinkedHashMap實際存的就是WebSecurityConfig,這段代碼將會調用WebSecurityConfig的init方法,而WebSecurityConfig的init方法來自于它的父類WebSecurityConfigurerAdapter,該init方法代碼如下:

public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer {
        public void init(final WebSecurity web) throws Exception {
        // 獲取HttpSecurity
        final HttpSecurity http = this.getHttp();
        // 將HttpSecurity放入WebSecurity中
        web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
            public void run() {
                FilterSecurityInterceptor securityInterceptor =
                 (FilterSecurityInterceptor)http.getSharedObject(FilterSecurityInterceptor.class);
                web.securityInterceptor(securityInterceptor);
            }
        });
       }
       
       protected final HttpSecurity getHttp() throws Exception {
        if (this.http != null) {
            return this.http;
        } else {
            DefaultAuthenticationEventPublisher eventPublisher = (DefaultAuthenticationEventPublisher)this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
            this.localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
            AuthenticationManager authenticationManager = this.authenticationManager();
            this.authenticationBuilder.parentAuthenticationManager(authenticationManager);
            this.authenticationBuilder.authenticationEventPublisher(eventPublisher);
            Map, Object> sharedObjects = this.createSharedObjects();
            this.http = new HttpSecurity(this.objectPostProcessor, this.authenticationBuilder, sharedObjects);
            if (!this.disableDefaults) {
                ((HttpSecurity)((DefaultLoginPageConfigurer)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)this.http.csrf().and()).addFilter(new WebAsyncManagerIntegrationFilter()).exceptionHandling().and()).headers().and()).sessionManagement().and()).securityContext().and()).requestCache().and()).anonymous().and()).servletApi().and()).apply(new DefaultLoginPageConfigurer())).and()).logout();
                ClassLoader classLoader = this.context.getClassLoader();
                List defaultHttpConfigurers = SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
                Iterator var6 = defaultHttpConfigurers.iterator();

                while(var6.hasNext()) {
                    AbstractHttpConfigurer configurer = (AbstractHttpConfigurer)var6.next();
                    this.http.apply(configurer);
                }
            }
            // 調用本類的configure方法
            this.configure(this.http);
            return this.http;
        }
    }
    
    // 模板方法設計模式,子類WebSecurityConfig將會覆蓋該方法
    protected void configure(HttpSecurity http) throws Exception {
        this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
        ((HttpSecurity)((HttpSecurity)((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated().and()).formLogin().and()).httpBasic();
    }
       
}

以上代碼最終還是實際調用了我們寫的WebSecurityConfig類的configure方法。

仔細觀察以上代碼,我們發現有一條語句web.addSecurityFilterChainBuilder(http),該語句將構建的HttpSecurity放入WebSecurity類中,以下是該方法源碼:

public WebSecurity addSecurityFilterChainBuilder(SecurityBuilder securityFilterChainBuilder) {
        this.securityFilterChainBuilders.add(securityFilterChainBuilder);
        return this;
    }

實際上就是將HttpSecurity放入了WebSecurity的一個list集合里,該list集合屬性名為securityFilterChainBuilders。

到目前為止,我們終于知道我們編寫的WebSecurityConfig類的configure方法是如何被調用的了,但是仍有許多疑問沒解開,比如HttpSecurity類的作用,Spring Security是如何通過HttpSecurity類構建一條攔截器鏈等。

這里我們先不分析HttpSecurity類的具體實現,再來看看WebSecurity的init方法執行后所執行的performBuild方法,該方法源碼如下:

protected Filter performBuild() throws Exception {
        Assert.state(!this.securityFilterChainBuilders.isEmpty(), () -> {
            return "At least one SecurityBuilder needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke " + WebSecurity.class.getSimpleName() + ".addSecurityFilterChainBuilder directly";
        });
        int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
        List securityFilterChains = new ArrayList(chainSize);
        Iterator var3 = this.ignoredRequests.iterator();

        while(var3.hasNext()) {
            RequestMatcher ignoredRequest = (RequestMatcher)var3.next();
            securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest, new Filter[0]));
        }
        
        / 遍歷securityFilterChainBuilders集合
        var3 = this.securityFilterChainBuilders.iterator();

        while(var3.hasNext()) {
            SecurityBuilder securityFilterChainBuilder = (SecurityBuilder)var3.next();
            // 執行securityFilterChainBuilders集合單位的build方法,返回一個SecurityFilterChain類,并加入List中
            securityFilterChains.add(securityFilterChainBuilder.build());
        }
        
        // 將List類構建成一個FilterChainProxy代理類
        FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
        if (this.httpFirewall != null) {
            filterChainProxy.setFirewall(this.httpFirewall);
        }

        filterChainProxy.afterPropertiesSet();
        Filter result = filterChainProxy;
        if (this.debugEnabled) {
            this.logger.warn("

********************************************************************
**********        Security debugging is enabled.       *************
**********    This may include sensitive information.  *************
**********      Do not use in a production system!     *************
********************************************************************

");
            result = new DebugFilter(filterChainProxy);
        }

        this.postBuildAction.run();
        // 返回FilterChainProxy代理類
        return (Filter)result;
    }

該方法執行的操作主要如下:

遍歷securityFilterChainBuilders集合,并執行其中的build方法,從上面代碼分析可知,securityFilterChainBuilders集合里存儲了HttpSecurity,所以這里執行了HttpSecurity的build方法構建SecurityFilterChain類

將List集合構建成一個FilterChainProxy代理類

返回這個FilterChainProxy代理類

到這里總的過程就非常明了了,實際上Spring Security的頂層filter就是一個FilterChainProxy類,而HttpSecurity主要用于注冊和實例化各種filter

到這里就分成了兩路,一路是HttpSecurity的build方法構建SecurityFilterChain類的原理,一路是FilterChainProxy類的作用,我們先從FilterChainProxy類開始

FilterChainProxy類

當請求到達的時候,FilterChainProxy會調用dofilter()方法,會遍歷所有的SecurityFilterChain,對匹配到的url,則一一調用SecurityFilterChain中的filter做認證授權。FilterChainProxy的dofilter()中調用了doFilterInternal()方法,如下:

private void doFilterInternal(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

    FirewalledRequest fwRequest = firewall
            .getFirewalledRequest((HttpServletRequest) request);
    HttpServletResponse fwResponse = firewall
            .getFirewalledResponse((HttpServletResponse) response);
    // 獲取請求對應的filter列表
    List filters = getFilters(fwRequest);

    if (filters == null || filters.size() == 0) {
        if (logger.isDebugEnabled()) {
            logger.debug(UrlUtils.buildRequestUrl(fwRequest)
                    + (filters == null ? " has no matching filters"
                            : " has an empty filter list"));
        }

        fwRequest.reset();

        chain.doFilter(fwRequest, fwResponse);

        return;
    }

    VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
    // 執行每個filter
    vfc.doFilter(fwRequest, fwResponse);
}

// 通過遍歷filterChains,調用SecurityFilterChain的matches方法,判斷當前的請求對應哪些filter,返回匹配的filter列表
private List getFilters(HttpServletRequest request) {
    for (SecurityFilterChain chain : filterChains) {
        if (chain.matches(request)) {
            return chain.getFilters();
        }
    }

    return null;
}

我們理清了FilterChainProxy類的作用,那么這些SecurityFilterChain是從哪來的呢?從上節可知SecurityFilterChain是由HttpSecurity的build方法生成的,下面我們分析下HttpSecurity類

HttpSecurity

HttpSecurity與WebSecurity一樣,都繼承了AbstractConfiguredSecurityBuilder類,而WebSecurity的build和doBuild方法和LinkedHashMap屬性,均來自AbstractConfiguredSecurityBuilder,故HttpSecurity的build方法代碼與WebSecurity的相同,區別在于LinkedHashMap存儲的東西不同,HttpSecurity正是通過如此來生成SecurityFilterChain類的。

下面我們來看HttpSecurity構建filter的幾個常見方法:

public ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry authorizeRequests() throws Exception {
        ApplicationContext context = this.getContext();
        return ((ExpressionUrlAuthorizationConfigurer)this.getOrApply(new ExpressionUrlAuthorizationConfigurer(context))).getRegistry();
    }


public FormLoginConfigurer formLogin() throws Exception {
        return (FormLoginConfigurer)this.getOrApply(new FormLoginConfigurer());
    }

都調用了getOrApply方法,再來看getOrApply方法,又調用了其中的apply方法

private > C getOrApply(C configurer) throws Exception {
        C existingConfig = (SecurityConfigurerAdapter)this.getConfigurer(configurer.getClass());
        return existingConfig != null ? existingConfig : this.apply(configurer);
    }
    
public > C apply(C configurer) throws Exception {
        configurer.addObjectPostProcessor(this.objectPostProcessor);
        configurer.setBuilder(this);
        this.add(configurer);
        return configurer;
    }

apply方法又調用了add方法,這里的add方法最終還是將該configurer加入了linkedHashMap中

private > void add(C configurer) throws Exception {
        Assert.notNull(configurer, "configurer cannot be null");
        Class> clazz = configurer.getClass();
        LinkedHashMap var3 = this.configurers;
        synchronized(this.configurers) {
            if (this.buildState.isConfigured()) {
                throw new IllegalStateException("Cannot apply " + configurer + " to already built object");
            } else {
                List> configs = this.allowConfigurersOfSameType ? (List)this.configurers.get(clazz) : null;
                if (configs == null) {
                    configs = new ArrayList(1);
                }

                ((List)configs).add(configurer);
                this.configurers.put(clazz, configs);
                if (this.buildState.isInitializing()) {
                    this.configurersAddedInInitializing.add(configurer);
                }

            }
        }
    }

故HttpSecurity在構建filter的過程中,本質還是將形如ExpressionUrlAuthorizationConfigurer、FormLoginConfigurer等類加入了它的LinkedHashMap中。

那么將這些Configurer類存入LinkedHashMap的作用又是什么?我們回憶上面WebSecurity類的doBuild方法,我們知道HttpSecurity類調用的doBuild方法與WebSecurity類一樣,而通過觀察WebSecurity類doBuild方法里this.init();this.configure();這些語句的具體實現,實際就是調用其LinkedHashMap中的元素的init方法和configure方法。

我們現在來查看其中一個ExpressionUrlAuthorizationConfigurer類的configure方法的詳細代碼:

public void configure(H http) throws Exception {
        FilterInvocationSecurityMetadataSource metadataSource = this.createMetadataSource(http);
        if (metadataSource != null) {
            FilterSecurityInterceptor securityInterceptor = this.createFilterSecurityInterceptor(http, metadataSource, (AuthenticationManager)http.getSharedObject(AuthenticationManager.class));
            if (this.filterSecurityInterceptorOncePerRequest != null) {
                securityInterceptor.setObserveOncePerRequest(this.filterSecurityInterceptorOncePerRequest);
            }

            securityInterceptor = (FilterSecurityInterceptor)this.postProcess(securityInterceptor);
            // 將Filter加入了HttpSecurity的Filters集合中
            http.addFilter(securityInterceptor);
            http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);
        }
    }

最后來看看HttpSecruity的performBuild()方法:

 protected DefaultSecurityFilterChain performBuild() throws Exception {
        Collections.sort(this.filters, this.comparator);
        return new DefaultSecurityFilterChain(this.requestMatcher, this.filters);
    }

實際上就是通過Filters集合構建了SecurityFilterChain。

從上面代碼可總結出,HttpSecurity內部維護一個Filter列表,而HttpSecurity調用形如authorizeRequests(),formLogin()等方法實際上就是將filter添加入它的列表當中,最后通過performBuild()方法構建出SecurityFilterChain,至此HttpSecurity構建filter的總過程就完成了。

總結

到目前為止,我們終于知道Spring Security是如何一步步的構建和初始化filter的了,我們最后再來簡單總結下構建過程:

Spring Security啟動過程中通過WebSecurityConfiguration實例化WebSecurity

WebSecurityConfiguration會將使用者編寫的WebSecurityConfig類放入WebSecurity中的LinkedHashMap中

在構建WebSecurity的時候,會調用WebSecurity的doBuild()方法,這個方法是一個核心方法。

doBuild中的init方法將會調用LinkedHashMap中元素的init方法(這里的元素是WebSecurityConfig),然后WebSecurityConfig的init方法會調用configure方法,調用configure方法后,將會初始化HttpSecurity構建各種Filter,這時HttpSecurity將會加入WebSecurity中。

doBuild中的init方法調用完后將會調用下一個performBuild()方法,該方法會獲取到HttpSecurity調用其doBuild方法構造SecurityFilterChain

將獲取到的SecurityFilterChain構建成一個FilterChainProxy類,作為Spring Security的頂層filter

至此Spring Security的Filter構建完成

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77713.html

相關文章

  • Spring Security 始化流程詳解

    摘要:構建完實例后,將它設置為的父認證管理器并將該傳入構造器構建實例。,目前為止已經被初始化,接下去需要設置對象添加至的列表中打開類結構,和一樣,它也實現了接口,同樣繼承自。最后返回的是的默認實現。 最近在整合微服務OAuth 2認證過程中,它是基于Spring Security之上,而本人對Spring Security架構原理并不太熟悉,導致很多配置搞不太清楚,遂咬牙啃完了Spring ...

    tommego 評論0 收藏0
  • spring security ajax登錄及返回

    摘要:返回總共需要處理個地方,一個是異常的處理,需要兼容請求,一個是成功返回的處理,一個是失敗返回的處理。這里就是攔截,獲取提交的參數,然后交給去認證。之后就是走后續的,如果成功,則會進行相應的配置。動態配置權限筆記自定義 序 本文講述一下如何自定義spring security的登錄頁,網上給的資料大多過時,而且是基于后端模板技術的,講的不是太清晰,本文給出一個采用ajax的登錄及返回的前...

    ideaa 評論0 收藏0
  • 關于web.xml配置那些事兒

    摘要:的版本增加了對事件監聽程序的支持,事件監聽程序在建立修改和刪除會話或環境時得到通知。元素指出事件監聽程序類。過濾器配置將一個名字與一個實現接口的類相關聯。 1.簡介 web.xml文件是Java web項目中的一個配置文件,主要用于配置歡迎頁、Filter、Listener、Servlet等,但并不是必須的,一個java web項目沒有web.xml文件照樣能跑起來。Tomcat容器/...

    zhichangterry 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<