当前位置: 首页 > news >正文

cola架构:一种扩展点的实现思路浅析

目录

1.扩展点使用实例

2.主要技术点

2.1 注解加持

2.2 注解解析

2.3 扩展点路由


在实际项目中,我们经常使用策略模式、或者状态模式来隔离同一接口下不同的实现逻辑,进而消除代码中ifelse硬编码分支,使代码结构更清晰,也大大提升了代码可读性;同时也满足了“开闭原则”,具备更高的可扩展性;

在cola架构中,给出了一种“扩展点”的思路,本质还是策略模式的实现方式,通过“扩展点注解”的组装方式将策略模式实现类注册到容器中,供后续场景逻辑决策使用;

1.扩展点使用实例

首先通过实例了解下,cola 扩展点的使用方式:

1.首先定义一个SomeExtPt接口,并实现ExtensionPointI接口

public interface SomeExtPt extends ExtensionPointI {public void doSomeThing();
}

2.具体实现SomeExtPt接口,这里给出了2个实现类,如下:

@Extension(bizId = "A")
@Component
public class SomeExtensionA implements SomeExtPt {@Overridepublic void doSomeThing() {System.out.println("SomeExtensionA::doSomething");}}
@Extension(bizId = "B")
@Component
public class SomeExtensionB implements SomeExtPt {@Overridepublic void doSomeThing() {System.out.println("SomeExtensionB::doSomething");}}

3.测试方法:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class ExtensionRegisterTest {@Resourceprivate ExtensionRegister register;@Resourceprivate ExtensionExecutor executor;@Testpublic void test() {SomeExtPt extA = new SomeExtensionA();register.doRegistration(extA);SomeExtPt extB = CglibProxyFactory.createProxy(new SomeExtensionB());register.doRegistration(extB);executor.executeVoid(SomeExtPt.class, BizScenario.valueOf("A"), SomeExtPt::doSomeThing);executor.executeVoid(SomeExtPt.class, BizScenario.valueOf("B"), SomeExtPt::doSomeThing);}}
public class CglibProxyFactory {public static <T> T createProxy(T object) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(object.getClass());enhancer.setCallback(new ProxyCallback(object));return (T) enhancer.create();}public static class ProxyCallback implements MethodInterceptor {private Object target;public ProxyCallback(Object target) {this.target = target;}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("ProxyObject::before");Object object = proxy.invoke(target, args);System.out.println("ProxyObject::after");return object;}}
}

2.主要技术点

2.1 注解加持

上述具体策略接口实现方法标注了扩展点注解:@Extension

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Repeatable(Extensions.class)
@Component
public @interface Extension {String bizId()  default BizScenario.DEFAULT_BIZ_ID;String useCase() default BizScenario.DEFAULT_USE_CASE;String scenario() default BizScenario.DEFAULT_SCENARIO;
}

所有的具体实现方法都需要标注该注解,表明该类属于一个扩展点;同时,由于标注了@Component注解,表明每一个扩展点也是一个bean实例;

2.2 注解解析

扩展点注解的解析工作主要借助类ExtensionBootstrap和ExtensionRegister完成:

