详解Java事件编程的使用

Java事件编程

当前在线网店很多,很涉及商品管理和销售的问题,比如:

一,在商品库存管理的商品增加时,我们主要业务时编辑保持商品信息,

同时因商品增加而附带有一些“非主要业务”,如:

1,应商品的库存数量等更新,2,热销产品的推广处理等

二,在商品产生订单时,我们的主要业务(对买家而言)是建立订单业务,

同时因产生订单而附带有一些不是买家关心的“非主要业务”,如:

1,库存和已售数量的更新2,发货的准备处理事宜3,物流的处理事宜

非主要业务我们可以让程序使用多线程异步执行,这样主要业务和非主要业务就完全解耦,主要业务可以快速完成响应,非主要业务多线程异步执行,提高程序的吞吐量即高处理能力;同时使用自定义的线程池,有比较好的把控;

下面我们就根据上面的场景需求编写一个例子,看看在Springboot中如何Java的事件编程,对比一下常规逻辑的编程,和采用Java事件编程的差异。

//常规逻辑的编程@GetMapping(value="/add")  @ResponseBodypublic String addProduct(Product product) {//增加产品//应商品的库存数量等更新//是否为热销产品的推广处理//其它处理return "产品增加完成";}

主要业务是增加产品信息,但是可能会因产品库存或热销产品等其它问题处理而收到影响,耦合性比较强,如果以后还有其它需求又需要改动程序,问题暴露出来了;

同样,下单也是一样问题,主要业务是买家下单时建立订单,

//常规逻辑的编程@GetMapping(value="/createOrder")  @ResponseBodypublic String createProductOrder(ProductOrder productOrder) {//收集产品订单信息,保持建立订单//库存和已售数量的更新//订单备货处理//物流处理return "产品订单建立完成";}

对买家来说,主要业务是产品下单,后续的库存和已售数量的更新,备货处理,物流处理等不是买家关心的,但是可能会因这些问题处理而受到影响,可能下单失败,耦合性比较强,如果以后还有其它需求又需要改动程序,同样问题暴露出来了;

那怎么建立主次分明的处理逻辑呢,这里用Java事件编程就很好处理这些问题,主次分明,完全解耦,程序改动比较小,程序吞吐量也强,相关注释在程序非常清楚,所以主要看代码吧;

三,使用Java事件编程,Springboot例子,

1,项目结构如下图:

2,自定义异步执行使用的线程池,参考如下代码:

