您的位置:用让网 > 生活 > >正文

​generic(巧用GenericObjectPool创建自定义对象池)

摘要generic(巧用GenericObjectPool创建自定义对象池) 作者:京东物流 高圆庆 1 前言 通常一个对象创建、销毁非常耗时的时候,我们不会频繁的创建和销毁它,而是考虑复用。复用对象的一种做...

generic(巧用GenericObjectPool创建自定义对象池)

作者:京东物流

高圆庆

1 前言

通常一个对象创建、销毁非常耗时的时候,我们不会频繁的创建和销毁它,而是考虑复用。复用对象的一种做法就是对象池,将创建好的对象放入池中维护起来,下次再用的时候直接拿池中已经创建好的对象继续用,这就是池化的思想。在java中,有很多池管理的概念,典型的如线程池,数据库连接池,socket连接池。本文章讲介绍apache提供的通用对象池框架GenericObjectPool,以及基于GenericObjectPool实现的sftp连接池在国际物流调度履约系统中的应用。

2 GenericObjectPool剖析

Apache Commons Pool是一个对象池的框架,他提供了一整套用于实现对象池化的API。它提供了三种对象池:GenericKeyedObjectPool,SoftReferenceObjectPool和GenericObjectPool,其中GenericObjectPool是我们最常用的对象池,内部实现也最复杂。GenericObjectPool的UML图如下所示:

2.1 核心接口ObjectPool

从图中可以看出,GenericObjectPool实现了ObjectPool接口,而ObjectPool就是对象池的核心接口,它定义了一个对象池应该实现的行为。

addObject方法:往池中添加一个对象

borrowObject方法:从池中借走到一个对象

returnObject方法:把对象归还给对象池

invalidateObject:验证对象的有效性

getNumIdle:返回对象池中有多少对象是空闲的,也就是能够被借走的对象的数量。

getNumActive:返回对象池中有对象对象是活跃的,也就是已经被借走的,在使用中的对象的数量。

clear:清理对象池。注意是清理不是清空,该方法要求的是,清理所有空闲对象,释放相关资源。

close:关闭对象池。这个方法可以达到清空的效果,清理所有对象以及相关资源。

2.2 对象工厂BasePooledObjectFactory

对象的创建需要通过对象工厂来创建,对象工厂需要实现BasePooledObjectFactory接口。ObjectPool接口中往池中添加一个对象,就需要使用对象工厂来创建一个对象。该接口说明如下:

publicinterfacePooledObjectFactory{/**
*创建一个可由池提供服务的实例,并将其封装在由池管理的PooledObject中。
*/
PooledObjectmakeObject()throwsException;/**
*销毁池不再需要的实例
*/
voiddestroyObject(PooledObjectp)throwsException;/**
*确保实例可以安全地由池返回
*/
booleanvalidateObject(PooledObjectp);/**
*重新初始化池返回的实例
*/
voidactivateObject(PooledObjectp)throwsException;/**
*取消初始化要返回到空闲对象池的实例
*/
voidpassivateObject(PooledObjectp)throwsException;
}

2.3 配置类GenericObjectPoolConfig

GenericObjectPoolConfig是封装GenericObject池配置的简单“结构”,此类不是线程安全的;它仅用于提供创建池时使用的属性。大多数情况,可以使用GenericObjectPoolConfig提供的默认参数就可以满足日常的需求,GenericObjectPoolConfig是一个抽象类,实际应用中需要新建配置类,然后继承它。

2.4 工作原理流程

构造方法
当我们执行构造方法时,主要工作就是创建了一个存储对象的LinkedList类型容器,也就是概念意义上的“池”

从对象池中获取对象
获取池中的对象是通过borrowObject()命令,源码比较复杂,简单而言就是去LinkedList中获取一个对象,如果不存在的话,要调用构造方法中第一个参数Factory工厂类的makeObject()方法去创建一个对象再获取,获取到对象后要调用validateObject方法判断该对象是否是可用的,如果是可用的才拿去使用。LinkedList容器减一

归还对象到线程池
简单而言就是先调用validateObject方法判断该对象是否是可用的,如果可用则归还到池中,LinkedList容器加一,如果是不可以的则则调用destroyObject方法进行销毁

上面三步就是最简单的流程,由于取和还的流程步骤都在borrowObject和returnObject方法中固定的,所以我们只要重写Factory工厂类的makeObject()和validateObject以及destroyObject方法即可实现最简单的池的管理控制,通过构造方法传入该Factory工厂类对象则可以创建最简单的对象池管理类。这算是比较好的解耦设计模式,借和还的流程如下图所示:

