往期热门文章:

1,《往期精选优秀博文都在这里了!》

2、面试 Google, 我失败了!

3、去掉烦人的 “ ! = null " (判空语句)

4、干掉可恶的 "try catch "!

5、图解Spring循环依赖,看过之后面试再也不用慌了!

作者 | Baldwin_KeepMind

责编 | 伍杏玲

出品 | CSDN博客

2019年6月,我通过社招入职现在所工作的公司,理论上应该有三个月时间的试用期,只有试用期表现良好我才有机会转正,但因为一次优化代码过程中造了一个轮子,我获得了一个月转正的机会。

我是一个懒人,又特别喜欢琢磨,在工作的过程中我发现有一个模块运行非常的慢,主要原因是在这个模块种需要大量的进行数据库操作,而目前公司业务扩大,在数据库中已经有上亿条数据,每次对这个表进行操作,都需要花费将近3S的时间,而实际上,整个流程走下来,程序也就花费4S的时间,所以我就特别想把这段代码优化一下,将代码的耗时降低下来,经过一个星期的努力,轮子的初版发布,同事们用完之后都觉得不错,然后老大就给我递交了提前转正申请,我工作一个月就顺利转正了。

现在将我当时造轮子的主要思路在这里写下来,希望能给你一点启发。

轮子相关

首先,我们现在所说的轮子可不是汽车上的轮子,之所以叫“轮子”,是为了更好的理解。

汽车轮子是圆的,这种圆形轮子已经被各界广泛认可是比较好的结构,我们无论怎么去做,也很难超越“圆轮子”,所以我们只能在“圆轮子”的基础上去<重复发明轮子>,即是所说的造轮子。但是有一句话叫做“不要重复造轮子”,因为无论我们怎么努力,也很难去超越以前已经有的轮子,那我们为什么还要去造轮子呢?

1.只有我们自己才懂得我们的需求,你在编程中无论用什么框架,总觉得跟业务不是很契合,甚至有时候框架很臃肿,但是因为要用到其中一个内容而不得不导入一个很大的框架。

2.已有的框架太复杂,我们完全没法掌握他的所有内容,出了bug后一头雾水解决不了问题。

3.公司要求,不可使用第三方框架(多见于数据保密的公司),但是程序中要反复使用某一组件功能。

4.找不到合适的轮子。

5.想装B。

如果遇到以上问题,或许自己造轮子才是一个好办法。

前期准备

本文所讲的,可能只是造轮子最基础的教程了,所以知识储备方面要求的不是很全面,如果有大佬看到这篇文章,请轻点喷,我绝对虚心接受批评。

2.1.基础知识

总从上次发了那篇关于阅读源码的文章,总是有朋友问我“你好,请问我现在刚大一,看不懂源码怎么办?”,每每遇到这种问题,我都很绝望,我在那篇文章中所讲的内容,都是建立在已经有一定编程基础的前提上,如果正在看这篇文章的你没有接触过编程,那现在可以先点一下关注,点赞,收藏,然后回去好好学习一下基础再看这篇文章。

我们想造一个轮子,最起码有一定的编程能力,如果要有一个标准,就是能够独自搭建一个项目。

本文中主要运用到的知识点有:注解、反射、多态。

2.2.了解源码

比如JDK源码和一些框架的源码,造轮子在某种层面上来说,就是写一个框架,我们在没有基础的情况下,先看一些大佬写好的框架源码是很有帮助的,在我们自己的轮子中,可以模仿他们框架的结构。

2.3.不怕失败

第一次造轮子绝对是一个艰难而又漫长的过程,你会一次次失败,你需要经受住失败带来的对你信心和耐心的打击,如果你无法坚持,还不如不要开始。

开工

如果你看到这里,相信你已经准备好了,那么现在就开始吧!

3.1.想好需求

既然是造轮子,那么总得先想好这个轮子的用途,我们我们假设一个需求:通过注解实现系统日志输出到文件。

