电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

springboot中如何使用自定義兩級緩存

瀏覽:95日期:2023-03-10 11:34:22

工作中用到了springboot的緩存,使用起來挺方便的,直接引入redis或者ehcache這些緩存依賴包和相關緩存的starter依賴包,然后在啟動類中加入@EnableCaching注解,然后在需要的地方就可以使用@Cacheable和@CacheEvict使用和刪除緩存了。這個使用很簡單,相信用過springboot緩存的都會玩,這里就不再多說了。美中不足的是,springboot使用了插件式的集成方式,雖然用起來很方便,但是當你集成ehcache的時候就是用ehcache,集成redis的時候就是用redis。如果想兩者一起用,ehcache作為本地一級緩存,redis作為集成式的二級緩存,使用默認的方式據我所知是沒法實現的(如果有高人可以實現,麻煩指點下我)。畢竟很多服務需要多點部署,如果單獨選擇ehcache可以很好地實現本地緩存,但是如果在多機之間共享緩存又需要比較費時的折騰,如果選用集中式的redis緩存,因為每次取數據都要走網絡,總感覺性能不會太好。

為了不要侵入springboot原本使用緩存的方式,這里自己定義了兩個緩存相關的注解,如下

@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Cacheable {String value() default '';String key() default '';//泛型的Class類型Class<?> type() default Exception.class; }@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface CacheEvict {String value() default '';String key() default ''; }

如上兩個注解和spring中緩存的注解基本一致,只是去掉了一些不常用的屬性。說到這里,不知道有沒有朋友注意過,當你在springboot中單獨使用redis緩存的時候,Cacheable和CacheEvict注解的value屬性,實際上在redis中變成了一個zset類型的值的key,而且這個zset里面還是空的,比如@Cacheable(value='cache1',key='key1'),正常情況下redis中應該是出現cache1 -> map(key1,value1)這種形式,其中cache1作為緩存名稱,map作為緩存的值,key作為map里的鍵,可以有效的隔離不同的緩存名稱下的緩存。但是實際上redis里確是cache1 -> 空(zset)和key1 -> value1,兩個獨立的鍵值對,試驗得知不同的緩存名稱下的緩存完全是共用的,如果有感興趣的朋友可以去試驗下,也就是說這個value屬性實際上是個擺設,鍵的唯一性只由key屬性保證。我只能認為這是spring的緩存實現的bug,或者是特意這么設計的,(如果有知道啥原因的歡迎指點)。

回到正題,有了注解還需要有個注解處理類,這里我使用aop的切面來進行攔截處理,原生的實現其實也大同小異。切面處理類如下:

import com.xuanwu.apaas.core.multicache.annotation.CacheEvict; import com.xuanwu.apaas.core.multicache.annotation.Cacheable; import com.xuanwu.apaas.core.utils.JsonUtil; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.json.JSONArray; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * 多級緩存切面 * @author rongdi */ @Aspect @Component public class MultiCacheAspect {private static final Logger logger = LoggerFactory.getLogger(MultiCacheAspect.class);@Autowiredprivate CacheFactory cacheFactory;//這里通過一個容器初始化監聽器,根據外部配置的@EnableCaching注解控制緩存開關private boolean cacheEnable;@Pointcut('@annotation(com.xuanwu.apaas.core.multicache.annotation.Cacheable)')public void cacheableAspect() {}@Pointcut('@annotation(com.xuanwu.apaas.core.multicache.annotation.CacheEvict)')public void cacheEvict() {}@Around('cacheableAspect()')public Object cache(ProceedingJoinPoint joinPoint) { //得到被切面修飾的方法的參數列表 Object[] args = joinPoint.getArgs(); // result是方法的最終返回結果 Object result = null; //如果沒有開啟緩存,直接調用處理方法返回 if(!cacheEnable){try { result = joinPoint.proceed(args);} catch (Throwable e) { logger.error('',e);}return result; } // 得到被代理方法的返回值類型 Class returnType = ((MethodSignature) joinPoint.getSignature()).getReturnType(); // 得到被代理的方法 Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); // 得到被代理的方法上的注解 Cacheable ca = method.getAnnotation(Cacheable.class); //獲得經過el解析后的key值 String key = parseKey(ca.key(),method,args); Class<?> elementClass = ca.type(); //從注解中獲取緩存名稱 String name = ca.value(); try {//先從ehcache中取數據String cacheValue = cacheFactory.ehGet(name,key);if(StringUtils.isEmpty(cacheValue)) { //如果ehcache中沒數據,從redis中取數據 cacheValue = cacheFactory.redisGet(name,key); if(StringUtils.isEmpty(cacheValue)) {//如果redis中沒有數據// 調用業務方法得到結果result = joinPoint.proceed(args);//將結果序列化后放入rediscacheFactory.redisPut(name,key,serialize(result)); } else {//如果redis中可以取到數據//將緩存中獲取到的數據反序列化后返回if(elementClass == Exception.class) { result = deserialize(cacheValue, returnType);} else { result = deserialize(cacheValue, returnType,elementClass);} } //將結果序列化后放入ehcache cacheFactory.ehPut(name,key,serialize(result));} else { //將緩存中獲取到的數據反序列化后返回 if(elementClass == Exception.class) {result = deserialize(cacheValue, returnType); } else {result = deserialize(cacheValue, returnType,elementClass); }} } catch (Throwable throwable) {logger.error('',throwable); } return result;}/** * 在方法調用前清除緩存,然后調用業務方法 * @param joinPoint * @return * @throws Throwable * */@Around('cacheEvict()')public Object evictCache(ProceedingJoinPoint joinPoint) throws Throwable { // 得到被代理的方法 Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); //得到被切面修飾的方法的參數列表 Object[] args = joinPoint.getArgs(); // 得到被代理的方法上的注解 CacheEvict ce = method.getAnnotation(CacheEvict.class); //獲得經過el解析后的key值 String key = parseKey(ce.key(),method,args); //從注解中獲取緩存名稱 String name = ce.value(); // 清除對應緩存 cacheFactory.cacheDel(name,key); return joinPoint.proceed(args);}/** * 獲取緩存的key * key 定義在注解上,支持SPEL表達式 * @return */private String parseKey(String key,Method method,Object [] args){ if(StringUtils.isEmpty(key)) return null; //獲取被攔截方法參數名列表(使用Spring支持類庫) LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer(); String[] paraNameArr = u.getParameterNames(method); //使用SPEL進行key的解析 ExpressionParser parser = new SpelExpressionParser(); //SPEL上下文 StandardEvaluationContext context = new StandardEvaluationContext(); //把方法參數放入SPEL上下文中 for(int i=0;i<paraNameArr.length;i++){context.setVariable(paraNameArr[i], args[i]); } return parser.parseExpression(key).getValue(context,String.class);}//序列化private String serialize(Object obj) { String result = null; try {result = JsonUtil.serialize(obj); } catch(Exception e) {result = obj.toString(); } return result;}//反序列化private Object deserialize(String str,Class clazz) { Object result = null; try {if(clazz == JSONObject.class) { result = new JSONObject(str);} else if(clazz == JSONArray.class) { result = new JSONArray(str);} else { result = JsonUtil.deserialize(str,clazz);} } catch(Exception e) { } return result;}//反序列化,支持List<xxx>private Object deserialize(String str,Class clazz,Class elementClass) { Object result = null; try {if(clazz == JSONObject.class) { result = new JSONObject(str);} else if(clazz == JSONArray.class) { result = new JSONArray(str);} else { result = JsonUtil.deserialize(str,clazz,elementClass);} } catch(Exception e) { } return result;}public void setCacheEnable(boolean cacheEnable) { this.cacheEnable = cacheEnable;} }

上面這個界面使用了一個cacheEnable變量控制是否使用緩存,為了實現無縫的接入springboot,必然需要受到原生@EnableCaching注解的控制,這里我使用一個spring容器加載完成的監聽器,然后在監聽器里找到是否有被@EnableCaching注解修飾的類,如果有就從spring容器拿到MultiCacheAspect對象,然后將cacheEnable設置成true。這樣就可以實現無縫接入springboot,不知道朋友們還有沒有更加優雅的方法呢?歡迎交流!監聽器類如下

import com.xuanwu.apaas.core.multicache.CacheFactory; import com.xuanwu.apaas.core.multicache.MultiCacheAspect; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; import java.util.Map; /** * 用于spring加載完成后,找到項目中是否有開啟緩存的注解@EnableCaching * @author rongdi */ @Component public class ContextRefreshedListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { // 判斷根容器為Spring容器,防止出現調用兩次的情況(mvc加載也會觸發一次) if(event.getApplicationContext().getParent()==null){//得到所有被@EnableCaching注解修飾的類Map<String,Object> beans = event.getApplicationContext().getBeansWithAnnotation(EnableCaching.class);if(beans != null && !beans.isEmpty()) { MultiCacheAspect multiCache = (MultiCacheAspect)event.getApplicationContext().getBean('multiCacheAspect'); multiCache.setCacheEnable(true);} }} }

實現了無縫接入,還需要考慮多點部署的時候,多點的ehcache怎么和redis緩存保持一致的問題。在正常應用中,一般redis適合長時間的集中式緩存,ehcache適合短時間的本地緩存,假設現在有A,B和C服務器,A和B部署了業務服務,C部署了redis服務。當請求進來,前端入口不管是用LVS或者nginx等負載軟件,請求都會轉發到某一個具體服務器,假設轉發到了A服務器,修改了某個內容,而這個內容在redis和ehcache中都有,這時候,A服務器的ehcache緩存,和C服務器的redis不管控制緩存失效也好,刪除也好,都比較容易,但是這時候B服務器的ehcache怎么控制失效或者刪除呢?一般比較常用的方式就是使用發布訂閱模式,當需要刪除緩存的時候在一個固定的通道發布一個消息,然后每個業務服務器訂閱這個通道,收到消息后刪除或者過期本地的ehcache緩存(最好是使用過期,但是redis目前只支持對key的過期操作,沒辦法操作key下的map里的成員的過期,如果非要強求用過期,可以自己加時間戳自己實現,不過用刪除出問題的幾率也很小,畢竟加緩存的都是讀多寫少的應用,這里為了方便都是直接刪除緩存)。總結起來流程就是更新某條數據,先刪除redis中對應的緩存,然后發布一個緩存失效的消息在redis的某個通道中,本地的業務服務去訂閱這個通道的消息,當業務服務收到這個消息后去刪除本地對應的ehcache緩存,redis的各種配置如下

import com.fasterxml.jackson.annotation.JsonAutoDetect;import com.fasterxml.jackson.annotation.PropertyAccessor;import com.fasterxml.jackson.databind.ObjectMapper;import com.xuanwu.apaas.core.multicache.subscriber.MessageSubscriber;import org.springframework.cache.CacheManager;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.cache.RedisCacheManager;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.listener.PatternTopic;import org.springframework.data.redis.listener.RedisMessageListenerContainer;import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;import java.util.HashMap;import java.util.Map;@Configurationpublic class MultiCacheConfig { @Bean public CacheManager cacheManager(RedisTemplate redisTemplate) { RedisCacheManager rcm = new RedisCacheManager(redisTemplate); //設置緩存過期時間(秒) Map<String, Long> expires = new HashMap<>(); expires.put('ExpOpState',0L); expires.put('ImpOpState',0L); rcm.setExpires(expires); rcm.setDefaultExpiration(600); return rcm; } @Bean public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) { StringRedisTemplate template = new StringRedisTemplate(factory); StringRedisSerializer redisSerializer = new StringRedisSerializer(); template.setValueSerializer(redisSerializer); template.afterPropertiesSet(); return template; } /** * redis消息監聽器容器 * 可以添加多個監聽不同話題的redis監聽器,只需要把消息監聽器和相應的消息訂閱處理器綁定,該消息監聽器 * 通過反射技術調用消息訂閱處理器的相關方法進行一些業務處理 * @param connectionFactory * @param listenerAdapter * @return */ @Bean public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); //訂閱了一個叫redis.uncache的通道 container.addMessageListener(listenerAdapter, new PatternTopic('redis.uncache')); //這個container 可以添加多個 messageListener return container; } /** * 消息監聽器適配器,綁定消息處理器,利用反射技術調用消息處理器的業務方法 * @param receiver * @return */ @Bean MessageListenerAdapter listenerAdapter(MessageSubscriber receiver) { //這個地方 是給messageListenerAdapter 傳入一個消息接受的處理器,利用反射的方法調用“handle” return new MessageListenerAdapter(receiver, 'handle'); }}

消息發布類如下:

import com.xuanwu.apaas.core.multicache.CacheFactory; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class MessageSubscriber {private static final Logger logger = LoggerFactory.getLogger(MessageSubscriber.class);@Autowiredprivate CacheFactory cacheFactory;/** * 接收到redis訂閱的消息后,將ehcache的緩存失效 * @param message 格式為name_key */public void handle(String message){ logger.debug('redis.ehcache:'+message); if(StringUtils.isEmpty(message)) { return; } String[] strs = message.split('#'); String name = strs[0]; String key = null; if(strs.length == 2) {key = strs[1]; } cacheFactory.ehDel(name,key);} }

具體操作緩存的類如下:

import com.xuanwu.apaas.core.multicache.publisher.MessagePublisher;import net.sf.ehcache.Cache;import net.sf.ehcache.CacheManager;import net.sf.ehcache.Element;import org.apache.commons.lang3.StringUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.RedisConnectionFailureException;import org.springframework.data.redis.core.HashOperations;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Component;import java.io.InputStream;/** * 多級緩存切面 * @author rongdi */@Componentpublic class CacheFactory { private static final Logger logger = LoggerFactory.getLogger(CacheFactory.class); @Autowired private RedisTemplate redisTemplate; @Autowired private MessagePublisher messagePublisher; private CacheManager cacheManager; public CacheFactory() {InputStream is = this.getClass().getResourceAsStream('/ehcache.xml');if(is != null) { cacheManager = CacheManager.create(is);} } public void cacheDel(String name,String key) {//刪除redis對應的緩存redisDel(name,key);//刪除本地的ehcache緩存,可以不需要,訂閱器那里會刪除 // ehDel(name,key);if(cacheManager != null) { //發布一個消息,告訴訂閱的服務該緩存失效 messagePublisher.publish(name, key);} } public String ehGet(String name,String key) {if(cacheManager == null) return null;Cache cache=cacheManager.getCache(name);if(cache == null) return null;cache.acquireReadLockOnKey(key);try { Element ele = cache.get(key); if(ele == null) return null; return (String)ele.getObjectValue();} finally { cache.releaseReadLockOnKey(key);} } public String redisGet(String name,String key) {HashOperations<String,String,String> oper = redisTemplate.opsForHash();try { return oper.get(name, key);} catch(RedisConnectionFailureException e) { //連接失敗,不拋錯,直接不用redis緩存了 logger.error('connect redis error ',e); return null;} } public void ehPut(String name,String key,String value) {if(cacheManager == null) return;if(!cacheManager.cacheExists(name)) { cacheManager.addCache(name);}Cache cache=cacheManager.getCache(name);//獲得key上的寫鎖,不同key互相不影響,類似于synchronized(key.intern()){}cache.acquireWriteLockOnKey(key);try { cache.put(new Element(key, value));} finally { //釋放寫鎖 cache.releaseWriteLockOnKey(key);} } public void redisPut(String name,String key,String value) {HashOperations<String,String,String> oper = redisTemplate.opsForHash();try { oper.put(name, key, value);} catch (RedisConnectionFailureException e) { //連接失敗,不拋錯,直接不用redis緩存了 logger.error('connect redis error ',e);} } public void ehDel(String name,String key) {if(cacheManager == null) return;Cache cache = cacheManager.getCache(name);if(cache != null) { //如果key為空,直接根據緩存名刪除 if(StringUtils.isEmpty(key)) {cacheManager.removeCache(name); } else {cache.remove(key); }} } public void redisDel(String name,String key) {HashOperations<String,String,String> oper = redisTemplate.opsForHash();try { //如果key為空,直接根據緩存名刪除 if(StringUtils.isEmpty(key)) {redisTemplate.delete(name); } else {oper.delete(name,key); }} catch (RedisConnectionFailureException e) { //連接失敗,不拋錯,直接不用redis緩存了 logger.error('connect redis error ',e);} }}

 工具類如下

import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; import org.json.JSONArray; import org.json.JSONObject; import java.util.*; public class JsonUtil {private static ObjectMapper mapper;static { mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);}/** * 將對象序列化成json * * @param obj 待序列化的對象 * @return * @throws Exception */public static String serialize(Object obj) throws Exception { if (obj == null) {throw new IllegalArgumentException('obj should not be null'); } return mapper.writeValueAsString(obj);}/** 帶泛型的反序列化,比如一個JSONArray反序列化成List<User>*/public static <T> T deserialize(String jsonStr, Class<?> collectionClass,Class<?>... elementClasses) throws Exception { JavaType javaType = mapper.getTypeFactory().constructParametrizedType( collectionClass, collectionClass, elementClasses); return mapper.readValue(jsonStr, javaType);}/** * 將json字符串反序列化成對象 * @param src 待反序列化的json字符串 * @param t 反序列化成為的對象的class類型 * @return * @throws Exception */public static <T> T deserialize(String src, Class<T> t) throws Exception { if (src == null) {throw new IllegalArgumentException('src should not be null'); } if('{}'.equals(src.trim())) {return null; } return mapper.readValue(src, t);} }

具體使用緩存,和之前一樣只需要關注@Cacheable和@CacheEvict注解,同樣也支持spring的el表達式。而且這里的value屬性表示的緩存名稱也沒有上面說的那個問題,完全可以用value隔離不同的緩存,例子如下

@Cacheable(value = 'bo',key='#session.productVersionCode+’’+#session.tenantCode+’’+#objectcode')@CacheEvict(value = 'bo',key='#session.productVersionCode+’’+#session.tenantCode+’’+#objectcode')

附上主要的依賴包

'org.springframework.boot:spring-boot-starter-redis:1.4.2.RELEASE',’net.sf.ehcache:ehcache:2.10.4’,'org.json:json:20160810'

以上就是springboot中如何使用自定義兩級緩存的詳細內容,更多關于springboot 使用自定義兩級緩存的資料請關注好吧啦網其它相關文章!

標簽: Spring
相關文章:
主站蜘蛛池模板: 土壤有机碳消解器-石油|表层油类分析采水器-青岛溯源环保设备有限公司 | 培训中心-翰香原香酥板栗饼加盟店总部-正宗板栗酥饼技术 | 车载加油机品牌_ 柴油加油机厂家 | 陕西鹏展科技有限公司 | PC构件-PC预制构件-构件设计-建筑预制构件-PC构件厂-锦萧新材料科技(浙江)股份有限公司 | 精密模具加工制造 - 富东懿 | 波纹补偿器_不锈钢波纹补偿器_巩义市润达管道设备制造有限公司 | 红立方品牌应急包/急救包加盟,小成本好项目代理_应急/消防/户外用品加盟_应急好项目加盟_新奇特项目招商 - 中红方宁(北京) 供应链有限公司 | 威海防火彩钢板,威海岩棉复合板,威海彩钢瓦-文登区九龙岩棉复合板厂 | 蜘蛛车-高空作业平台-升降机-高空作业车租赁-臂式伸缩臂叉装车-登高车出租厂家 - 普雷斯特机械设备(北京)有限公司 | 三防漆–水性三防漆–水性浸渍漆–贝塔三防漆厂家 | 双齿辊破碎机-大型狼牙破碎机视频-对辊破碎机价格/型号图片-金联机械设备生产厂家 | AR开发公司_AR增强现实_AR工业_AR巡检|上海集英科技 | 釜溪印象网络 - Powered by Discuz! | 烟台金蝶财务软件,烟台网站建设,烟台网络推广 | 档案密集架_电动密集架_移动密集架_辽宁档案密集架-盛隆柜业厂家现货批发销售价格公道 | 高通量组织研磨仪-多样品组织研磨仪-全自动组织研磨仪-研磨者科技(广州)有限公司 | 耐酸碱泵-自吸耐酸碱泵型号「品牌厂家」立式耐酸碱泵价格-昆山国宝过滤机有限公司首页 | 【官网】博莱特空压机,永磁变频空压机,螺杆空压机-欧能优 | 广西正涛环保工程有限公司【官网】| 恒湿机_除湿加湿一体机_恒湿净化消毒一体机厂家-杭州英腾电器有限公司 | 河南中整光饰机械有限公司-抛光机,去毛刺抛光机,精密镜面抛光机,全自动抛光机械设备 | 课件导航网_ppt课件_课件模板_课件下载_最新课件资源分享发布平台 | 棉服定制/厂家/公司_棉袄订做/价格/费用-北京圣达信棉服 | 广州中央空调回收,二手中央空调回收,旧空调回收,制冷设备回收,冷气机组回收公司-广州益夫制冷设备回收公司 | 硬质合金模具_硬质合金非标定制_硬面加工「生产厂家」-西迪技术股份有限公司 | WTB5光栅尺-JIE WILL磁栅尺-B60数显表-常州中崴机电科技有限公司 | 卫生纸复卷机|抽纸机|卫生纸加工设备|做卫生纸机器|小型卫生纸加工需要什么设备|卫生纸机器设备多少钱一台|许昌恒源纸品机械有限公司 | 云南丰泰挖掘机修理厂-挖掘机维修,翻新,再制造的大型企业-云南丰泰工程机械维修有限公司 | 塑胶地板-商用PVC地板-pvc地板革-安耐宝pvc塑胶地板厂家 | 粉末包装机-给袋式包装机-全自动包装机-颗粒-液体-食品-酱腌菜包装机生产线【润立机械】 | 微水泥_硅藻泥_艺术涂料_艺术漆_艺术漆加盟-青岛泥之韵环保壁材 武汉EPS线条_EPS装饰线条_EPS构件_湖北博欧EPS线条厂家 | 台湾HIWIN上银直线模组|导轨滑块|TBI滚珠丝杆丝杠-深圳汉工 | 雪花制冰机(实验室雪花制冰机)百科| uv固化机-丝印uv机-工业烤箱-五金蚀刻机-分拣输送机 - 保定市丰辉机械设备制造有限公司 | 美国查特CHART MVE液氮罐_查特杜瓦瓶_制造全球品质液氮罐 | 考勤系统_人事考勤管理系统_本地部署BS考勤系统_考勤软件_天时考勤管理专家 | _网名词典_网名大全_qq网名_情侣网名_个性网名 | 大米加工设备|大米加工机械|碾米成套设备|大米加工成套设备-河南成立粮油机械有限公司 | 专注氟塑料泵_衬氟泵_磁力泵_卧龙泵阀_化工泵专业品牌 - 梭川泵阀 | 破碎机锤头_耐磨锤头_合金锤头-鼎成机械一站式耐磨铸件定制服务 微型驱动系统解决方案-深圳市兆威机电股份有限公司 |