@Component
public class ExtensionBootstrap implements ApplicationContextAware {@Resourceprivate ExtensionRegister extensionRegister;private ApplicationContext applicationContext;@PostConstructpublic void init(){Map<String, Object> extensionBeans = applicationContext.getBeansWithAnnotation(Extension.class);extensionBeans.values().forEach(extension -> extensionRegister.doRegistration((ExtensionPointI) extension));// handle @Extensions annotationMap<String, Object> extensionsBeans = applicationContext.getBeansWithAnnotation(Extensions.class);extensionsBeans.values().forEach( extension -> extensionRegister.doRegistrationExtensions((ExtensionPointI) extension));}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}
@Component
public class ExtensionRegister {/*** 扩展点接口名称不合法*/private static final String EXTENSION_INTERFACE_NAME_ILLEGAL = "extension_interface_name_illegal";/*** 扩展点不合法*/private static final String EXTENSION_ILLEGAL = "extension_illegal";/*** 扩展点定义重复*/private static final String EXTENSION_DEFINE_DUPLICATE = "extension_define_duplicate";@Resourceprivate ExtensionRepository extensionRepository;public final static String EXTENSION_EXTPT_NAMING = "ExtPt";public void doRegistration(ExtensionPointI extensionObject) {Class<?> extensionClz = extensionObject.getClass();if (AopUtils.isAopProxy(extensionObject)) {extensionClz = ClassUtils.getUserClass(extensionObject);}Extension extensionAnn = AnnotationUtils.findAnnotation(extensionClz, Extension.class);BizScenario bizScenario = BizScenario.valueOf(extensionAnn.bizId(), extensionAnn.useCase(), extensionAnn.scenario());ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(calculateExtensionPoint(extensionClz), bizScenario.getUniqueIdentity());ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionCoordinate, extensionObject);if (preVal != null) {String errMessage = "Duplicate registration is not allowed for :" + extensionCoordinate;throw new ExtensionException(EXTENSION_DEFINE_DUPLICATE, errMessage);}}public void doRegistrationExtensions(ExtensionPointI extensionObject){Class<?> extensionClz = extensionObject.getClass();if (AopUtils.isAopProxy(extensionObject)) {extensionClz = ClassUtils.getUserClass(extensionObject);}Extensions extensionsAnnotation = AnnotationUtils.findAnnotation(extensionClz, Extensions.class);Extension[] extensions = extensionsAnnotation.value();if (!ObjectUtils.isEmpty(extensions)){for (Extension extensionAnn : extensions) {BizScenario bizScenario = BizScenario.valueOf(extensionAnn.bizId(), extensionAnn.useCase(), extensionAnn.scenario());ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(calculateExtensionPoint(extensionClz), bizScenario.getUniqueIdentity());ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionCoordinate, extensionObject);if (preVal != null) {String errMessage = "Duplicate registration is not allowed for :" + extensionCoordinate;throw new ExtensionException(EXTENSION_DEFINE_DUPLICATE, errMessage);}}}//String[] bizIds = extensionsAnnotation.bizId();String[] useCases = extensionsAnnotation.useCase();String[] scenarios = extensionsAnnotation.scenario();for (String bizId : bizIds) {for (String useCase : useCases) {for (String scenario : scenarios) {BizScenario bizScenario = BizScenario.valueOf(bizId, useCase, scenario);ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(calculateExtensionPoint(extensionClz), bizScenario.getUniqueIdentity());ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionCoordinate, extensionObject);if (preVal != null) {String errMessage = "Duplicate registration is not allowed for :" + extensionCoordinate;throw new ExtensionException(EXTENSION_DEFINE_DUPLICATE, errMessage);}}}}}/*** @param targetClz* @return*/private String calculateExtensionPoint(Class<?> targetClz) {Class<?>[] interfaces = ClassUtils.getAllInterfacesForClass(targetClz);if (interfaces == null || interfaces.length == 0) {throw new ExtensionException(EXTENSION_ILLEGAL, "Please assign a extension point interface for " + targetClz);}for (Class intf : interfaces) {String extensionPoint = intf.getSimpleName();if (extensionPoint.contains(EXTENSION_EXTPT_NAMING)) {return intf.getName();}}String errMessage = "Your name of ExtensionPoint for " + targetClz +" is not valid, must be end of " + EXTENSION_EXTPT_NAMING;throw new ExtensionException(EXTENSION_INTERFACE_NAME_ILLEGAL, errMessage);}}

最终将扩展点和决策条件的映射关系存储到ExtensionRepository中:

@Component
public class ExtensionRepository {public Map<ExtensionCoordinate, ExtensionPointI> getExtensionRepo() {return extensionRepo;}private Map<ExtensionCoordinate, ExtensionPointI> extensionRepo = new HashMap<>();}

2.3 扩展点路由

在实际业务场景调度过程中,会调用ExtensionExecutor的方法locateExtension完成扩展点的查找,最终执行扩展点逻辑;

@Component
public class ExtensionExecutor extends AbstractComponentExecutor {private static final String EXTENSION_NOT_FOUND = "extension_not_found";private Logger logger = LoggerFactory.getLogger(ExtensionExecutor.class);@Resourceprivate ExtensionRepository extensionRepository;@Overrideprotected <C> C locateComponent(Class<C> targetClz, BizScenario bizScenario) {C extension = locateExtension(targetClz, bizScenario);logger.debug("[Located Extension]: " + extension.getClass().getSimpleName());return extension;}/*** if the bizScenarioUniqueIdentity is "ali.tmall.supermarket"* <p>* the search path is as below:* 1、first try to get extension by "ali.tmall.supermarket", if get, return it.* 2、loop try to get extension by "ali.tmall", if get, return it.* 3、loop try to get extension by "ali", if get, return it.* 4、if not found, try the default extension** @param targetClz*/protected <Ext> Ext locateExtension(Class<Ext> targetClz, BizScenario bizScenario) {checkNull(bizScenario);Ext extension;logger.debug("BizScenario in locateExtension is : " + bizScenario.getUniqueIdentity());// first try with full namespaceextension = firstTry(targetClz, bizScenario);if (extension != null) {return extension;}// second try with default scenarioextension = secondTry(targetClz, bizScenario);if (extension != null) {return extension;}// third try with default use case + default scenarioextension = defaultUseCaseTry(targetClz, bizScenario);if (extension != null) {return extension;}String errMessage = "Can not find extension with ExtensionPoint: " +targetClz + " BizScenario:" + bizScenario.getUniqueIdentity();throw new ExtensionException(EXTENSION_NOT_FOUND, errMessage);}/*** first try with full namespace* <p>* example:  biz1.useCase1.scenario1*/private <Ext> Ext firstTry(Class<Ext> targetClz, BizScenario bizScenario) {logger.debug("First trying with " + bizScenario.getUniqueIdentity());return locate(targetClz.getName(), bizScenario.getUniqueIdentity());}/*** second try with default scenario* <p>* example:  biz1.useCase1.#defaultScenario#*/private <Ext> Ext secondTry(Class<Ext> targetClz, BizScenario bizScenario) {logger.debug("Second trying with " + bizScenario.getIdentityWithDefaultScenario());return locate(targetClz.getName(), bizScenario.getIdentityWithDefaultScenario());}/*** third try with default use case + default scenario* <p>* example:  biz1.#defaultUseCase#.#defaultScenario#*/private <Ext> Ext defaultUseCaseTry(Class<Ext> targetClz, BizScenario bizScenario) {logger.debug("Third trying with " + bizScenario.getIdentityWithDefaultUseCase());return locate(targetClz.getName(), bizScenario.getIdentityWithDefaultUseCase());}private <Ext> Ext locate(String name, String uniqueIdentity) {final Ext ext = (Ext) extensionRepository.getExtensionRepo().get(new ExtensionCoordinate(name, uniqueIdentity));return ext;}private void checkNull(BizScenario bizScenario) {if (bizScenario == null) {throw new IllegalArgumentException("BizScenario can not be null for extension");}}}

相关文章:

cola架构:一种扩展点的实现思路浅析

目录 1.扩展点使用实例 2.主要技术点 2.1 注解加持 2.2 注解解析 2.3 扩展点路由 在实际项目中&#xff0c;我们经常使用策略模式、或者状态模式来隔离同一接口下不同的实现逻辑&#xff0c;进而消除代码中ifelse硬编码分支&#xff0c;使代码结构更清晰&#xff0c;也大大…...

Thread常用API

setname方法每个线程取名 需要创建构造器 线程设置名字 package Thread_api_test;// 继承Thread类 public class MyThread extends Thread {//创建构造器 线程设置名字public MyThread(String name){super(name);}Overridepublic void run() {super.run();Thread mThread.cur…...

系列九、Redis的发布订阅

一、概述 Redis的发布订阅是进程间的一种消息通信模式&#xff0c;发送者&#xff08;Publisher&#xff09;发送消息&#xff0c;订阅者&#xff08;Subscriber&#xff09;接收消息。 二、命令 三、案例演示 3.1、订阅单个 客户端订阅 cctv-1、cctv-2、 cctv-3三个频道 SUB…...

《TypeScript》系列之对比JavaScript,TypeScript的优势

概述 TypeScript是微软公司开发的一种基于JavaScript语言的编程语言&#xff0c;它的目的并不是创造一种全新的语言&#xff0c;而是增强JavaScript的功能&#xff0c;使其更适合多人合作的企业级项目。TypeScript可以看做是JavaScript的超集&#xff0c;即它继承了后者的全部…...

Notepad++使用技巧

显示远程连接的文件目录 自动完成&#xff1a;函数自动提示 自动输入&#xff1a;输入一半括号自动补全另一半 自动关联 .pc文件识别为C 列模式 按住Alt不松手&#xff0c;可以直接范围选择&#xff0c;便于编辑选择的区域 关键行筛选 1.进入搜索页面的标记 2.选中标…...

React 中报Type error: Could not find a declaration file for module ‘qs‘.(已解决)

ts项目引入qs 比如 nextjs 或者reactjs npm install --save-dev types/qs 确保你的tsconfig.json文件中有以下设置 {"compilerOptions": {"types": ["qs"]}} 如果你的项目中没有tsconfig.json文件&#xff0c;你可以创建一个。在项目根目录下创…...

【Linux】:Linux环境与版本

以下哪个命令输出Linux内核的版本信息 A.uname -r B.vmstat C.sar D.stat uname -r 查看linux内核版本信息 vmstat 报告关于内核线程、虚拟内存、磁盘、陷阱和 CPU 活动的统计信息 sar 主要帮助我们掌握系统资源的使用情况&#xff0c;特别是内存和CPU的使用情况 stat 用于显示…...

PNG转EPS,包括Latex导入

在电脑TEXLIVE文件夹里中找到bmeps.exe TEXLIVE\2022\bin\win32 可以新建一个文件夹picture&#xff08;图片和exe文件必须在一个文件夹里&#xff09;&#xff0c;将bmeps.exe复制出来&#xff0c;方便后续大量图片操作 导入png图片 新建一个txt文件&#xff0c;命名为Fig1.…...

Visual Studio 2022 cmake编译 PP-OCRv4

1 环境准备 下载PaddleOCR PaddleOCR C 部署代码位于 PaddleOCR\deploy\cpp_infer目录下 paddle_inference paddle_inference opencv 这里使用已经安装好的opencv4.5.5下载dirent-master.zip 下载dirent-master.zip, 解压并复制dirent.h文件到PaddleOCR\deploy\cpp_infer目录下…...

学习笔记|串口与PC通信的接线|移植驱动程序|串口通信实战|STC32G单片机视频开发教程(冲哥)|第二十一集(上):串口与PC通信

目录 1.串口与PC通信的接线芯片与芯片之间的通讯&#xff1a;芯片与电脑之间的通讯&#xff1a; 2.利用实验箱示例代码移植驱动程序3.串口通信实战最小工程代码移植编译提示错误&#xff1a;undefined identifier 1.串口与PC通信的接线 上节试验是串口和电脑进行一个通信&…...

关于Mybaits缓存....

记Mybaits缓存踩的坑 1.问题提出 最近开发一个记录操作前后修改内容的功能&#xff0c;获取修改前数据比较简单&#xff0c;直接从数据库获取&#xff0c;记录修改后的功能也比较简单&#xff0c;直接将用户修改的内容封装成po对象&#xff0c;然后两个比对就可以了&#xff…...

Vue axios调用springboot接口获取数据库数据并显示到网页

axios调用接口获取数据 可以查看简述化的此文 点击 此文简述化文章 PS**由于我自己的本次springboot项目内容很多&#xff0c;所以只是截取了其中关于axios调用接口获取数据的内容&#xff0c;还请大家了解工作原理即可** 前端 添加axios和vue2链接 <script src"htt…...

12-bean创建流程3

文章目录 1 bean实例化前 2. bean实例化doCreateBean&#xff08;&#xff09; 1 bean实例化前 createBean方法里面的resolveBeforeInstantiation方法,InstantiationAwareBeanPostProcessor接口创建一个代理对象返回 try {// Give BeanPostProcessors a chance to return a p…...

volatile关键字 和 i = i + 1过程

本文是复制粘贴,请直接看原文 原文链接:Java并发编程&#xff1a;volatile关键字解析 - Matrix海子 - 博客园 (cnblogs.com) ------------------------------------------------------------------------------------------------------------------- Java并发编程&#xff1…...

ubuntu20 安装 cmake 3.27

1. 下载cmake3.27 建议从cmake官网下载安装&#xff0c;虽然比较慢&#xff0c;但从清华镜像里下载的cmake文件不全。 我下载的是&#xff1a;cmake-3.27.7.tar.gz 博客 ubuntu安装cmake的三种方法&#xff08;超方便&#xff01;&#xff09;-CSDN博客 里面提供了三种方法&am…...

faster lio 回环 加入GTSAM优化的记录

首先感谢这位博主的文章&#xff1a;https://blog.csdn.net/weixin_41281151/article/details/125371285&#xff0c;其中部分代码参考于改博主中的github&#xff1a; https://github.com/kahowang/FAST_LIO_SAM 不同的是&#xff0c;我使用的是faster lio进行更改&#xff0c…...

深入剖析 深度学习中 __init()__函数和forward()函数

目录 前言1. __init()__函数2. forward()函数3. 两者关系 前言 再看代码时&#xff0c;发现init函数和forward函数都有参数&#xff0c;具体是怎么传参的呢&#xff1f; 为了更方便的讲解&#xff0c;会举简单的代码例子结合讲解。 forward() 和 __init__() 是神经网络模型类…...

BUUCTF学习(一):SQL注入,万能密码

1、场景 2、题目 3、解题 用户名&#xff1a;admin or 11# 密码&#xff1a;123456 4、解析SQL注入 “SQL注入是一种常见的Web应用程序漏洞&#xff0c;攻击者可以通过注入的SQL语句获取数据库的敏感信息&#xff0c;对网站用户的数据安全造成威胁。SQL注入的特点包括广泛性、隐…...

基于springboot实现心灵治愈心理健康平台系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现心灵心理健康平台系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个心灵治愈交流平台 &#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论…...

百度Apollo自动驾驶

百度从2013年开始布局自动驾驶领域&#xff0c;十年来一直坚持压强式的、马拉松式的研发投入&#xff0c;以技术创新驱动长期发展。百度Apollo L4级自动驾驶运营测试里程累计已超5000万公里&#xff0c;拥有自动驾驶专利族超4600件&#xff0c;其中高级别自动驾驶专利族数全球第…...

数据迁移库工具-C版-01-HappySunshineV1.0-(支持Gbase8a)

一、测试环境信息 名称值CPUIntel(R) Core(TM) i5-1035G1 CPU 1.00GHz操作系统CentOS Linux release 7.9.2009 (Core)内存3G逻辑核数2Gbase8a版本8.6.2-R43.34.27468a27HappySunshine版本V1.0 二、支持功能 序号功能1GBASE8a到GBASE8a的库级数据迁移。2批量加载。&#xff…...

【sv】 pack/unpack stream

https://www.amiq.com/consulting/2017/05/29/how-to-pack-data-using-systemverilog-streaming-operators/ https://www.amiq.com/consulting/2017/06/23/how-to-unpack-data-using-the-systemverilog-streaming-operators/...

二、使用DockerCompose部署RocketMQ

使用DockerCompose进行部署 docker-compose的版本为 Docker Compose version v2.2.3 RocketMQ的部署方式以及各自的特点 单master模式 只有一个 master 节点&#xff0c;如果master节点挂掉了&#xff0c;会导致整个服务不可用&#xff0c;线上不宜使用&#xff0c;适合个人学习…...

论文笔记[156]PARAFAC. tutorial and applications

原文下载&#xff1a;https://www.sciencedirect.com/science/article/abs/pii/S0169743997000324 摘要 本文介绍了PARAFAC的多维分解方法及其在化学计量学中的应用。PARAFAC是PCA对高阶数组的推广&#xff0c;但该方法的一些特性与普通的二维情况截然不同。例如&#xff0c;…...

AKKA.Net 的使用 来自CHATGPT

请用C# 语言实现一个自动化设备 流水线调度模型&#xff0c;流水线各个环节需要并行执行&#xff1a; 下面是一个使用C#语言实现自动化设备流水线调度模型的简单示例。该示例使用并发编程库System.Threading.Tasks来实现流水线各个环节的并行执行。 csharp using System; usi…...

网络安全—小白学习笔记

1.网络安全是什么 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高&#xff1b; 二、则是发展相对成熟入…...

OpenRemote: Java 开源 IoT 物联网开发平台,匹配智慧城市、智能家居、能源管理

OpenRemote 是一个直观、用户友好的基于Java语言的开源 IoT 物联网设备管理平台&#xff0c;它包括从连接设备到构建应用程序和特定领域的智能应用程序的所有功能和特性。通过OpenRemote物联网平台&#xff0c;用户可以收集和处理来自不同设备的传感器数据&#xff0c;适用于智…...

GO-unioffice实现word编辑

导包 import ("fmt""log""os""time""github.com/unidoc/unioffice/common/license""github.com/unidoc/unioffice/document" ) 创建word文件 func CreateFile(name string) {filename : name ".docx&quo…...

SpringMVC的拦截器(Interceptor)

拦截器简介 SpringMVC的拦截器Interceptor&#xff0c;主要是对Controller资源访问时进行拦截的基本操作的技术&#xff0c;当然拦截后可以进行权限控制&#xff0c;功能增强等都是可以的。拦截器类似于JavaWeb开发中的Filter&#xff0c;他们之间的区别如下图所示 Filter技术…...

【git】gitlab常用命令

gitlab官网 官网&#xff1a;官网 中文官网&#xff1a;中文官网 默认的gitlab安装目录 /opt/gitlab/bin 启动 gitlab-ctl start 查看状态 gitlab-ctl status 停止 gitlab-ctl stop 重启GitLab gitlab-ctl restart 查看gitlab的配置文件 配置的路径是&#xff1a;/…...

温州哪里有网站建设/公司做网络推广哪个网站好

消防安全知识讲座观后感 500字 [篇1]我们一起聆听了消防知识普及专题讲座&#xff0c;受益匪浅。主讲者从鲜活的案例入题&#xff0c;讲述了近一两年发生在我国的重特大火灾事故及其引发的原因&#xff0c;给我们敲响了警钟。消防安全在日常工作中往往被忽视&#xff0c;我们往…...

找网站开发/开发新客户的十大渠道

S参数描述了RF网络的基本特征&#xff0c;其主要类型有小信号、大信号、脉冲、冷模式和混合模式S参数。 引言 本文延续之前的一系列短文&#xff0c;旨在为非RF工程师讲解RF的奥秘。其中一些RF文章如下&#xff1a;“RF揭秘——了解波反射”&#xff0c;探讨了波反射&#xf…...

云浮 网站建设/微信朋友圈广告投放代理

真的没想到随手写的一篇小笔记会引起那么多关注&#xff0c;真是瑟瑟发抖。读研之后&#xff0c;才开始接触pytorch, 理解的难免有很多错误&#xff0c;感谢各位大佬指出的错误和提出的意见&#xff0c;我会慢慢修改的。评论区有大佬说nvidia的apex.distributeddataparallel也是…...

购物网站排名 2019/搜索引擎登录入口

异步调用除了可以使用多线程以外&#xff0c;spring自已也实现了通过注解进行异步调用的功能&#xff0c;我们只需要进行一些简单的配置&#xff0c;并且在需要异步调用的方法上添加对应的注解即可。 在applicationContext.xml中添加如下&#xff1a; <task:annotation-dri…...

上饶便宜的网站建设/百度手机助手应用商店

以163为例 首先打开进程管理周到邮箱大师 右键属性打开最右边菜单取到 mailmaster.exe winr 后输入 regedit 然后按f3 输入mailmaster.exe 然后就可以找到了...

wordpress插件漏洞/网站seo外包公司有哪些

JavaScript通常被认为是最容易入门却最难以掌握的编程语言。我完全赞同这种说法。这是因为JavaScript是一门非常古老却又非常灵活的语言。它有着各种各样神秘的语法和古老的特性。我是一个JavaScript老用户&#xff0c;直到现在&#xff0c;我仍然会时不时地发现一些我从来都不…...