package com.shenzhennba.pro06.eventSource.asyncConfig; import java.io.Serializable; import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.task.AsyncTaskExecutor;import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; /** * 配置异步任务线程池,然后注入springIOC容器,提供异步任务使用; * @author shenzhenNBA * @since 2021/06/14 */@Configurationpublic class AsyncThreadPoolConfig implements Serializable { private static final long serialVersionUID = 20210606010060L;private static final int MAX_POOL_SIZE = 100;    private static final int CORE_POOL_SIZE = 20;    private static final String THREAD_NAME_PREFIX = "async_task_";    //创建线程池,并指定实例名称    @Bean("asyncTaskExecutor")    public AsyncTaskExecutor asyncTaskExecutor() {ThreadPoolTaskExecutor asyncTaskExecutor = new ThreadPoolTaskExecutor();asyncTaskExecutor.setMaxPoolSize(MAX_POOL_SIZE);asyncTaskExecutor.setCorePoolSize(CORE_POOL_SIZE);asyncTaskExecutor.setThreadNamePrefix(THREAD_NAME_PREFIX);asyncTaskExecutor.initialize();return asyncTaskExecutor;}    }

3,定义业务相关需要的实体或model,参考如下代码:

package com.shenzhennba.pro06.eventSource.model; import java.io.Serializable; import org.apache.commons.lang.builder.ReflectionToStringBuilder; /** * 商品实体类 * @author shenzhenNBA * @since 2021/06/14 */public class Product implements Serializable { private static final long serialVersionUID = 20210606010010L;private String categoryCode;//商品类别代码private String productName;//商品名称private String productCode;//商品代码private Double price;//商品单价private Long addNum;//增加数量private Long isHotSell=0L;//是否促销,0=不是,1=是private String createTime;//商品入库时间public Product() {super();} //getter / setter 省略...@Override    public String toString() {        return ReflectionToStringBuilder.toString(this);    }}
package com.shenzhennba.pro06.eventSource.model; import java.io.Serializable; import org.apache.commons.lang.builder.ReflectionToStringBuilder; /** * 商品的数量和积分相关实体类 * @author shenzhenNBA * @since 2021/06/14 */public class ProductNumber implements Serializable {private static final long serialVersionUID = 20210606010020L;private String productCode;//商品代码private Long storageNum;//商品库存private Long soldNum;//已收数量private Long scoreNum;//单个购买赠送积分数public ProductNumber() {super();} // getter / setter 省略...@Override    public String toString() {        return ReflectionToStringBuilder.toString(this);    }}
package com.shenzhennba.pro06.eventSource.model; import java.io.Serializable;import java.util.Date; import org.apache.commons.lang.builder.ReflectionToStringBuilder; /** * 商品订单实体类 * @author shenzhenNBA * @since 2021/06/14 */public class ProductOrder implements Serializable {private static final long serialVersionUID = 20210606010030L;private String orderCode;//订单代码private String productName;//商品名称private String productCode;//商品代码private Double price;//商品单价private String createTime;//下单时间private Long scoreNum;//购买赠送积分数private Long buyNum;//订单购买数量private Integer isPrepared = 0;//发货准备状态,默认0=未准备好,1=已准备好private Integer isSendOut = 0;//是否已发货,默认0=未发,1=已发private String warehouseCode ;//发货仓库代码private String senderCode ;//物流处理商家代码private Date planSendTime ;//计划发货时间private Date recieveTime ;//收货时间private String recieveAddress;//收货地址public ProductOrder() {super();} // getter / setter 省略...@Override    public String toString() {        return ReflectionToStringBuilder.toString(this);    }

4,定义业务的相关Java事件源,主要是编写继承org.springframework.context.ApplicationEvent 的类对象,以及定义各事件相关的对象,比如,商品对象,或订单对象,事件源的目的是存储事件相关的目标信息,参考如下代码:

package com.shenzhennba.pro06.eventSource.eventSource; import java.io.Serializable; import org.springframework.context.ApplicationEvent; import com.shenzhennba.pro06.eventSource.model.Product; /** * 商品添加事件源的定义,目的是用于存储事件的目标信息,即添加的商品对象; * 注意:是继承ApplicationEvent的普通Bean对象 * @author shenzhenNBA * @since 2021/06/14 */public class ProductEventSource extends ApplicationEvent implements Serializable { private static final long serialVersionUID = 20210606010040L;//目的是用于存储事件的目标信息,即添加的商品对象private Product product;public ProductEventSource(Product source) {super(source);this.product = source;} public Product getProduct() {return product;} public void setProduct(Product product) {this.product = product;}}
package com.shenzhennba.pro06.eventSource.eventSource; import java.io.Serializable; import org.springframework.context.ApplicationEvent; import com.shenzhennba.pro06.eventSource.model.ProductOrder; /** * 商品订单建立事件源的定义,目的是用于存储事件的目标信息,即建立的商品订单对象; * 注意:是继承ApplicationEvent的普通Bean对象 * @author shenzhenNBA * @since 2021/06/14 */public class ProductOrderEventSource extends ApplicationEvent implements Serializable{ private static final long serialVersionUID = 20210606010050L;//目的是用于存储事件的目标信息,即建立的商品订单对象private ProductOrder productOrder;public ProductOrderEventSource(ProductOrder source) {super(source);this.productOrder = source;}public ProductOrder getProductOrder() {return productOrder;}public void setProductOrder(ProductOrder productOrder) {this.productOrder = productOrder;}}

5,定义各种监听器和相关业务处理逻辑,并将其纳入SpringIOC容器管理,监听类方法加注解 @EventListener 使之变为一个监听器,加注解@Async(“asyncTaskExecutor”) 指定监听器异步执行,同时指定异步执行使用的线程池,加注解 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)指定监听器事务的阶段, 一般为业务数据入库之后,再异步执行方法;加注解 @Order(1) 指定同一事件有多个监听器时执行顺序,值越小,执行顺序优先,类前加 @Component 注解,把各监听器交给SpringIOC容器管理,参考如下代码:

package com.shenzhennba.pro06.eventSource.listener; import java.io.Serializable; import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.context.event.EventListener;import org.springframework.core.annotation.Order;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Component;import org.springframework.transaction.event.TransactionPhase;import org.springframework.transaction.event.TransactionalEventListener; import com.shenzhennba.pro06.eventSource.eventSource.ProductEventSource;import com.shenzhennba.pro06.eventSource.eventSource.ProductOrderEventSource;import com.shenzhennba.pro06.eventSource.model.ProductOrder; /** * 定义各种事件监听器和其各自的业务处理逻辑, * 然后注入springIOC容器,并监听相应的事件,只要触发相应事件则用对应监听器处理; * 注意:各种监听器还是由spring容器管理 * @author shenzhenNBA * @since 2021/06/14 */ @Componentpublic class EventListeners implements Serializable {private static Logger logger = LoggerFactory.getLogger(EventListeners.class);private static final long serialVersionUID = 20210606010070L; //指定异步执行监听器,同时使用的自定义的异步线程池@Async("asyncTaskExecutor")//指定监听事务的阶段,多数情况下的业务操作会涉及数据库事务,确保主业务的数据入库后,再进行本方法的异步操作@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)//注解定义本方法为一个监听器,处理因增加产品而引发的事件中有关产品库存等数量相关信息@EventListener//指定当同一个事件有多个监听器时执行顺序,值越小,执行顺序优先@Order(1)public void updateProductReferNumListener(ProductEventSource pes) {if (null == pes || null == pes.getProduct()) {return;}logger.info("");logger.info("产品增加事件监听器 1, 事件关联信息:{}", pes.getProduct());logger.info("");// TODO 有关产品数量的库存等更新操作}//指定异步执行监听器,同时使用的自定义的异步线程池@Async("asyncTaskExecutor")//指定监听事务的阶段,多数情况下的业务操作会涉及数据库事务,确保主业务的数据入库后,再进行本方法的异步操作@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)//注解定义本方法为一个监听器,处理因增加产品而引发的事件中有关热销产品的相关促销事宜@EventListener//指定当同一个事件有多个监听器时执行顺序,值越小,执行顺序优先@Order(5)public void handleHotSellProductListener(ProductEventSource pes) {if (null == pes || null == pes.getProduct()) {return;}logger.info("");logger.info("产品增加事件监听器 2, 事件关联信息:{}", pes.getProduct());logger.info("");if (null == pes.getProduct()) {return;}if (1 != pes.getProduct().getIsHotSell().intValue()) {logger.info("产品增加事件监听器 2, 非热销产品");return;}// TODO 有关热销产品的相关促销事宜的处理}//指定异步执行监听器,同时使用的自定义的异步线程池@Async("asyncTaskExecutor")//指定监听事务的阶段,多数情况下的业务操作会涉及数据库事务,确保主业务的数据入库后,再进行本方法的异步操作@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)//注解定义本方法为一个监听器,处理因产品产生订单而引发的事件中有关产品库存和已售数量相关信息@EventListener//指定当同一个事件有多个监听器时执行顺序,值越小,执行顺序优先@Order(1)public void updateProductReferNumListener(ProductOrderEventSource poes) {if (null == poes || null == poes.getProductOrder()) {return;}logger.info("");logger.info("产品产生订单事件监听器 1, 事件关联信息:{}", poes.getProductOrder());logger.info("");// TODO 有关产品数量的库存和已售数量更新操作}//指定异步执行监听器,同时使用的自定义的异步线程池@Async("asyncTaskExecutor")//指定监听事务的阶段,多数情况下的业务操作会涉及数据库事务,确保主业务的数据入库后,再进行本方法的异步操作@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)//注解定义本方法为一个监听器,处理因产品产生订单而引发的事件中有关产品发货的相关准备和处理事宜@EventListener//指定当同一个事件有多个监听器时执行顺序,值越小,执行顺序优先@Order(5)public void prepareSendProductListener(ProductOrderEventSource poes) {if (null == poes || null == poes.getProductOrder()) {return;}ProductOrder proOrder = poes.getProductOrder();logger.info("");logger.info("产品产生订单事件监听器 2, 事件关联信息:{}", proOrder);logger.info("");if (null != proOrder.getIsSendOut() && 1 == proOrder.getIsSendOut()) {logger.info("产品产生订单事件监听器 2, 订单已经发货,不用再处理");return;}if (null != proOrder.getIsPrepared() && 1 == proOrder.getIsPrepared()) {logger.info("产品产生订单事件监听器 2, 订单发货准备已经完成,不用再处理");return;}// TODO 有关产品订单发货的事宜}//指定异步执行监听器,同时使用的自定义的异步线程池@Async("asyncTaskExecutor")//指定监听事务的阶段,多数情况下的业务操作会涉及数据库事务,确保主业务的数据入库后,再进行本方法的异步操作@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)//注解定义本方法为一个监听器,处理因产品产生订单而引发的事件中有关产品发货的物流事宜@EventListener//指定当同一个事件有多个监听器时执行顺序,值越小,执行顺序优先@Order(10)public void sendProductListener(ProductOrderEventSource poes) {if (null == poes || null == poes.getProductOrder()) {return;}ProductOrder proOrder = poes.getProductOrder();logger.info("");logger.info("产品产生订单事件监听器 3, 事件关联信息:{}", poes.getProductOrder());logger.info("");if (null != proOrder.getIsSendOut() && 1 == proOrder.getIsSendOut()) {logger.info("产品产生订单事件监听器 3, 订单已经发货,不用再处理");return;}if (null != proOrder.getIsPrepared() && 0 == proOrder.getIsPrepared().intValue()) {logger.info("产品产生订单事件监听器 3, 订单发货准备还未完成,先等备好货,再处理物流事宜");}// TODO 有关产品订单的物流事宜}}

6,定义业务接口,参考如下代码:

package com.shenzhennba.pro06.eventSource.service; import java.io.Serializable; import com.shenzhennba.pro06.eventSource.model.Product; /** * 商品service层接口 * @author shenzhenNBA * @since 2021/06/14 */public interface ProductService extends Serializable {/** * 增加产品 * @author shenzhenNBA * @param product * @return */Integer addProduct(Product product);/** * 根据产品代码查询产品记录 * @author shenzhenNBA * @param product * @return */Product getProduct(String productCode);}
package com.shenzhennba.pro06.eventSource.service; import java.io.Serializable; import com.shenzhennba.pro06.eventSource.model.Product;import com.shenzhennba.pro06.eventSource.model.ProductOrder; /** * 商品订单service层接口 * @author shenzhenNBA * @since 2021/06/14 */public interface ProductOrderService extends Serializable { /** * 建立产品订单 * @author shenzhenNBA * @param product * @return */Integer createProductOrder(ProductOrder productOrder);/** * 根据产品订单代码查询产品订单记录 * @author shenzhenNBA * @param product * @return */Product getProductOrder(String productOrderCode);}

7,实现所定义的业务接口,参考如下代码:

package com.shenzhennba.pro06.eventSource.service.impl; import org.springframework.stereotype.Service; import com.shenzhennba.pro06.eventSource.model.Product;import com.shenzhennba.pro06.eventSource.service.ProductService; /** * 商品service接口实现类 * @author shenzhenNBA * @since 2021/06/14 */ @Servicepublic class ProductServiceImpl implements ProductService { private static final long serialVersionUID = 20210606010090L;/** * 增加产品 */@Overridepublic Integer addProduct(Product product) {// TODO more biz handle herereturn null;} /** * 根据产品代码查询产品记录 */@Overridepublic Product getProduct(String productCode) {// TODO more biz handle herereturn null;} }
package com.shenzhennba.pro06.eventSource.service.impl; import org.springframework.stereotype.Service; import com.shenzhennba.pro06.eventSource.model.Product;import com.shenzhennba.pro06.eventSource.model.ProductOrder;import com.shenzhennba.pro06.eventSource.service.ProductOrderService; /** * 商品订单service接口实现类 * @author shenzhenNBA * @since 2021/06/14 */@Servicepublic class ProductOrderServiceImpl implements ProductOrderService { private static final long serialVersionUID = 20210606010100L;/** * 建立产品订单 */@Overridepublic Integer createProductOrder(ProductOrder productOrder) {// TODO more biz handle herereturn null;} /** * 根据产品订单代码查询产品订单记录 */@Overridepublic Product getProductOrder(String productOrderCode) {// TODO more biz handle herereturn null;}}

8,在对外API(即controller层)接口中处理业务,同时使用 org.springframework.context.ApplicationEventPublisher实例发布相应的Java事件,参考如下代码:

package com.shenzhennba.pro06.eventSource.controller; import java.io.Serializable; import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationEventPublisher;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody; import com.shenzhennba.pro06.eventSource.eventSource.ProductEventSource;import com.shenzhennba.pro06.eventSource.listener.EventListeners;import com.shenzhennba.pro06.eventSource.model.Product;import com.shenzhennba.pro06.eventSource.service.ProductService; /** * 使用Java事件编程方式实现, * 产品对外API接口, * @author shenzhenNBA * @since 2021/06/14 */ @Controller@RequestMapping("/product")public class ProductController implements Serializable { private static Logger logger = LoggerFactory.getLogger(EventListeners.class);private static final long serialVersionUID = 20210606010080L;//Spring的事件发布器@Autowiredprivate ApplicationEventPublisher appEventPublisher;@Autowiredprivate ProductService productService;@GetMapping(value="/add")  @ResponseBodypublic String addProduct(Product product) {logger.info("controller ProductController.addProduct(), add product");//主要业务,增加产品productService.addProduct(product);//?categoryCode=c01&productName=productName001&productCode=pc001&price=20.5&addNum=2&isHotSell=1//因增加产品而发布与其相关的事件,处理与之相关的非主要业务,//首先执行EventListeners.updateProductReferNumListener()监听器,处理相关业务//其次执行EventListeners.handleHotSellProductListener()监听器,处理相关业务//应用Spring事件,可以让非主要业务和主要业务解耦,使用异步处理非主要业务,让程序吞吐量即处理能力更强appEventPublisher.publishEvent(new ProductEventSource(product));return "产品增加完成";}}
package com.shenzhennba.pro06.eventSource.controller; import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationEventPublisher;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody; import com.shenzhennba.pro06.eventSource.eventSource.ProductOrderEventSource;import com.shenzhennba.pro06.eventSource.listener.EventListeners;import com.shenzhennba.pro06.eventSource.model.ProductOrder;import com.shenzhennba.pro06.eventSource.service.ProductOrderService; /** * 使用Java事件编程方式实现, * 产品订单对外API接口, * @author shenzhenNBA * @since 2021/06/14 */@Controller@RequestMapping("/productOrder")public class ProductOrderController { private static Logger logger = LoggerFactory.getLogger(EventListeners.class);private static final long serialVersionUID = 20210606010080L;//Spring的事件发布器@Autowiredprivate ApplicationEventPublisher appEventPublisher;@Autowiredprivate ProductOrderService productOrderService;@GetMapping(value="/createOrder")  @ResponseBodypublic String createProductOrder(ProductOrder productOrder) {logger.info("controller ProductOrderController.createProductOrder(), create product order");//主要业务,增加产品productOrderService.createProductOrder(productOrder);//?productCode=pc001&productName=productName001&price=20.5&buyNum=3&//scoreNum=0&warehouseCode=house01&recieveAddress=add001//因增加产品而发布与其相关的事件,处理与之相关的非主要业务,//首先执行EventListeners.updateProductReferNumListener()监听器,处理相关业务//其次执行EventListeners.prepareSendProductListener()监听器,处理相关业务//再次执行EventListeners.sendProductListener()监听器,处理相关业务//应用Spring事件,可以让非主要业务和主要业务解耦,使用异步处理非主要业务,让程序吞吐量即处理能力更强appEventPublisher.publishEvent(new ProductOrderEventSource(productOrder));return "产品订单建立完成";}}

9,在启动类中启用异步处理功能,参考如下代码:

package com.shenzhennba.pro06.eventSource; import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;import org.springframework.scheduling.annotation.EnableAsync; //启动异步处理功能@EnableAsync //启动springboot但不用排除DB功能@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})public class EventSourceApp {private static Logger logger = LoggerFactory.getLogger(EventSourceApp.class);public static void main(String[] args) {SpringApplication.run(EventSourceApp.class, args);logger.info("");logger.info("----------- Server is running... -----------");logger.info("");}}

10,工程配置文件,参考如下代码:

spring.application.name=eventSourceApp01 server.port=8080

11,启动工程,模拟增加一个商品,相关截图如下,可见商品增加事件已经触发,

http://localhost:8080/product/add?categoryCode=c01&productName=productName001&productCode=pc001&price=20.5&addNum=2&isHotSell=1

12,启动工程,模拟建立一个订单,相关截图如下,可见订单相关事件已经触发,

http://localhost:8080/productOrder/createOrder?productCode=pc001&productName=productName001&price=20.5&buyNum=3&scoreNum=0&warehouseCode=house01&recieveAddress=add001

13,工程 pom.xml 文件,参见如下代码:

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.0</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.shenzhennba.pro06</groupId><artifactId>eventSource</artifactId><version>0.0.1-SNAPSHOT</version><name>eventSource</name><description>Demo event source</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-rest</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- apache的依赖 --><dependency>    <groupId>commons-lang</groupId>    <artifactId>commons-lang</artifactId>    <version>2.6</version></dependency></dependencies> <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build> </project>

后记

Java的事件编程可以应用到很多的地方,合理应用可以使代码主次业务分清,业务解耦,提高程序吞吐量,但需要开发人员掌握更多的知识,只要学习应该不是很难,习惯一种编程逻辑多一种开发思路,欢迎拍砖讨论…

到此这篇关于详解Java事件编程的使用的文章就介绍到这了,更多相关Java事件编程内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

是清晰的又是朦胧的,是一杯苦茶,最后却品出清雅之香。

详解Java事件编程的使用

相关文章:

你感兴趣的文章:

标签云: