前言

本文介绍适配器模式,源码分析spring aop, jpa, mvc中的适配器模式

推荐阅读

设计模式 | 简单工厂模式及典型应用
设计模式 | 工厂方法模式及典型应用
设计模式 | 抽象工厂模式及典型应用
设计模式 | 建造者模式及典型应用
设计模式 | 原型模式及典型应用
设计模式 | 外观模式及典型应用
设计模式 | 装饰者模式及典型应用

更多内容可访问我的个人博客:laijianfeng.org

关注【小旋锋】微信公众号,及时接收博文推送

适配器模式

适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

在适配器模式中,我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。

根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。

角色

Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。

Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。

Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。

缺省适配器模式(Default Adapter Pattern):当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。缺省适配器模式是适配器模式的一种变体,其应用也较为广泛。在JDK类库的事件处理包java.awt.event中广泛使用了缺省适配器模式,如WindowAdapter、KeyAdapter、MouseAdapter等。

示例

类适配器

首先有一个已存在的将被适配的类

public class Adaptee {public void adapteeRequest() {System.out.println("被适配者的方法");}
}
复制代码

定义一个目标接口

public interface Target {void request();
}
复制代码

怎么才可以在目标接口中的 request() 调用 AdapteeadapteeRequest() 方法呢?

如果直接实现 Target 是不行的

public class ConcreteTarget implements Target {@Overridepublic void request() {System.out.println("concreteTarget目标方法");}
}
复制代码

如果通过一个适配器类,实现 Target 接口,同时继承了 Adaptee 类,然后在实现的 request() 方法中调用父类的 adapteeRequest() 即可实现

