SpringBoot實現(xiàn)嵌入式 Servlet容器
一、如何定制和修改Servlet容器的相關(guān)配置
前言: SpringBoot
在Web
環(huán)境下,默認使用的是Tomact
作為嵌入式的Servlet
容器;
【1】修改和server
相關(guān)的配置(ServerProperties
實現(xiàn)了EmbeddedServletContainerCustomizer
)例如:修改端口號
#通用的Servlet容器設(shè)置:修改端口號 server: port: 8081 tomcat: #設(shè)置Tomact的相關(guān)屬性,例如編碼格式 uri-encoding: utf-8
? 我們也可以進入port
所屬的對象中,發(fā)現(xiàn)其他可修改的參數(shù)等等,如下:
@ConfigurationProperties( prefix = "server", ignoreUnknownFields = true ) public class ServerProperties implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered { private Integer port; private InetAddress address; private String contextPath; private String displayName = "application"; ......
【2】編寫一個EmbeddedServletContainerCustomizer
:嵌入式的 Servlet容器的定制器,來修改 Servlet容器的配置。其實1中的 ServerProperties也是實現(xiàn)了 EmbeddedServletContainerCustomizer。xxxCustomizer 幫組我們進行定制配置。
@Configuration public class MyMvcConfig extends WebMvcConfigurerAdapter { @Bean public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){ return new EmbeddedServletContainerCustomizer() { @Override public void customize(ConfigurableEmbeddedServletContainer container) { container.setPort(8082); } }; }
二、注冊Servlet三大組件【Servlet、Filter、Listener】
由于SpringBoot
默認是以 jar包的方式啟動嵌入的Servlet
容器來啟動SpringBoot
的web
應(yīng)用,沒有web.xml
文件。注冊三大組件的方式如下:
【1】通過 ServletRegistrationBean
注冊自定義的Servlet
。
//首先創(chuàng)建一個Servlet public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("Hello MyServlet"); super.doPost(req, resp); } } //將創(chuàng)建的Servlet通過配置類注入到容器中,兩個是不同的類。 @Configuration public class MyMvcConfig extends WebMvcConfigurerAdapter { @Bean public ServletRegistrationBean myServlet(){ ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(), "/myServlet"); return registrationBean; }
【2】通過 FilterRegistrationBean 注冊攔截器 Filter。
//自定義一個filter實現(xiàn)servlet.Filter接口 public class myFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.printf("myFilter"); filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { } } //通過配置類注入自定義的Filter @Configuration public class MyMvcConfig extends WebMvcConfigurerAdapter { @Bean public FilterRegistrationBean myFilter(){ FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(new myFilter()); registrationBean.setUrlPatterns(Arrays.asList("/hello","/myFilter")); return registrationBean; } 【3】通過`ServletListenerRegistrationBean`注冊自定義的`Listener`。 ```java //創(chuàng)建自定義的Listener監(jiān)聽 public class myListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent servletContextEvent) { System.out.printf("服務(wù)啟動"); } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { System.out.printf("服務(wù)銷毀"); } } //通過配置類注入自定義的listener @Configuration public class MyMvcConfig extends WebMvcConfigurerAdapter { public ServletListenerRegistrationBean myListener(){ ServletListenerRegistrationBean<MyListener> servletListenerRegistrationBean = new ServletListenerRegistrationBean<>(new MyListener()); return servletListenerRegistrationBean; }
三、使用其他 Servlet容器:Jetty(長連接引用)、Undertow(不支持JSP)
【1】我們在定制嵌入式的Servlet
容器時,會傳入ConfigurableEmbeddedServletContainer
類,我們通過Ctrl+T
查看此可配置嵌入式類容器中可以配置Tomcat
、Jetty
和Undertow
。
//ConfigurableEmbeddedServletContainer @Bean public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){ return new EmbeddedServletContainerCustomizer() { @Override public void customize(ConfigurableEmbeddedServletContainer container) { container.setPort(8082); } }; }
【2】默認使用Tomcat
,因為starter-web
引入的是Tomcat
的starter
。我們排除Tomcat
的依賴,引入Jetty
的依賴即可。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <artifactId>spring-boot-starter-tomcat</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions> </dependency> <dependency> <artifactId>spring-boot-starter-Jetty</artifactId> <groupId>org.springframework.boot</groupId> </dependency>
四、嵌入式 Servlet容器自動配置原理
【1】EmbeddedServletContainerAutoConfiguration
類主要用來自動配置嵌入式的Servlet
容器。
@AutoConfigureOrder(-2147483648) @Configuration @ConditionalOnWebApplication //導(dǎo)入BeanPostProcessorsRegistrar:后置處理器:在bean初始化前后,執(zhí)行(剛創(chuàng)建完對象,還沒屬性賦值)初始化工作. //給容器中導(dǎo)入一些組件,導(dǎo)入了embeddedServletContainerCustomizerBeanPostProcessor @Import({EmbeddedServletContainerAutoConfiguration.BeanPostProcessorsRegistrar.class}) public class EmbeddedServletContainerAutoConfiguration { @Configuration @ConditionalOnClass({Servlet.class, Tomcat.class})//判斷當(dāng)前Servlet中是否引入的Tomcat依賴 @ConditionalOnMissingBean( value = {EmbeddedServletContainerFactory.class}, search = SearchStrategy.CURRENT )//判斷當(dāng)前容器中,沒有用戶自定義的EmbeddedServletContainerFactory嵌入式的Servlet容器工廠, //作用:創(chuàng)建嵌入式的servlet容器。 public static class EmbeddedTomcat { public EmbeddedTomcat() { } @Bean public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() { return new TomcatEmbeddedServletContainerFactory(); } } @Configuration @ConditionalOnClass({Servlet.class, Undertow.class, SslClientAuthMode.class}) @ConditionalOnMissingBean( value = {EmbeddedServletContainerFactory.class}, search = SearchStrategy.CURRENT ) public static class EmbeddedUndertow { public EmbeddedUndertow() { } @Bean public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() { return new UndertowEmbeddedServletContainerFactory(); } } @Configuration @ConditionalOnClass({Servlet.class, Server.class, Loader.class, WebAppContext.class}) @ConditionalOnMissingBean( value = {EmbeddedServletContainerFactory.class}, search = SearchStrategy.CURRENT ) public static class EmbeddedJetty { public EmbeddedJetty() { } @Bean public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() { return new JettyEmbeddedServletContainerFactory(); } } }
【2】嵌入式的容器工廠:EmbeddedServletContainerFactory
,用來創(chuàng)建嵌入式的Servlet
容器。
public interface EmbeddedServletContainerFactory { //獲取嵌入式的Servlet容器 EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... var1); }
? SpringBoot 再帶了三種嵌入式的容器工廠,如下:
【3】EmbeddedServletContainer:嵌入式的容器,SpringBoot 為我們提供了三種不同的嵌入式容器,與工廠相互對應(yīng),如下:
【4】我們進入工廠類 TomcatEmbeddedServletContainerFactory發(fā)現(xiàn),其實也是創(chuàng)建一個 Tomcat并配置其基本屬性。
public EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers) { //創(chuàng)建Tomcat Tomcat tomcat = new Tomcat(); //配置Tomcat的基本環(huán)境 File baseDir = this.baseDirectory != null?this.baseDirectory:this.createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); this.customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); this.configureEngine(tomcat.getEngine()); Iterator var5 = this.additionalTomcatConnectors.iterator(); while(var5.hasNext()) { Connector additionalConnector = (Connector)var5.next(); tomcat.getService().addConnector(additionalConnector); } this.prepareContext(tomcat.getHost(), initializers); //將配置好的Tomcat傳入,并啟動Tomcat,Tomcat.start() return this.getTomcatEmbeddedServletContainer(tomcat); }
【5】用戶自定義的Servlet
容器配置類和SpringBoot
默認的ServerProperties
配置類,都實現(xiàn)了EmbeddedServletContainerCustomizer
接口。到底是怎么實現(xiàn)的哪?其實是SpringBoot
自動配置類中引入了后置處理器,如下:
//與用戶自定義的Servlet容器實現(xiàn)的接口名很類似,有一定的命名規(guī)則。 embeddedServletContainerCustomizerBeanPostProcessor
? 進入后置處理器類中,重點看如下代碼:
//初始化之前執(zhí)行 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { //如果當(dāng)前初始化的是當(dāng)前ConfigurableEmbeddedServletContainer類型的組件 if(bean instanceof ConfigurableEmbeddedServletContainer) { this.postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer)bean); } return bean; } //上面的postProcessBeforeInitialization方法: private void postProcessBeforeInitialization(ConfigurableEmbeddedServletContainer bean) { Iterator var2 = this.getCustomizers().iterator(); while(var2.hasNext()) { //獲取所有的定制器,調(diào)用每一個定制器的customize方法來給servlet屬性賦值。 EmbeddedServletContainerCustomizer customizer = (EmbeddedServletContainerCustomizer)var2.next(); customizer.customize(bean); } private Collection<EmbeddedServletContainerCustomizer> getCustomizers() { if(this.customizers == null) { //this.beanFactory.xx表示從容器中獲取XXCustomizer自定義類型的組件 this.customizers = new ArrayList(this.beanFactory.getBeansOfType(EmbeddedServletContainerCustomizer.class, false, false).values()); Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE); this.customizers = Collections.unmodifiableList(this.customizers); } return this.customizers; }
整理下步驟:
【1】SpringBoot
根據(jù)pom.xml
中導(dǎo)入的依賴,給容器中添加其對應(yīng)的嵌入式的服務(wù)容器工廠類,例如默認的Tomcat
工廠:EmbeddedServletContainerFactory
【TomcatEmbeddedServletContainerFactory】
【2】給容器中某個組件要創(chuàng)建對象就會觸發(fā)后置處理器EmbeddedServletContainerCustomizerBeanPostProcessor
,只要是嵌入式的Servlet
容器工廠,后置處理器就會工作(默認的ServerProperties
也是實現(xiàn)了此類接口的,所以肯定存在相關(guān)配置類)
【3】后置處理器從容器中獲取所有的EmbeddedServletContainerCustomizer
,調(diào)用定制器的定制方法。
五、嵌入式Servlet容器啟動原理
根據(jù)上述的流程,我們要研究Servlet容器的啟動原理。其實就是研究什么時候創(chuàng)建嵌入式的容器工廠和何時獲取嵌入式的容器并啟動Tomcat。獲取嵌入式的Servlet容器工廠的過程(在new TomcatEmbeddedServletContainerFactory()時打一個斷電,查看過程):
【1】SpringBoot 應(yīng)用啟動運行 run() 方法。
【2】this.refreshContext(context) 方法:用來初始化 IOC容器,既創(chuàng)建 IOC容器對象并初始化IOC容器中的每一個組件。
protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if(contextClass == null) { try { //判斷是不是web環(huán)境,是Web環(huán)境引入AnnotationConfigEmbeddedWebApplicationContext,否則引入AnnotationConfigApplicationContext contextClass = Class.forName(this.webEnvironment ?"org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext" :"org.springframework.context.annotation.AnnotationConfigApplicationContext"); } catch (ClassNotFoundException var3) { throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3); } } return (ConfigurableApplicationContext)BeanUtils.instantiate(contextClass); }
【3】this.refresh(context)
:刷新剛才創(chuàng)建好的IOC
容器。
【4】this.onRefresh()
:web
的IoC
容器重寫了onRefresh()
方法。
protected void onRefresh() { super.onRefresh(); try { //重點是創(chuàng)建了嵌入式的Servlet容器 this.createEmbeddedServletContainer(); } catch (Throwable var2) { throw new ApplicationContextException("Unable to start embedded container", var2); } }
【5】this.createEmbeddedServletContainer()
:web
的IOC
容器會創(chuàng)建嵌入式的Servlet
容器。
private void createEmbeddedServletContainer() { EmbeddedServletContainer localContainer = this.embeddedServletContainer; ServletContext localServletContext = this.getServletContext(); if(localContainer == null && localServletContext == null) { // 1、獲取嵌入式的Servlet嵌入式的工廠 EmbeddedServletContainerFactory containerFactory = this.getEmbeddedServletContainerFactory(); this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer( new ServletContextInitializer[]{this.getSelfInitializer()}); } }
【6】獲取嵌入式工廠后,便可從容器中獲取EmbeddedServletContainerFactory
的組件tomcatEmbeddedServletContainerFactory
來創(chuàng)建Tomcat
對象,后置處理器就會觸發(fā)獲取所有的定制器來確定Servlet
容器的相關(guān)配置。
【7】通過嵌入式工廠獲取嵌入式容器,如下:
this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer( new ServletContextInitializer[]{this.getSelfInitializer()});
● 嵌入式的Servlet容器創(chuàng)建并啟動對象:
public EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers) { //創(chuàng)建對象 Tomcat tomcat = new Tomcat(); //啟動對象 this.tomcat.start();
● 先啟動嵌入式的Servlet容器,再將IOC容器中剩下沒有創(chuàng)建的對象進行初始化,如下:
this.onRefresh(); //啟動完嵌入式容器后,后續(xù)還有其他對象的初始化工作 this.registerListeners(); this.finishBeanFactoryInitialization(beanFactory); this.finishRefresh(); } catch (BeansException var9) {
六、使用外置的Servlet容器
嵌入式Servlet容器的缺點: 默認不支持JSP
、優(yōu)化和定制比較復(fù)雜。外置Servlet
容器:安裝外部的Tomcat
,步驟如下:
1)、必須創(chuàng)建一個war
項目,需要手動創(chuàng)建目錄(利用Idea
快速創(chuàng)建)如下:
2)、將嵌入式的Tomcat指定為provide(Idea創(chuàng)建完后,會自動幫我們完成,但我們需要了解)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency>
3)、需要編寫一個SpringBootServletInitializer
的子類,并調(diào)用configure
方法:
public class ServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(SpringBootWebApplication.class); } }
4)、配置本地的Tomcat,并啟動Tomcat即可。(此項目運行run()方法是不能啟動項目的):需要設(shè)置名稱和本地Tomcat的路徑即可使用外部Servlet。
七、外置服務(wù)器的使用原理
? jar包:執(zhí)行SpringBoot主類的main方法,啟動并初始化IOC容器且創(chuàng)建嵌入式的Servlet容器。
? war包:啟動服務(wù)器后調(diào)用SpringBootServletInitializer中的configure()方法,加載我們的SpringBoot應(yīng)用并啟動。
Servlet3.0規(guī)則:
1)、服務(wù)器啟動后,會創(chuàng)建當(dāng)前web應(yīng)用中包含的每個jar內(nèi)的ServletContainerInitializer實例。
2)、ServletContainerInitializer的實現(xiàn)放在jar包的META-INF/services文件夾下(javax.servlet.ServletContainerInitializer:內(nèi)容就是ServletContainerInitializer的全類名)
3)、可以使用@handlesTypes注解,在應(yīng)用啟動時加載我們需要的類。
流程: 1)、啟動Tomcat后,獲取servlet.ServletContainerInitializer文件如下:其中的內(nèi)容同下:
#文件中的內(nèi)容 org.springframework.web.SpringServletContainerInitializer
2)、進入SpringServletContainerInitializer
發(fā)現(xiàn)此類將@HandlesTypes({WebApplicationInitializer.class})
標(biāo)注的所有這個類型的類都傳入到onStartup方法中的Set<Class<?>>
,并為這些類創(chuàng)建實例。
@HandlesTypes({WebApplicationInitializer.class}) public class SpringServletContainerInitializer implements ServletContainerInitializer { //onStartup方法,用來實例化感興趣的對象 public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { if(webAppInitializerClasses != null) { var4 = webAppInitializerClasses.iterator(); while(var4.hasNext()) { Class<?> waiClass = (Class)var4.next(); if(!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { //實例化 initializers.add((WebApplicationInitializer)waiClass.newInstance()); } catch (Throwable var7) { throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7); } } } }
3)、每一個WebApplicationInitializer
都調(diào)用自己的onStartup()方法。
while(var4.hasNext()) { WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next(); //onStartup()方法 initializer.onStartup(servletContext); }
4)、WebApplicationInitializer
只是一個接口,其實現(xiàn)類主要有以下三個:SpringBootServletInitalizer
正是SpringBoot
給我們創(chuàng)建好的啟動類,會被創(chuàng)建對象,并啟動自身的onStartup()方法。
5)、執(zhí)行onStartup()
方法時,會調(diào)用createRootApplicationContext()
方法來創(chuàng)建容器
public void onStartup(ServletContext servletContext) throws ServletException { this.logger = LogFactory.getLog(this.getClass()); //創(chuàng)建容器 WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext); if(rootAppContext != null) { servletContext.addListener(new ContextLoaderListener(rootAppContext) { public void contextInitialized(ServletContextEvent event) { } }); //容器的具體調(diào)用實現(xiàn) protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) { //創(chuàng)建Spring應(yīng)用的構(gòu)建器 SpringApplicationBuilder builder = this.createSpringApplicationBuilder(); //設(shè)置主類 builder.main(this.getClass()); //創(chuàng)建一些環(huán)境 ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext); if(parent != null) { this.logger.info("Root context already created (using as parent)."); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null); builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)}); } builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)}); builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class); //重要:子類中重寫了此方法,子類出入了應(yīng)用的主程序類 builder = this.configure(builder); builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext, null)}); //使用build()創(chuàng)建一個Spring應(yīng)用 SpringApplication application = builder.build(); if(application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) { application.addPrimarySources(Collections.singleton(this.getClass())); } Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation"); if(this.registerErrorPageFilter) { application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class)); } //啟動應(yīng)用 return this.run(application); }
6)、執(zhí)行應(yīng)用的run()
方法,來啟動Spring
應(yīng)用并創(chuàng)建IOC容器。
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList(); this.configureHeadlessProperty(); SpringApplicationRunListeners listeners = this.getRunListeners(args); listeners.starting(); Collection exceptionReporters; try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); this.configureIgnoreBeanInfo(environment); Banner printedBanner = this.printBanner(environment); context = this.createApplicationContext(); exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, new Object[]{context}); this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); //刷新IOC容器 this.refreshContext(context); this.afterRefresh(context, applicationArguments); stopWatch.stop(); if(this.logStartupInfo) { (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); } listeners.started(context); this.callRunners(context, applicationArguments); } catch (Throwable var10) { this.handleRunFailure(context, var10, exceptionReporters, listeners); throw new IllegalStateException(var10); } try { listeners.running(context); return context; } catch (Throwable var9) { this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null); throw new IllegalStateException(var9); } }
到此這篇關(guān)于SpringBoot實現(xiàn)嵌入式 Servlet容器的文章就介紹到這了,更多相關(guān)SpringBoot 嵌入式 Servlet容器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java用POI解析excel并獲取所有單元格數(shù)據(jù)的實例
下面小編就為大家?guī)硪黄狫ava用POI解析excel并獲取所有單元格數(shù)據(jù)的實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10JAVA面試題之Forward與Redirect的區(qū)別詳解
這篇文章主要給大家介紹了在JAVA面試中可能遇到會遇到的一道題,就是java中Forward與Redirect兩者之前的區(qū)別,文中介紹的非常詳細,對大家具有一定參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。2017-05-05Spring Cloud Hystrix線程池不足的解決方法
這篇文章主要介紹了Spring Cloud Hystrix線程池不足的解決方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-022種Java刪除ArrayList中的重復(fù)元素的方法
這篇文章主要介紹了2種Java刪除ArrayList中的重復(fù)元素的方法,感興趣的朋友可以參考下2015-08-08