3 开源框架如何使用GenericObjectPool

redis的java客户端jedis就是基于Apache Commons Pool对象池的框架来实现的。

3.1 对象工厂类JedisFactory

对象工厂类只需实现activateObject、destroyObject、makeObject、validateObject方法即可,源码如下:

classJedisFactoryimplementsPooledObjectFactory{privatefinalStringhost;privatefinalintport;privatefinalinttimeout;privatefinalintnewTimeout;privatefinalStringpassword;privatefinalintdatabase;privatefinalStringclientName;publicJedisFactory(Stringhost,intport,inttimeout,Stringpassword,intdatabase){this(host,port,timeout,password,database,(String)null);
}publicJedisFactory(Stringhost,intport,inttimeout,Stringpassword,intdatabase,StringclientName){this(host,port,timeout,timeout,password,database,clientName);
}publicJedisFactory(Stringhost,intport,inttimeout,intnewTimeout,Stringpassword,intdatabase,StringclientName){this.host=host;this.port=port;this.timeout=timeout;this.newTimeout=newTimeout;this.password=password;this.database=database;this.clientName=clientName;
}publicvoidactivateObject(PooledObjectpooledJedis)throwsException{
BinaryJedisjedis=(BinaryJedis)pooledJedis.getObject();if(jedis.getDB()!=(long)this.database){
jedis.select(this.database);
}
}publicvoiddestroyObject(PooledObjectpooledJedis)throwsException{
BinaryJedisjedis=(BinaryJedis)pooledJedis.getObject();if(jedis.isConnected()){try{try{
jedis.quit();
}catch(Exceptionvar4){
}
jedis.disconnect();
}catch(Exceptionvar5){
}
}
}publicPooledObjectmakeObject()throwsException{
Jedisjedis=newJedis(this.host,this.port,this.timeout,this.newTimeout);
jedis.connect();if(null!=this.password){
jedis.auth(this.password);
}if(this.database!=0){
jedis.select(this.database);
}if(this.clientName!=null){
jedis.clientSetname(this.clientName);
}returnnewDefaultPooledObject(jedis);
}publicvoidpassivateObject(PooledObjectpooledJedis)throwsException{
}publicbooleanvalidateObject(PooledObjectpooledJedis){
BinaryJedisjedis=(BinaryJedis)pooledJedis.getObject();try{returnjedis.isConnected()&&jedis.ping().equals("PONG");
}catch(Exceptionvar4){returnfalse;
}
}
}

3.2 配置类JedisPoolConfig

publicclassJedisPoolConfigextendsGenericObjectPoolConfig{publicJedisPoolConfig(){this.setTestWhileIdle(true);this.setMinEvictableIdleTimeMillis(60000L);this.setTimeBetweenEvictionRunsMillis(30000L);this.setNumTestsPerEvictionRun(-1);
}
}

4 国际物流履约系统中的应用

在国际物流履约系统中,我们和客户交互文件经常使用sftp服务器,因为创建sftp服务器的连接比较耗时,所以基于Apache Commons Pool对象池的框架来实现的我们自己的sftp链接池。

4.1 sftp对象池

SftpPool比较简单,直接继承GenericObjectPool。

publicclassSftpPoolextendsGenericObjectPool{publicSftpPool(SftpFactoryfactory,SftpPoolConfigconfig,SftpAbandonedConfigabandonedConfig){super(factory,config,abandonedConfig);
}
}

4.2 对象工厂SftpFactory

这是基于Apache Commons Pool框架实现自定义对象池的核心类,代码如下:

publicclassSftpFactoryextendsBasePooledObjectFactory{privatestaticfinalStringCHANNEL_TYPE="sftp";privatestaticPropertiessshConfig=newProperties();privateStringhost;privateintport;privateStringusername;privateStringpassword;static{
sshConfig.put("StrictHostKeyChecking","no");
}@Override
publicSftpcreate(){try{
JSchjsch=newJSch();
SessionsshSession=jsch.getSession(username,host,port);
sshSession.setPassword(password);
sshSession.setConfig(sshConfig);
sshSession.connect();
ChannelSftpchannel=(ChannelSftp)sshSession.openChannel(CHANNEL_TYPE);
channel.connect();
log.info("sftpFactory创建sftp");returnnewSftp(channel);
}catch(JSchExceptione){
log.error("连接sftp失败:",e);thrownewBizException(ResultCodeEnum.SFTP_EXCEPTION);
}
}/**
*@paramsftp被包装的对象
*@return对象包装器
*/
@Override
publicPooledObjectwrap(Sftpsftp){returnnewDefaultPooledObject<>(sftp);
}/**
*销毁对象
*@paramp对象包装器
*/
@Override
publicvoiddestroyObject(PooledObjectp){
log.info("开始销毁channelSftp");if(p!=null){
Sftpsftp=p.getObject();if(sftp!=null){
ChannelSftpchannelSftp=sftp.getChannelSftp();if(channelSftp!=null){
channelSftp.disconnect();
log.info("销毁channelSftp成功");
}
}
}
}/**
*检查连接是否可用
*
*@paramp对象包装器
*@return{@codetrue}可用,{@codefalse}不可用
*/
@Override
publicbooleanvalidateObject(PooledObjectp){if(p!=null){
Sftpsftp=p.getObject();if(sftp!=null){try{
sftp.getChannelSftp().cd("./");
log.info("验证连接是否可用,结果为true");returntrue;
}catch(SftpExceptione){
log.info("验证连接是否可用,结果为false",e);returnfalse;
}
}
}
log.info("验证连接是否可用,结果为false");returnfalse;
}publicstaticclassBuilder{privateStringhost;privateintport;privateStringusername;privateStringpassword;publicSftpFactorybuild(){returnnewSftpFactory(host,port,username,password);
}publicBuilderhost(Stringhost){this.host=host;returnthis;
}publicBuilderport(intport){this.port=port;returnthis;
}publicBuilderusername(Stringusername){this.username=username;returnthis;
}publicBuilderpassword(Stringpassword){this.password=password;returnthis;
}
}
}

4.3 配置类SftpPoolConfig

配置类继承了GenericObjectPoolConfig,可继承该类的默认属性,也可自定义配置参数。

publicclassSftpPoolConfigextendsGenericObjectPoolConfig{publicstaticclassBuilder{privateintmaxTotal;privateintmaxIdle;privateintminIdle;privatebooleanlifo;privatebooleanfairness;privatelongmaxWaitMillis;privatelongminEvictableIdleTimeMillis;privatelongevictorShutdownTimeoutMillis;privatelongsoftMinEvictableIdleTimeMillis;privateintnumTestsPerEvictionRun;privateEvictionPolicyevictionPolicy;//仅2.6.0版本commons-pool2需要设置
privateStringevictionPolicyClassName;privatebooleantestOnCreate;privatebooleantestOnBorrow;privatebooleantestOnReturn;privatebooleantestWhileIdle;privatelongtimeBetweenEvictionRunsMillis;privatebooleanblockWhenExhausted;privatebooleanjmxEnabled;privateStringjmxNamePrefix;privateStringjmxNameBase;publicSftpPoolConfigbuild(){
SftpPoolConfigconfig=newSftpPoolConfig();
config.setMaxTotal(maxTotal);
config.setMaxIdle(maxIdle);
config.setMinIdle(minIdle);
config.setLifo(lifo);
config.setFairness(fairness);
config.setMaxWaitMillis(maxWaitMillis);
config.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
config.setEvictorShutdownTimeoutMillis(evictorShutdownTimeoutMillis);
config.setSoftMinEvictableIdleTimeMillis(softMinEvictableIdleTimeMillis);
config.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
config.setEvictionPolicy(evictionPolicy);
config.setEvictionPolicyClassName(evictionPolicyClassName);
config.setTestOnCreate(testOnCreate);
config.setTestOnBorrow(testOnBorrow);
config.setTestOnReturn(testOnReturn);
config.setTestWhileIdle(testWhileIdle);
config.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
config.setBlockWhenExhausted(blockWhenExhausted);
config.setJmxEnabled(jmxEnabled);
config.setJmxNamePrefix(jmxNamePrefix);
config.setJmxNameBase(jmxNameBase);returnconfig;
}
}

4.4 SftpClient配置类

读取配置文件,创建SftpFactory、SftpPoolConfig、SftpPool,代码如下:

@Configuration
@ConditionalOnClass(SftpPool.class)@EnableConfigurationProperties(SftpClientProperties.class)publicclassSftpClientAutoConfiguration{
@Bean
@ConditionalOnMissingBean
publicISftpClientsftpClient(SftpClientPropertiessftpClientProperties){if(sftpClientProperties.isMultiple()){
MultipleSftpClientmultipleSftpClient=newMultipleSftpClient();
sftpClientProperties.getClients().forEach((name,properties)->{
SftpFactorysftpFactory=createSftpFactory(properties);
SftpPoolConfigsftpPoolConfig=createSftpPoolConfig(properties);
SftpAbandonedConfigsftpAbandonedConfig=createSftpAbandonedConfig(properties);
SftpPoolsftpPool=newSftpPool(sftpFactory,sftpPoolConfig,sftpAbandonedConfig);
ISftpClientsftpClient=newSftpClient(sftpPool);
multipleSftpClient.put(name,sftpClient);
});returnmultipleSftpClient;
}
SftpFactorysftpFactory=createSftpFactory(sftpClientProperties);
SftpPoolConfigsftpPoolConfig=createSftpPoolConfig(sftpClientProperties);
SftpAbandonedConfigsftpAbandonedConfig=createSftpAbandonedConfig(sftpClientProperties);
SftpPoolsftpPool=newSftpPool(sftpFactory,sftpPoolConfig,sftpAbandonedConfig);returnnewSftpClient(sftpPool);
}
publicSftpFactorycreateSftpFactory(SftpClientPropertiesproperties){returnnewSftpFactory.Builder()
.host(properties.getHost())
.port(properties.getPort())
.username(properties.getUsername())
.password(properties.getPassword())
.build();
}
publicSftpPoolConfigcreateSftpPoolConfig(SftpClientPropertiesproperties){
SftpClientProperties.Poolpool=properties.getPool();returnnewSftpPoolConfig.Builder()
.maxTotal(pool.getMaxTotal())
.maxIdle(pool.getMaxIdle())
.minIdle(pool.getMinIdle())
.lifo(pool.isLifo())
.fairness(pool.isFairness())
.maxWaitMillis(pool.getMaxWaitMillis())
.minEvictableIdleTimeMillis(pool.getMinEvictableIdleTimeMillis())
.evictorShutdownTimeoutMillis(pool.getEvictorShutdownTimeoutMillis())
.softMinEvictableIdleTimeMillis(pool.getSoftMinEvictableIdleTimeMillis())
.numTestsPerEvictionRun(pool.getNumTestsPerEvictionRun())
.evictionPolicy(null)
.evictionPolicyClassName(DefaultEvictionPolicy.class.getName())
.testOnCreate(pool.isTestOnCreate())
.testOnBorrow(pool.isTestOnBorrow())
.testOnReturn(pool.isTestOnReturn())
.testWhileIdle(pool.isTestWhileIdle())
.timeBetweenEvictionRunsMillis(pool.getTimeBetweenEvictionRunsMillis())
.blockWhenExhausted(pool.isBlockWhenExhausted())
.jmxEnabled(pool.isJmxEnabled())
.jmxNamePrefix(pool.getJmxNamePrefix())
.jmxNameBase(pool.getJmxNameBase())
.build();
}
publicSftpAbandonedConfigcreateSftpAbandonedConfig(SftpClientPropertiesproperties){
SftpClientProperties.Abandonedabandoned=properties.getAbandoned();returnnewSftpAbandonedConfig.Builder()
.removeAbandonedOnBorrow(abandoned.isRemoveAbandonedOnBorrow())
.removeAbandonedOnMaintenance(abandoned.isRemoveAbandonedOnMaintenance())
.removeAbandonedTimeout(abandoned.getRemoveAbandonedTimeout())
.logAbandoned(abandoned.isLogAbandoned())
.requireFullStackTrace(abandoned.isRequireFullStackTrace())
.logWriter(newPrintWriter(System.out))
.useUsageTracking(abandoned.isUseUsageTracking())
.build();
}
}

4.5 对象SftpClient

SftpClient是实际工作的类,从SftpClient 中可获取到一个sftp链接,使用完成后,归还给sftpPool。SftpClient代码如下:

publicclassSftpClientimplementsISftpClient{privateSftpPoolsftpPool;/**
*从sftp连接池获取连接并执行操作
*
*@paramhandlersftp操作
*/
@Override
publicvoidopen(ISftpClient.Handlerhandler){
Sftpsftp=null;try{
sftp=sftpPool.borrowObject();
ISftpClient.HandlerpolicyHandler=newDelegateHandler(handler);
policyHandler.doHandle(sftp);
}catch(Exceptione){
log.error("sftp异常:",e);thrownewBizException(ResultCodeEnum.SFTP_EXCEPTION);
}finally{if(sftp!=null){
sftpPool.returnObject(sftp);
}
}
}@AllArgsConstructor
staticclassDelegateHandlerimplementsISftpClient.Handler{privateISftpClient.Handlertarget;@Override
publicvoiddoHandle(Sftpsftp){try{
target.doHandle(sftp);
}catch(Exceptione){
log.error("sftp异常:",e);thrownewBizException(ResultCodeEnum.SFTP_EXCEPTION);
}
}
}
}

4.6 实战代码示例

通过sftp上传文件到XX服务器

//通过SFTP上传到XX((MultipleSftpClient)sftpClient).choose("XX");
sftpClient.open(sftp->{
booleanexist=sftp.isExist(inventoryPath);if(!exist){
sftp.mkdirs(inventoryPath);
}//执行sftp操作
InputStreamis=newFileInputStream(oneColumnCSVFile);
sftp.upload(inventoryPath,titleName,is);
log.info("inventoryuploadover");
});

5 总结

通过本文的介绍可以知道,Apache Commons Pool定义了一个对象池的行为,提供了可扩展的配置类和对象工厂,封装了对象创建、从池中获取对象、归还对象的核心流程。还介绍了开源框架Jedis是如何基于GenericObjectPool来实现的连接池。最后介绍了国际物流履约系统中是如何基于GenericObjectPool来管理Sftp连接的。
掌握了GenericObjectPool的核心原理,我们就可以通过实现几个关键的接口,创建一个对象池管理工具,在项目中避免了对象的频繁创建和销毁,从而显著提升程序的性能。

标签:

推荐阅读

  • ​29个稀有姓(中国人口最少的3个姓氏,比大熊猫还稀有,一姓被韩国人认作祖先)

    29个稀有姓(中国人口最少的3个姓氏,比大熊猫还稀有,一姓被韩国人认作祖先) 中国人有很强的宗族姓氏观念。在古代,一个身份卑微的人甚至没有自己的姓和名,只有一个由他人习惯...

    2024-01-20 23:58:25
  • ​格林童话(《格林童话》如何从少儿不宜变成孩子的甜蜜读物)

    格林童话(《格林童话》如何从少儿不宜变成孩子的甜蜜读物) 2010年时,国内市场曾出现过一本名为《令人战栗的格林童话:你没读过的初版原型》的图书,引起了轩然大波。该书在封面...

    2024-01-20 23:56:20
  • ​拿破仑的评价(拿破仑的评价300字)

    拿破仑的评价(拿破仑的评价300字) 我们应该如何正确评价拿破仑 拿破仑是迄今争议最大的历史人物之一。他曾被人讥讽为“矮小的科西嘉人”。拿破仑有超人的精力,非凡的胆识,...

    2024-01-20 23:54:15
  • ​企业号(企业号可以认证几个抖音)

    企业号(企业号可以认证几个抖音) 微信企业号怎么申请 微信企业号申请的方法如下: 电脑:联想电脑天逸510S。 系统:Windows10。 软件:Microsoft Edge102.0.1245.44。 1、首先打开Microsoft...

    2024-01-20 23:52:10
  • ​巨龙之魂h攻略(h巨龙之魂卡cd)

    巨龙之魂h攻略(h巨龙之魂卡cd) H巨龙之魂 DKT攻略 1号,没什么。现在1号有BUG,一般都是DK自己去卡BUG抗分身,基本不掉血。如果正常打,要注意BOSS践踏的时候身边有人分担伤害,出球...

    2024-01-20 23:50:05
  • ​演员徐佳宁(演员徐佳宁留村查看)

    演员徐佳宁(演员徐佳宁留村查看) 徐佳宁哪年哪月出生的,徐佳宁的父亲到底是谁 提起徐佳宁哪年哪月出生的,大家都知道,有人问徐佳宁的到底是谁,另外,还有人想问李小冉和...

    2024-01-20 23:48:00
  • ​连衣裙用英语怎么写(我最喜欢的衣服是连衣裙用英语怎么写)

    连衣裙用英语怎么写(我最喜欢的衣服是连衣裙用英语怎么写) 本篇文章给大家谈谈连衣裙用英语怎么写,以及我最喜欢的衣服是连衣裙用英语怎么写对应的知识点,希望对各位有所帮...

    2024-01-20 07:31:39
  • ​电热水器安装(电热水器安装在哪里最好)

    电热水器安装(电热水器安装在哪里最好) 3硬水质的地方,建议进行水软化处理4电气有电气接地,以减少触电或可能电击的危险热水器应可靠接地每台商用电热水器应安装带过载保护和...

    2024-01-20 07:29:34
  • ​达利园是中国的品牌吗(达利园把“达利园”告了,究竟谁是李鬼?需警惕商号侵权

    达利园是中国的品牌吗(达利园把“达利园”告了,究竟谁是李鬼?需警惕商号侵权 ) 点击右上方字体“关注”,即可免费阅览和获取《三晋视角》最新资讯 据天眼查信息显示,达利园...

    2024-01-20 07:27:29
  • ​qq餐厅7级(餐厅变成什么了)

    qq餐厅7级(餐厅变成什么了) 本篇文章给大家谈谈qq餐厅7级,以及餐厅变成什么了对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。 QQ餐厅档次升级,从6级升到7级,扣了我4...

    2024-01-20 07:25:24
  • ​bha(bha是什么球队)

    bha(bha是什么球队) 本篇文章给大家谈谈bha,以及bha是什么球队对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。 BHA是什么意思呀? 1、BHA是丁基羟基茴香醚,白色或微黄色...

    2024-01-20 07:23:19
  • ​亚洲5号卫星(亚洲5号卫星节目表参数最新)

    亚洲5号卫星(亚洲5号卫星节目表参数最新) 本篇文章给大家谈谈亚洲5号卫星,以及亚洲5号卫星节目表参数最新对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。 亚太5号卫...

    2024-01-20 07:21:15
  • ​香奈儿5号香水多少钱(香奈儿5号香水好闻吗)

    香奈儿5号香水多少钱(香奈儿5号香水好闻吗) 今天给各位分享香奈儿5号香水多少钱的知识,其中也会对香奈儿5号香水好闻吗进行解释,如果能碰巧解决你现在面临的问题,别忘了关...

    2024-01-20 07:19:10
  • ​微生态发酵床养猪(发酵床养猪场)

    微生态发酵床养猪(发酵床养猪场) 本篇文章给大家谈谈微生态发酵床养猪,以及发酵床养猪场对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。 现在发酵床养猪怎么样,三...

    2024-01-20 07:17:05
  • ​天使禁猎区cos(天使禁猎区是什么风格)

    天使禁猎区cos(天使禁猎区是什么风格) 本篇文章给大家谈谈天使禁猎区cos,以及天使禁猎区是什么风格对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。 介绍几个著名的...

    2024-01-20 07:15:00
  • ​赏金猎人符文(赏金猎人符文怎么点)

    赏金猎人符文(赏金猎人符文怎么点) 本篇文章给大家谈谈赏金猎人符文,以及赏金猎人符文怎么点对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。 军团战争赏金猎人怎...

    2024-01-20 07:12:55
  • ​追男生的十大禁忌(追男生最好的办法是什么)

    追男生的十大禁忌(追男生最好的办法是什么) 1、1不要死缠烂打 死缠烂打的女生会让男生觉得非常的厌烦,并不会让男生对你产生好感,对你的印象不好之后你就很难再接近你喜欢的男...

    2024-01-20 04:24:05
  • ​橡皮泥作品(中国风橡皮泥作品)

    橡皮泥作品(中国风橡皮泥作品) 橡皮泥手工制作碗的方法 孩子们通过橡皮泥,充分发挥自己的想象力,可以让孩子们自己的手工制作过程中,将橡皮泥变成各种手工作品。下面是我...

    2024-01-20 04:22:00
  • ​跆拳道腰带系法(跆拳道腰带系法图解)

    跆拳道腰带系法(跆拳道腰带系法图解) 本篇文章给大家谈谈跆拳道腰带系法,以及跆拳道腰带系法图解对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。 跆拳道道带怎么...

    2024-01-20 04:19:55
  • ​乔治阿玛尼手表(乔治阿玛尼手表中国官方网站)

    乔治阿玛尼手表(乔治阿玛尼手表中国官方网站) 今天给各位分享乔治阿玛尼手表的知识,其中也会对乔治阿玛尼手表中国官方网站进行解释,如果能碰巧解决你现在面临的问题,别忘...

    2024-01-20 04:17:50