public class Adapter extends Adaptee implements Target{@Overridepublic void request() {//...一些操作...super.adapteeRequest();//...一些操作...}
}
复制代码

我们来测试一下

public class Test {public static void main(String[] args) {Target target = new ConcreteTarget();target.request();Target adapterTarget = new Adapter();adapterTarget.request();}
}
复制代码

输出

concreteTarget目标方法
被适配者的方法
复制代码

这样我们即可在新接口 Target 中适配旧的接口或类

对象适配器

对象适配器与类适配器不同之处在于,类适配器通过继承来完成适配,对象适配器则是通过关联来完成,这里稍微修改一下 Adapter 类即可将转变为对象适配器

public class Adapter implements Target{// 适配者是对象适配器的一个属性private Adaptee adaptee = new Adaptee();@Overridepublic void request() {//...adaptee.adapteeRequest();//...}
}
复制代码

注意这里的 Adapter 是将 Adaptee 作为一个成员属性,而不是继承它

电压适配器

再来一个好理解的例子,我们国家的民用电都是 220V,日本是 110V,而我们的手机充电一般需要 5V,这时候要充电,就需要一个电压适配器,将 220V 或者 100V 的输入电压变换为 5V 输出

定义输出交流电接口,输出220V交流电类和输出110V交流电类

public interface AC {int outputAC();
}public class AC110 implements AC {public final int output = 110;@Overridepublic int outputAC() {return output;}
}public class AC220 implements AC {public final int output = 220;@Overridepublic int outputAC() {return output;}
}
复制代码

适配器接口,其中 support() 方法用于检查输入的电压是否与适配器匹配,outputDC5V() 方法则用于将输入的电压变换为 5V 后输出

public interface DC5Adapter {boolean support(AC ac);int outputDC5V(AC ac);
}
复制代码

实现中国变压适配器和日本变压适配器

public class ChinaPowerAdapter implements DC5Adapter {public static final int voltage = 220;@Overridepublic boolean support(AC ac) {return (voltage == ac.outputAC());}@Overridepublic int outputDC5V(AC ac) {int adapterInput = ac.outputAC();//变压器...int adapterOutput = adapterInput / 44;System.out.println("使用ChinaPowerAdapter变压适配器,输入AC:" + adapterInput + "V" + ",输出DC:" + adapterOutput + "V");return adapterOutput;}
}public class JapanPowerAdapter implements DC5Adapter {public static final int voltage = 110;@Overridepublic boolean support(AC ac) {return (voltage == ac.outputAC());}@Overridepublic int outputDC5V(AC ac) {int adapterInput = ac.outputAC();//变压器...int adapterOutput = adapterInput / 22;System.out.println("使用JapanPowerAdapter变压适配器,输入AC:" + adapterInput + "V" + ",输出DC:" + adapterOutput + "V");return adapterOutput;}
}
复制代码

测试,准备中国变压适配器和日本变压适配器各一个,定义一个方法可以根据电压找到合适的变压器,然后进行测试

public class Test {private List<DC5Adapter> adapters = new LinkedList<DC5Adapter>();public Test() {this.adapters.add(new ChinaPowerAdapter());this.adapters.add(new JapanPowerAdapter());}// 根据电压找合适的变压器public DC5Adapter getPowerAdapter(AC ac) {DC5Adapter adapter = null;for (DC5Adapter ad : this.adapters) {if (ad.support(ac)) {adapter = ad;break;}}if (adapter == null){throw new  IllegalArgumentException("没有找到合适的变压适配器");}return adapter;}public static void main(String[] args) {Test test = new Test();AC chinaAC = new AC220();DC5Adapter adapter = test.getPowerAdapter(chinaAC);adapter.outputDC5V(chinaAC);// 去日本旅游,电压是 110VAC japanAC = new AC110();adapter = test.getPowerAdapter(japanAC);adapter.outputDC5V(japanAC);}
}
复制代码

输出

使用ChinaPowerAdapter变压适配器,输入AC:220V,输出DC:5V
使用JapanPowerAdapter变压适配器,输入AC:110V,输出DC:5V
复制代码

适配器模式总结

主要优点

  1. 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
  2. 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
  3. 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。

具体来说,类适配器模式还有如下优点:

  • 由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

对象适配器模式还有如下优点:

  • 一个对象适配器可以把多个不同的适配者适配到同一个目标;
  • 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配。

类适配器模式的缺点如下:

  1. 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;
  2. 适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;
  3. 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。

对象适配器模式的缺点如下:

  • 与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

适用场景

  • 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
  • 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

源码分析适配器模式的典型应用

spring AOP中的适配器模式

在Spring的Aop中,使用的 Advice(通知) 来增强被代理类的功能。

Advice的类型有:MethodBeforeAdviceAfterReturningAdviceThrowsAdvice

在每个类型 Advice 都有对应的拦截器,MethodBeforeAdviceInterceptorAfterReturningAdviceInterceptorThrowsAdviceInterceptor

Spring需要将每个 Advice 都封装成对应的拦截器类型,返回给容器,所以需要使用适配器模式对 Advice 进行转换

三个适配者类 Adaptee 如下:

public interface MethodBeforeAdvice extends BeforeAdvice {void before(Method var1, Object[] var2, @Nullable Object var3) throws Throwable;
}public interface AfterReturningAdvice extends AfterAdvice {void afterReturning(@Nullable Object var1, Method var2, Object[] var3, @Nullable Object var4) throws Throwable;
}public interface ThrowsAdvice extends AfterAdvice {
}
复制代码

目标接口 Target,有两个方法,一个判断 Advice 类型是否匹配,一个是工厂方法,创建对应类型的 Advice 对应的拦截器

public interface AdvisorAdapter {boolean supportsAdvice(Advice var1);MethodInterceptor getInterceptor(Advisor var1);
}
复制代码

三个适配器类 Adapter 分别如下,注意其中的 Advice、Adapter、Interceptor之间的对应关系

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {@Overridepublic boolean supportsAdvice(Advice advice) {return (advice instanceof MethodBeforeAdvice);}@Overridepublic MethodInterceptor getInterceptor(Advisor advisor) {MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();return new MethodBeforeAdviceInterceptor(advice);}
}@SuppressWarnings("serial")
class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {@Overridepublic boolean supportsAdvice(Advice advice) {return (advice instanceof AfterReturningAdvice);}@Overridepublic MethodInterceptor getInterceptor(Advisor advisor) {AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();return new AfterReturningAdviceInterceptor(advice);}
}class ThrowsAdviceAdapter implements AdvisorAdapter, Serializable {@Overridepublic boolean supportsAdvice(Advice advice) {return (advice instanceof ThrowsAdvice);}@Overridepublic MethodInterceptor getInterceptor(Advisor advisor) {return new ThrowsAdviceInterceptor(advisor.getAdvice());}
}
复制代码

客户端 DefaultAdvisorAdapterRegistry

public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {private final List<AdvisorAdapter> adapters = new ArrayList(3);public DefaultAdvisorAdapterRegistry() {// 这里注册了适配器this.registerAdvisorAdapter(new MethodBeforeAdviceAdapter());this.registerAdvisorAdapter(new AfterReturningAdviceAdapter());this.registerAdvisorAdapter(new ThrowsAdviceAdapter());}public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {List<MethodInterceptor> interceptors = new ArrayList(3);Advice advice = advisor.getAdvice();if (advice instanceof MethodInterceptor) {interceptors.add((MethodInterceptor)advice);}Iterator var4 = this.adapters.iterator();while(var4.hasNext()) {AdvisorAdapter adapter = (AdvisorAdapter)var4.next();if (adapter.supportsAdvice(advice)) {   // 这里调用适配器方法interceptors.add(adapter.getInterceptor(advisor));  // 这里调用适配器方法}}if (interceptors.isEmpty()) {throw new UnknownAdviceTypeException(advisor.getAdvice());} else {return (MethodInterceptor[])interceptors.toArray(new MethodInterceptor[0]);}}// ...省略...
}
复制代码

这里看 while 循环里,逐个取出注册的适配器,调用 supportsAdvice() 方法来判断 Advice 对应的类型,然后调用 getInterceptor() 创建对应类型的拦截器

这里应该属于对象适配器模式,关键字 instanceof 可看成是 Advice 的方法,不过这里的 Advice 对象是从外部传进来,而不是成员属性

spring JPA中的适配器模式

在Spring的ORM包中,对于JPA的支持也是采用了适配器模式,首先定义了一个接口的 JpaVendorAdapter,然后不同的持久层框架都实现此接口。

jpaVendorAdapter:用于设置实现厂商JPA实现的特定属性,如设置Hibernate的是否自动生成DDL的属性generateDdl;这些属性是厂商特定的,因此最好在这里设置;目前Spring提供 HibernateJpaVendorAdapterOpenJpaVendorAdapterEclipseLinkJpaVendorAdapterTopLinkJpaVendorAdapter 四个实现。其中最重要的属性是 database,用来指定使用的数据库类型,从而能根据数据库类型来决定比如如何将数据库特定异常转换为Spring的一致性异常,目前支持如下数据库(DB2、DERBY、H2、HSQL、INFORMIX、MYSQL、ORACLE、POSTGRESQL、SQL_SERVER、SYBASE)

public interface JpaVendorAdapter
{// 返回一个具体的持久层提供者public abstract PersistenceProvider getPersistenceProvider();// 返回持久层提供者的包名public abstract String getPersistenceProviderRootPackage();// 返回持久层提供者的属性public abstract Map<String, ?> getJpaPropertyMap();// 返回JpaDialectpublic abstract JpaDialect getJpaDialect();// 返回持久层管理器工厂public abstract Class<? extends EntityManagerFactory> getEntityManagerFactoryInterface();// 返回持久层管理器public abstract Class<? extends EntityManager> getEntityManagerInterface();// 自定义回调方法public abstract void postProcessEntityManagerFactory(EntityManagerFactory paramEntityManagerFactory);
}
复制代码

我们来看其中一个适配器实现类 HibernateJpaVendorAdapter

public class HibernateJpaVendorAdapter extends AbstractJpaVendorAdapter {//设定持久层提供者private final PersistenceProvider persistenceProvider;//设定持久层方言private final JpaDialect jpaDialect;public HibernateJpaVendorAdapter() {this.persistenceProvider = new HibernatePersistence();this.jpaDialect = new HibernateJpaDialect();}//返回持久层方言public PersistenceProvider getPersistenceProvider() {return this.persistenceProvider;}//返回持久层提供者public String getPersistenceProviderRootPackage() {return "org.hibernate";}//返回JPA的属性public Map<String, Object> getJpaPropertyMap() {Map jpaProperties = new HashMap();if (getDatabasePlatform() != null) {jpaProperties.put("hibernate.dialect", getDatabasePlatform());} else if (getDatabase() != null) {Class databaseDialectClass = determineDatabaseDialectClass(getDatabase());if (databaseDialectClass != null) {jpaProperties.put("hibernate.dialect",databaseDialectClass.getName());}}if (isGenerateDdl()) {jpaProperties.put("hibernate.hbm2ddl.auto", "update");}if (isShowSql()) {jpaProperties.put("hibernate.show_sql", "true");}return jpaProperties;}//设定数据库protected Class determineDatabaseDialectClass(Database database)     {                                                                                       switch (1.$SwitchMap$org$springframework$orm$jpa$vendor$Database[database.ordinal()]) {                                                                                     case 1:                                                                             return DB2Dialect.class;                                                            case 2:                                                                               return DerbyDialect.class;                                                          case 3:                                                                               return H2Dialect.class;                                                             case 4:                                                                               return HSQLDialect.class;                                                           case 5:                                                                               return InformixDialect.class;                                                       case 6:                                                                               return MySQLDialect.class;                                                          case 7:                                                                               return Oracle9iDialect.class;                                                       case 8:                                                                               return PostgreSQLDialect.class;                                                     case 9:                                                                               return SQLServerDialect.class;                                                      case 10:                                                                              return SybaseDialect.class; }                                                       return null;              }//返回JPA方言public JpaDialect getJpaDialect() {return this.jpaDialect;}//返回JPA实体管理器工厂public Class<? extends EntityManagerFactory> getEntityManagerFactoryInterface() {return HibernateEntityManagerFactory.class;}//返回JPA实体管理器public Class<? extends EntityManager> getEntityManagerInterface() {return HibernateEntityManager.class;}
}
复制代码

配置文件中可以这样指定

<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="generateDdl" value="false" />  <property name="database" value="HSQL"/>
</bean>
<bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
复制代码

spring MVC中的适配器模式

Spring MVC中的适配器模式主要用于执行目标 Controller 中的请求处理方法。

在Spring MVC中,DispatcherServlet 作为用户,HandlerAdapter 作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller 作为需要适配的类。

为什么要在 Spring MVC 中使用适配器模式?Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,需要的自行来判断,像下面这段代码一样:

if(mappedHandler.getHandler() instanceof MultiActionController){  ((MultiActionController)mappedHandler.getHandler()).xxx
}else if(mappedHandler.getHandler() instanceof XXX){  ...
}else if(...){  ...
}
复制代码

这样假设如果我们增加一个 HardController,就要在代码中加入一行 if(mappedHandler.getHandler() instanceof HardController),这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。

我们来看看源码,首先是适配器接口 HandlerAdapter

public interface HandlerAdapter {boolean supports(Object var1);ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;long getLastModified(HttpServletRequest var1, Object var2);
}
复制代码

现该接口的适配器每一个 Controller 都有一个适配器与之对应,这样的话,每自定义一个 Controller 需要定义一个实现 HandlerAdapter 的适配器。

springmvc 中提供的 Controller 实现类有如下

springmvc 中提供的 HandlerAdapter 实现类如下

HttpRequestHandlerAdapter 这个适配器代码如下

public class HttpRequestHandlerAdapter implements HandlerAdapter {public HttpRequestHandlerAdapter() {}public boolean supports(Object handler) {return handler instanceof HttpRequestHandler;}public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {((HttpRequestHandler)handler).handleRequest(request, response);return null;}public long getLastModified(HttpServletRequest request, Object handler) {return handler instanceof LastModified ? ((LastModified)handler).getLastModified(request) : -1L;}
}
复制代码

当Spring容器启动后,会将所有定义好的适配器对象存放在一个List集合中,当一个请求来临时,DispatcherServlet 会通过 handler 的类型找到对应适配器,并将该适配器对象返回给用户,然后就可以统一通过适配器的 hanle() 方法来调用 Controller 中的用于处理请求的方法。

public class DispatcherServlet extends FrameworkServlet {private List<HandlerAdapter> handlerAdapters;//初始化handlerAdaptersprivate void initHandlerAdapters(ApplicationContext context) {//..省略...}// 遍历所有的 HandlerAdapters,通过 supports 判断找到匹配的适配器protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {for (HandlerAdapter ha : this.handlerAdapters) {if (logger.isTraceEnabled()) {logger.trace("Testing handler adapter [" + ha + "]");}if (ha.supports(handler)) {return ha;}}}// 分发请求,请求需要找到匹配的适配器来处理protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;// Determine handler for the current request.mappedHandler = getHandler(processedRequest);// 确定当前请求的匹配的适配器.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());ha.getLastModified(request, mappedHandler.getHandler());mv = ha.handle(processedRequest, response, mappedHandler.getHandler());}// ...省略...
}
复制代码

通过适配器模式我们将所有的 controller 统一交给 HandlerAdapter 处理,免去了写大量的 if-else 语句对 Controller 进行判断,也更利于扩展新的 Controller 类型。

参考:
刘伟:设计模式Java版
慕课网java设计模式精讲 Debug 方式+内存分析
孤落:Spring MVC中的适配器模式
ToughMind_:深入浅出设计模式(五):7.适配器模式

设计模式 | 适配器模式及典型应用相关推荐

  1. 设计模式 | 观察者模式及典型应用

    本文主要内容: 介绍观察者模式 微信公众号的发布/订阅示例 观察者模式总结 分析观察者模式的典型应用 JDK 提供的观察者接口中的观察者模式 Guava EventBus 中的观察者模式 JDK 委托 ...

  2. 简述事件接口与事件适配器的联系与区别_设计模式——适配器模式

    适配器模式 适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper).适配器模式既可以作为类结构型模式,也 ...

  3. Python设计模式-适配器模式

    Python设计模式-适配器模式 基于Python3.5.2,代码如下 #coding:utf-8class ACpnStaff:name = ""id = "" ...

  4. Java | 设计模式-适配器模式

    继代理模式后又来到适配器模式啦,想看之前的也有哦.持续更新中哦.让我们一起加油吧兄弟们,干他. 很喜欢一句话:"八小时内谋生活,八小时外谋发展". 你好,如果喜欢,请一起坚持!! ...

  5. java设计模式适配器模式_Java中的适配器设计模式

    java设计模式适配器模式 适配器设计模式是一种结构设计模式 ,可以帮助我们连接到通过不同接口公开相似功能的旧版或第三方代码. 适配器的现实世界是我们用来将USB电缆连接到以太网端口的类比. 在设计一 ...

  6. 设计模式适配器模式_适配器设计模式示例

    设计模式适配器模式 本文是我们名为" Java设计模式 "的学院课程的一部分. 在本课程中,您将深入研究大量的设计模式,并了解如何在Java中实现和利用它们. 您将了解模式如此重要 ...

  7. 设计模式适配器模式_21世纪的设计模式:适配器模式

    设计模式适配器模式 这是我的演讲的第三部分," 21世纪的设计模式" . 适配器模式桥接世界. 在一个世界中,我们有一个概念的界面. 在另一个世界,我们有不同的界面. 这两个接口有 ...

  8. java设计模式适配器模式_Java解释器设计模式

    java设计模式适配器模式 Interpreter design pattern is one of the behavioral design pattern. Interpreter patter ...

  9. Java代码审计-设计模式-适配器模式

    Java设计模式-适配器模式(Adapter Pattern) 目录 什么是适配器模式 适配器模式的3种类型 JavaSE适配器模式的应用 Struts2适配器模式的应用 适配器模式是一种" ...

最新文章

  1. UA MATH566 统计理论 QE练习题2.2
  2. Head First HTML与CSS阅读笔记(二)
  3. csp怎么给线条描边_PS的四种“描边”方式你都知道吗?Photoshop小知识
  4. ASP.NET Core 2.0 : 图说管道,唐僧扫塔的故事
  5. 基于java的订餐系统设计(含源文件)
  6. java前端编译和后端编译理解
  7. linux不用清理内存吗,清理不必要的Ubuntu磁盘空间占用
  8. type-aliases-package不生效问题记录
  9. 沐神点赞!同济子豪兄精读AI经典论文,包括图像分类、目标检测、生成对抗网络、轻量化卷积神经网络等领域...
  10. 2018最新精选的Go框架,库和软件的精选列表 三
  11. 阿里云学生成长计划领取资格考试答案
  12. html爆炸效果,HTML5 SVG炫酷文字爆炸特效
  13. github上能找到中文博主吗_Lyx的安装流程(windows10系统)及配置中文环境
  14. 超级记忆/图像数字记忆 110位数字图像转换表 01-10
  15. 关于split的用法与注意事项
  16. matlab内弹道程序计算,火炮内弹道求解与计算
  17. 常见FC,SAS,SATA接口硬盘的区别
  18. 10 面阿里,7 面头条,6 个 offer, 你猜我进阿里没?
  19. 2017年高教社杯全国大学生数学建模竞赛题目 B题 “拍照赚钱”的任务定价
  20. Unity 3D俄罗斯方块

热门文章

  1. 修改Jtable字体颜色
  2. 中导入pygame_Pygame(二)--线条的艺术
  3. int** 赋值_Python的赋值、浅拷贝、深拷贝之间的区别
  4. python反转字符串的元音字母_345. 反转字符串中的元音字母-----leetcode刷题(python解题)...
  5. 计算机组成原理实验八报告,计算机组成原理实验八报告
  6. android调用oncreate,Android - 每次启动时都会调用onCreate
  7. 编程实现基于二维易位置换机制进行信息加解密_基于TEE的TBOX安全技术
  8. java 命名管道_Java中命名管道的并发读/写(在Windows上)
  9. mysql8连接数据库显示cache_MySQL数据库之mysql 8.0 java连接报错:Unknown system variable 'query_cache_size'...
  10. java freememory 单位_Runtime类中的freeMemory,totalMemory,maxMemory区别