具体需求如下:

1.记录注解注入的数据

2.日志记录应通过文件保存到系统中,路径可配置,若无配置则选用默认配置

3.日志记录需要添加时间标签

4.日志文件名可在注解中设置

5.引入队列传递日志MSG

3.2.创建项目

为了简化过程,我们可以直接创建一个Maven项目,在你对底层有更好的理解之后,就可以用更好的架构。

新建一个LogUtil项目,项目架构如下:

因为只是一个简单的示例,所以有很多的内容知识有思想,但是还没有实现。

3.2.一些常量

主要保存在Constants.java文件中:

public interface Constants {//等下要引入的配置文件名String CONFIG_FILE_NAME = "yzlogconfig";//配置文件中配置的日志路径String CONFIG_LOG_PATH = "logpath";//配置文件中配置的要扫描的,可能存在我们注解的路径String CONFIG_SACN_PATH = "scanpath";//若未声明某些信息,则使用以下默认值//默认的我们的日志信息前缀,对日志信息做简单描述String DEFAULT_CONTENT_PREFIX = "注入值:";//默认的日志文件名(实际写入时会在日志文件名后加上日期标签)String DEFAULT_FILE_NAME = "log";//日志信息类型,处理消息时会用到String MSG_TYPE_LOG = "log";//默认的Linux系统下的日志路径String LINUX_LOG_PATH = "/home/data/";//默认的Windows系统下的日志路径String WIN_LOG_PATH = "D:/winLog/data/";}

3.3.加载配置

思想:给予用户配置权限。

框架是拿给别人用的,一定要给予用户自主配置的权限,这里要加载的是那些引入我们轮子的项目的配置。

某个项目在引入我们的轮子的时候,他自己是应当有权限去自己设置一些东西的,比如我们这里的文件处理路径,是给了用户权限去配置的。

我们规定配置文件的文件名为yzlogconfig.xml,等下在代码中也可以看到这个配置文件名的设置。

ConfigurationUtil

这个工具类主要用来加载配置信息,基础工具类,这里不再累述。

import java.util.ResourceBundle;public class ConfigurationUtil {private static Object lock = new Object();private static ConfigurationUtil config = null;private static ResourceBundle rb = null;private ConfigurationUtil(String filename) {rb = ResourceBundle.getBundle(filename);}public static ConfigurationUtil getInstance(String filename) {synchronized (lock) {if (null == config) {config = new ConfigurationUtil(filename);}}return (config);}public String getValue(String key) {String ret = "";if (rb.containsKey(key)) {ret = rb.getString(key);}return ret;}}

3.4.日志记录功能实现

这里算是核心功能的一部分了,需要的工具类有:DateUtil(获取日期)、SystemUtil(获取当前系统的类型)、FileUtil(创建日志文件)。

DataUtil

import java.text.SimpleDateFormat;import java.util.Date;public class DateUtil {public final static String DATE_A = "yyyy-MM-dd";public final static String DATE_B = "yyyy-MM-dd HH:mm:ss";public final static String DATE_C = "yyyyMMddHHmmss";public final static String DATE_D = "yyyyMMdd-HHmmss-SS";public final static String DATE_E = "M月d日";public final static String DATE_F = "MM-dd";public final static String DATE_G = "yyyyMMddHHmmss";// 普通的当前时间转字符串方法,格式为yyyy-MM-ddpublic static String getDate() {SimpleDateFormat sdf = new SimpleDateFormat(DATE_A);return sdf.format(new Date());}public static String getDateTime() {Date date = new Date();String datestr;SimpleDateFormat sdf = new SimpleDateFormat(DATE_B);datestr = sdf.format(date);return datestr;}}

SystemUtil

/***@描述 用于判断当前系统*@参数*@返回值*@创建人  Baldwin*@创建时间  2020/4/4*@修改人和其它信息*/public class SystemUtil {/*** 判断系统时win还是linux* @return*/public static boolean isLinux(){String name = System.getProperty("os.name");if(name.toLowerCase().startsWith("win"))return false;elsereturn true;}}

FileUtil

import java.io.BufferedWriter;import java.io.File;import java.io.FileOutputStream;import java.io.OutputStreamWriter;public class FileUtil {// 在已经存在的文件后面追加写的方式public static boolean write(String path, String str) {File f = new File(path);File fileParent = f.getParentFile();BufferedWriter bw = null;try {if(!fileParent.exists()){fileParent.mkdirs();}if(!f.exists()){f.createNewFile();}// new FileWriter(name,true)设置文件为在尾部添加模式,参数为false和没有参数都代表覆写方式bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path, true), "UTF-8"));bw.write(str);} catch (Exception e) {e.printStackTrace();return false;} finally {try {if(bw!=null)bw.close();} catch (Exception e) {System.out.println("FileUtil.write colse bw wrong:" + e);}}return true;}}

LogUtil

思想:默认配置

很多时候,我们的用户可能没有配置需要配置的信息,而且注解中也没有声明,那么就要求我们存在默认的配置来填补这些空缺,从而避免由于空配置导致的错误。

在这个类里面,我们主要进行一些日志路径和内容的整理:

import cn.yzstu.support.Constants;import cn.yzstu.support.DateUtil;import cn.yzstu.support.FileUtil;import cn.yzstu.support.SystemUtil;public class LogUtil {//日志写入操作public static void write2file(String path, String fileName, String content) {//获取当前日期,我们的日志保存的文件夹名是自定义path+日期String date = DateUtil.getDate()+"/";try {//传了path,那我们直接用这个pathif (null != path && 0 != path.length()) {//写入FileUtil.write(path + date + fileName + ".txt",DateUtil.getDateTime() + ":" + content + "\r\n");} else {//没有传path或错误使用默认的路径if (SystemUtil.isLinux()) {FileUtil.write(Constants.LINUX_LOG_PATH + date + fileName + ".txt",DateUtil.getDateTime() + ":" + content + "\r\n");} else {FileUtil.write(Constants.WIN_LOG_PATH + date + fileName + ".txt",DateUtil.getDateTime() + ":" + content + "\r\n");}}} catch (Exception e) {e.printStackTrace();}}
}

3.5.日志消息

我们等一下要把日志消息放到队列里来处理,这里定义一个日志类的消息类型,方便后续处理,在本示例中,做了简化处理,实际上对于队列消息,我们需要定义一个统一接口,让所有的消息类型都实现他,这样如果我们的消息类型很多的时候,就能做一个统一的管理了。

我们为了方便处理,在构造函数中就让这个消息入列了,并且把他的MsgType直接设置成了logmsg。

import cn.yzstu.core.MsgQueue;import cn.yzstu.support.Constants;public class LogMsg {private String path;private String content;private String fileName;private String msgType;public LogMsg(String path, String content, String fileName) {this.path = path;this.content = content;this.fileName = fileName;this.msgType = "logmsg";//在构造函数中就让这个消息入列MsgQueue.push(this);} public String getPath() {return path;}public void setPath(String path) {this.path = path;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}public String getFileName() {return fileName;}public void setFileName(String fileName) {this.fileName = fileName;}public String getMsgType() {return this.msgType;}public void setMsgType(String msgType) {this.msgType = msgType;}@Overridepublic String toString() {return "LogMsg{" +"path='" + path + '\'' +", content='" + content + '\'' +", fileName='" + fileName + '\'' +", msgType='" + msgType + '\'' +'}';}}

3.6.定义队列

实际情况中,我们的队列里面会存在很多中类型的消息,在本示例中只存在logmsg。

import cn.yzstu.beans.LogMsg;import java.util.Queue;import java.util.concurrent.ConcurrentLinkedDeque;public class MsgQueue {private static Queue queue = new ConcurrentLinkedDeque<>();//消息入列public static boolean push(LogMsg logMsg){return queue.offer(logMsg);}//消息出列public static LogMsg poll(){return queue.poll();}//消息队列是否已经处理完毕,处理完毕返回truepublic static boolean isFinash(){return !queue.isEmpty();}}

3.7.定义注解

在此我们定义一个名为YzLogWrite的注解类,它主要实现的功能是值注入及日志标记。

对于注解不是很了解的同僚可以看我的另一篇文章:想自己写框架?不了解注解可不行。

YzLogWrite

import java.lang.annotation.*;//作用于字段
@Target({ElementType.FIELD})
//运行时生效
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface YzLogWrite {//需要注解的值int value() default -1;//默认是Linux系统,默认记录文件夹如下String path() default "";//文件名String fileName() default "";//内容String msgPrefix() default "";
}

3.8.注解逻辑实现

思想:声明大于配置

如果我们在注解中声明了一些用到的信息,但是配置文件中也有这些信息,我们应该有限选用注解中声明的信息。

思想:自定义扫描路径

我们应当给予用户权限去让他自己规定自己注解使用的包。

我们定义了注解,但是还需要进行一些操作来完善注解的功能,在这一部分,我们要将值注入,并且将值信息发送到消息队列中。

DealAnnotation

import cn.yzstu.annotation.YzLogWrite;
import cn.yzstu.beans.LogMsg;
import cn.yzstu.support.Constants;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;public class DealAnnotation {//配置文件中设置的log所在地址private static String LOG_PATH = ConfigurationUtil.getInstance(Constants.CONFIG_FILE_NAME).getValue(Constants.CONFIG_LOG_PATH);//保存那些存在注解的class的类名private List<String> registyClasses = new ArrayList<>();public void injectAndMakeMsg() {//需要扫描的注解可能存在的位置String scanPath = ConfigurationUtil.getInstance(Constants.CONFIG_FILE_NAME).getValue(Constants.CONFIG_SACN_PATH);doScanner(scanPath);for (String className : registyClasses) {try {Class clazz = Class.forName(className);Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {//获取类的所有注解Annotation[] annotations = field.getAnnotations();//没有注解或没有我们的注解,跳过if (0 == annotations.length || !field.isAnnotationPresent(YzLogWrite.class)) {continue;}//获取注解YzLogWrite yzLogWrite = field.getAnnotation(YzLogWrite.class);//提取注解中的值//声明大于配置String path = null == yzLogWrite.path() || yzLogWrite.path().isEmpty() ? LOG_PATH : yzLogWrite.path();String content = null == yzLogWrite.msgPrefix() || yzLogWrite.msgPrefix().isEmpty() ? Constants.DEFAULT_CONTENT_PREFIX : yzLogWrite.msgPrefix();String fileName = null == yzLogWrite.fileName() || yzLogWrite.fileName().isEmpty() ? Constants.DEFAULT_FILE_NAME : yzLogWrite.fileName();int value = yzLogWrite.value();//新建logMsg,在构造函数中已入列new LogMsg(path, content + ":" + value, fileName);//开始注入//强制访问该成员变量field.setAccessible(true);//注入int值field.setInt(Integer.class, value);}} catch (ClassNotFoundException | IllegalAccessException e) {e.printStackTrace();}}}private void doScanner(String scanPath) {URL url = this.getClass().getClassLoader().getResource(scanPath.replaceAll("\\.", "/"));File classPath = new File(url.getFile());for (File file : classPath.listFiles()) {if (file.isDirectory()) {//如果是目录则递归调用,直到找到classdoScanner(scanPath + "." + file.getName());} else {if (!file.getName().endsWith(".class")) {continue;}String className = (scanPath.replace("/", ".") + "." + file.getName().replace(".class", ""));registyClasses.add(className);}}}
}

3.9.处理消息

思想:多态分发

尽量让我们的队列能够处理不同种类的消息,我们在获取到队列中的消息之后,应当有一个对消息类型的判断,并将不同类型的消息分发到不同方法中的操作。

通过上面的操作,我们已经把值注入并且把日志消息传到队列中去了,现在还要对队列中的消息进行处理。

import cn.yzstu.beans.LogMsg;
public class DealMsg extends Thread{@Overridepublic void run() {while (MsgQueue.isFinash()){//多态//实际中,我们可以定义很多中msg,用type来区分,并通过不同的方法来处理//此处运用了这种思想,但是没有实现具体操作LogMsg logMsg = MsgQueue.poll();switch (logMsg.getMsgType()){case "logmsg" ://如果类型是logmsg,那就通过日志来处理dealLogMsg(logMsg);break;default:defaultMethod(logMsg);}}this.interrupt();}private void defaultMethod(LogMsg logMsg) {System.out.println("no msg");}private void dealLogMsg(LogMsg logMsg) {LogUtil.write2file(logMsg.getPath(),logMsg.getFileName(),logMsg.getContent());}@Overridepublic synchronized void start() {this.run();}
}

3.10.提供入口

我们的一个简单的实例基本上功能已经完成了,那如何引入呢?这里我采取的方法是留一个操作的方法来执行我们所有的功能。

import cn.yzstu.annotation.YzLogWrite;
public class StartWork {//程序入口public static void doWork(){//处理:扫描注解、注入、发送日志消息到队列new DealAnnotation().injectAndMakeMsg();//创建线程来处理消息new DealMsg().start();}
}

测试

我们以上已经完成了所有的功能,需要现在就来测试一下。

创建配置类

我们规定配置文件名为“yzlogconfig”,那么现在在resource文件夹下创建一个该配置文件。

#logpath最后需要带/
logpath = /opt/
scanpath = cn/yzstu/tt

我们只配置了log日志路径和注解位置,用以测试默认参数是否生效

创建测试类

我们在上面配置文件中规定了我们注解使用的包,所以应当在该包下去使用注解,否则扫描不到我们的注解。

import cn.yzstu.annotation.YzLogWrite;
import cn.yzstu.core.StartWork;public class Demo {//因为测试用的main函数是static,所以此时将age设置为static@YzLogWrite(value = 18,msgPrefix = "记录Baldwin的年龄:")static int age;public static void main(String[] args) {StartWork.doWork();System.out.println(age);}
}

执行结果

首先看控制台,显示注入成功。

/opt/java/jdk1.8.0_241/bin/java -javaagent:/opt/jetbrains/idea-IU-193.6911.18/lib/idea_rt.jar=38115:/opt/jetbrains/idea-IU-193.6911.18/bin -Dfile.encoding=UTF-8 -classpath /opt/java/jdk1.8.0_241/jre/lib/charsets.jar:/opt/java/jdk1.8.0_241/jre/lib/deploy.jar:/opt/java/jdk1.8.0_241/jre/lib/ext/cldrdata.jar:/opt/java/jdk1.8.0_241/jre/lib/ext/dnsns.jar:/opt/java/jdk1.8.0_241/jre/lib/ext/jaccess.jar:/opt/java/jdk1.8.0_241/jre/lib/ext/jfxrt.jar:/opt/java/jdk1.8.0_241/jre/lib/ext/localedata.jar:/opt/java/jdk1.8.0_241/jre/lib/ext/nashorn.jar:/opt/java/jdk1.8.0_241/jre/lib/ext/sunec.jar:/opt/java/jdk1.8.0_241/jre/lib/ext/sunjce_provider.jar:/opt/java/jdk1.8.0_241/jre/lib/ext/sunpkcs11.jar:/opt/java/jdk1.8.0_241/jre/lib/ext/zipfs.jar:/opt/java/jdk1.8.0_241/jre/lib/javaws.jar:/opt/java/jdk1.8.0_241/jre/lib/jce.jar:/opt/java/jdk1.8.0_241/jre/lib/jfr.jar:/opt/java/jdk1.8.0_241/jre/lib/jfxswt.jar:/opt/java/jdk1.8.0_241/jre/lib/jsse.jar:/opt/java/jdk1.8.0_241/jre/lib/management-agent.jar:/opt/java/jdk1.8.0_241/jre/lib/plugin.jar:/opt/java/jdk1.8.0_241/jre/lib/resources.jar:/opt/java/jdk1.8.0_241/jre/lib/rt.jar:/root/IdeaProjects/LogUtil/target/classes cn.yzstu.tt.Demo18
Process finished with exit code 0

然后再看我们的/opt文件夹下有么有日志文件,日志成功写入。

查看日志文件,注解声明内容启用。

2020-04-06 00:17:30:记录Baldwin的年龄::18

总结

目前为止,我们的一个简单的日志记录的轮子已经造好了,我们可以把他打成JAR包引入到我们的项目中去,只需要在项目初始化时启用我们的功能即可。

5.1.思想

思想:给予用户配置权限

框架是拿给别人用的,一定要给予用户自主配置的权限。

思想:默认配置

很多时候,我们的用户可能没有配置需要配置的信息,而且注解中也没有声明,那么就要求我们存在默认的配置来填补这些空缺,从而避免由于空配置导致的错误。

思想:声明大于配置

如果我们在注解中声明了一些用到的信息,但是配置文件中也有这些信息,我们应该有限选用注解中声明的信息。

思想:自定义扫描路径

我们应当给予用户权限去让他自己规定自己注解使用的包

思想:多态分发

尽量让我们的队列能够处理不同种类的消息,我们在获取到队列中的消息之后,应当有一个对消息类型的判断,并将不同类型的消息分发到不同方法中的操作

5.2.关于本项目

作者是一个正在编程路上匍匐前进的萌新,这篇实例仅提供给新手入门使用,如果有错误,还请大佬不吝指点。

项目代码:http://gitee.com/dikeywork/LogUtil

5.3.个人总结

完成项目时遇到了许多的困难,本来打算一天完事儿,但是真正写完这篇文章却用了整整两天,仍需进步。

我是Baldwin,一个25岁的程序员,致力于让学习变得更有趣,如果你也真正喜爱编程,真诚的希望与你交个朋友,一起在编程的海洋里徜徉!

原文链接:

http://blog.csdn.net/shouchenchuan5253/java/article/details/105256723

往期热门文章:
1、《历史文章分类导读列表!精选优秀博文都在这里了!》
2、图解Spring循环依赖,看过之后面试再也不用慌了!
3、他来了!IDEA 2020.1 新版介绍!不过升级前请注意避坑!
4、七个略火的Spring Boot+Vue开源项目!
5、Linux 11个炫酷的终端命令!你知道几个?
6、十个你可能不曾用过的Linux命令!巨好用!
7、分库分表  PK NewSQL数据库!
8、快给你的Spring Boot做个埋点监控吧!
9、一入职!就遇到上亿(MySQL)大表的优化....
10、惊呆了,Spring Boot居然这么耗内存!

因为造轮子,我一个月就转正了相关推荐

  1. 因为造轮子,我一个月就转正了 | 原力计划

    作者 | Baldwin_KeepMind 责编 | 伍杏玲 出品 | CSDN博客 2019年6月,我通过社招入职现在所工作的公司,理论上应该有三个月时间的试用期,只有试用期表现良好我才有机会转正, ...

  2. 造轮子-AgileConfig一个基于.NetCore开发的轻量级配置中心

    微服务确实是行业的一个趋势,我自己也在把一些项目往微服务架构迁移.玩微服务架构配置中心是一个绕不过去的东西,有很多大牌的组件可以选,比如spring-cloud-config,apoll,discon ...

  3. 「造轮子」一个文件上传靶场知识总结记录

    https://www.sqlsec.com/2020/10/upload.html#toc-heading-1 或者 https://xz.aliyun.com/t/8435 直接使用别人的靶场总感 ...

  4. 开源造轮子:一个简洁,高效,轻量级,酷炫的不要不要的canvas粒子运动插件库...

    一:开篇 哈哈哈,感谢标题党的莅临~ 虽然标题有点夸张的感觉,但实际上,插件库确实是简洁,高效,轻量级,酷炫酷炫的咯.废话不多说,先来看个标配例子吧: (codepen在线演示编辑:http://co ...

  5. 你们应该听说过”w8ay“这个ID吧!一个喜欢造轮子的小子

    他是w8scan.w9scan.w11scan.airbug的作者.他也是hack-requests的开发者.他在2018 Kcon以学生的身份展示了"自动化扫描工具".他也在Fr ...

  6. 分享一个牛逼的 Java 开源后台管理系统,不要造轮子了!

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:后端程序员必备:书写高质量SQL的30条建议个人原创+1博客:点击前往,查看更多 还是那句话,重复造轮子是个苦逼 ...

  7. 动手造轮子:实现一个简单的基于 Console 的日志输出

    动手造轮子:实现一个简单的基于 Console 的日志输出 Intro 之前结合了微软的 Logging 框架和 Serilog 写了一个简单的日志框架,但是之前的用法都是基于 log4net.ser ...

  8. 动手造轮子:写一个日志框架

    动手造轮子:写一个日志框架 Intro 日志框架有很多,比如 log4net / nlog / serilog / microsoft.extensions.logging 等,如何在切换日志框架的时 ...

  9. 动手造轮子:实现一个简单的依赖注入(二) --- 服务注册优化

    动手造轮子:实现一个简单的依赖注入(二) --- 服务注册优化 Intro 之前实现的那版依赖注入框架基本可用,但是感觉还是不够灵活,而且注册服务和解析服务在同一个地方感觉有点别扭,有点职责分离不够. ...

最新文章

  1. Web开发之Goahead
  2. 计算机图形学三大应用领域,计算机图形学作业
  3. pringMVC“Ambiguous mapping found. Cannot map 'XXXController' bean method”解决方法
  4. java发送c语言结构体_C语言中结构体直接赋值?
  5. 计算机科学与技术mobi,080901-计算机科学和技术.PDF
  6. 新独立版抖音口红机全修复版本附视频教程
  7. Python打造文件MD5值计算器
  8. 在线摇骰子/色子工具
  9. office linux版本好用吗,Linux最好的最适合新手的版本是什么
  10. sqlserver 游标写法
  11. 只需5分钟-----手把手教你如何注册美国AppleId账号
  12. [自我介绍]第一篇博客
  13. 电吉他弦距测试软件,测吉他弦距的简单方法
  14. java开发知识总结1
  15. numpy 索引和切片
  16. bzoj 3162: 独钓寒江雪 树哈希+树形dp
  17. XML第二讲:格式正规的XML文档、XML文档的元素、属性、实体深度详解
  18. MATLAB示例——图像中圆的识别与判断
  19. 天梯训练赛——彩虹瓶
  20. Java实例方法、实例变量、类方法、类变量

热门文章

  1. 微博爬虫之:无需账号获取微博weibo的Cookie
  2. 前端页面如果不放在statis等文件夹,想正常访问该如何解决
  3. 今天是一位朋友的生日,送给她最美丽的祝福.
  4. iphone12android在线啥意思,iOS12要来了,你还不知道这些iPhone的隐藏功能?
  5. 使用代理后,雷鸟连接qq邮箱
  6. java DES-CBC加解密
  7. 用户画像 | 标签数据存储之MySQL真实应用
  8. MiniGPT4,开源了
  9. 买个ssl证书费用要多少钱?
  10. 服务器运行时将杀毒软件关掉,教你快速关闭禁用Windows 10自带的杀毒软件Windows Defender...