SpringMvc的手寫版(PS:只是閒來無事寫的簡化版,僅供大家理解SpringMvc的運作原理)
最近手頭正好有些時間,想著寫點什麼好呢?後來看到了一篇帖子說面試的時候有面試官問他能不能手寫一套SpringMvc出來,不拉不拉的….不多說了。
所以想著就寫寫試試,捋了捋思路,無非就是三點(大神勿噴!):
1. 例項化
2. 注入
3. url對映
連起來說就是對加了@Controller、@Service註解的物件進行例項化,然後對這些物件中的某些加了@Autowired註解的屬性進行依賴注入,然後對Controller中加了@RequestMapping註解的方法做url對映,用於請求來到的時候根據url對映到需要執行的方法,同時將傳遞的引數注入到方法中。
額。。。這個說的有點敷衍,確實打字太費勁了,最喜歡直接貼程式碼了!不過本著敬業的原則還是再重新說一下這個專案具體實現了哪些功能及使用方法。
1. 例項化規則,加了@Controller、@Service註解的物件預設beanName就是類名首字母小寫,同時也可以寫別名,那麼beanName就是別名
2. 注入規則,加了@Autowired註解的屬性,預設是通過這個屬性的型別來找他的實現類,如果該介面實現類有多個那麼丟擲異常!那麼我非得有兩個實現類怎麼辦呢?可以,你需要給實現類起個別名比如@Service(“woShiNumberOne”),然後注入的時候@Autowired(“woShiNumberOne”)這樣注入就可以了。
3. url對映方法,就是掃描每個Controller,如果類名上加了@RequestMapping註解那麼這算是根目錄,然後逐個掃描方法,只要方法上加了@RequestMapping註解那麼這就是子目錄,最後會將根目錄+子目錄拼接到一起對映到當前controller的當前方法。
4. 方法引數注入規則,自動識別當前方法有多少個引數,除了request和response這兩個引數以外的其他任何引數都需要加@RequestParam註解,用來給這個引數定義別名,否則無法注入進來!而且由於是簡化版這裡只支援基本資料型別的注入,不支援物件的注入,這點還沒來得及寫。
這樣可以了吧,下面可以開心的來讀程式碼啦!!!
不下原始碼的直接來看下專案結構:
OK下面我們先來自定義5個註解:
/**
* @Description controller註冊的註解
* @author chenbin.sun
* @date 2017年8月30日下午5:12:55
*
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
/**
* 表示給controller註冊別名
* @return
*/
String value() default "";
}
/**
* @Description service註冊的註解
* @author chenbin.sun
* @date 2017年8月30日下午5:12:40
*
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
/**
* 表示給service註冊別名
* @return
*/
String value() default "";
}
/**
* @Description controller和方法上的註解
* @author chenbin.sun
* @date 2017年8月30日下午5:16:05
*
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
/**
* 表示訪問該方法的url
* @return
*/
String value() default "";
}
/**
* @Description 自動注入註解(如果不加別名自動通過介面型別注入實現類)
* @author chenbin.sun
* @date 2017年8月30日下午5:12:40
*
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
/**
* 表示給filed注入的bean的name
* @return
*/
String value() default "";
}
/**
* @Description 用作請求傳引數的別名
* @author chenbin.sun
* @date 2017年8月31日下午2:31:42
*
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestParam {
/**
* 表示引數的別名,必填
* @return
*/
String value();
}
這五個註解的作用上面都描述了,有一點值得注意@RequestParam這個註解是用於寫在方法的引數上的,用於給這個引數起別名的,前端傳過來的引數的key必須和這個相同才能注入進來,否則注入失敗,當然這裡其實還可以擴充套件一些應用場景,比方說該引數是否必須傳入啊等等,但這些都要相應的方法去做對應的實現,目前我沒有寫那麼多。
下面看我的控制層,很簡單,幾種測試場景我都寫上去了
/**
* @Description 測試控制器類
* @author chenbin.sun
* @date 2017年8月31日下午6:57:35
*
*/
@Controller
@RequestMapping("/test")
public class TestController {
@Autowired("testServiceImpl")
private TestService testService;
@Autowired
private TestService2 testService2;
@RequestMapping("/doTest")
public void test(HttpServletRequest request, HttpServletResponse response, @RequestParam("param") String param){
String result = testService.test();
try {
response.getWriter().println("do service result:" + result);
} catch (IOException e) {
e.printStackTrace();
}
}
@RequestMapping("/doTest2")
public void test2(HttpServletRequest request, HttpServletResponse response){
String result = testService2.test2();
try {
response.getWriter().println("do service2 result:" + result);
} catch (IOException e) {
e.printStackTrace();
}
}
}
然後是業務邏輯層,先上兩個介面
public interface TestService {
String test();
}
public interface TestService2 {
String test2();
}
so easy對不對!繼續看兩個實現類,為了測試service之間也能相互注入,所以寫了兩個介面兩個實現類
@Service
public class TestServiceImpl implements TestService {
@Autowired
private TestService2 testService2;
@Override
public String test() {
System.out.println(testService2.test2());
return "method test do success!";
}
}
@Service
public class TestServiceImpl2 implements TestService2 {
@Override
public String test2() {
return "method test2 do success!";
}
}
OK,下面重點來了,核心程式碼支援全部註解特性的功能都在這裡
/**
* @Description 請求幾種處理類
* @author chenbin.sun
* @date 2017年8月30日下午5:23:54
*
*/
public class DispatcherServlet extends HttpServlet {
private static final long serialVersionUID = 1378531571714153483L;
/** 要掃描的包,只有在這個包下並且加了註解的才會唄掃描到 */
private static final String PACKAGE = "chenbin.sun";
private static final String CONTROLLER_KEY = "controller";
private static final String METHOD_KEY = "method";
/** 存放Controller中url和方法的對應關係,格式:{url:{controller:例項化後的物件,method:例項化的方法}} */
private static Map<String, Map<String, Object>> urlMethodMapping = new HashMap<>();
public DispatcherServlet() {
super();
}
/**
* 初始化方法,用於例項化掃描到的物件,並做注入和url對映(注:該方法邏輯上已經判斷了,只執行一次)
*/
@Override
public void init(ServletConfig config) throws ServletException {
// 只處理一次
if (urlMethodMapping.size() > 0) {
return;
}
// 開始掃描包下全部class檔案
Set<Class<?>> classes = ClassTools.getClasses(PACKAGE);
// 存放Controller和Service的Map,格式:{beanName:例項化後的物件}
Map<String, Object> instanceNameMap = new HashMap<String, Object>();
// 存放Service介面型別與介面例項物件的Map,格式:{Service.instance.class:實現類例項化後的物件}
Map<Class<?>, Object> instanceTypeMap = new HashMap<Class<?>, Object>();
// 組裝instanceMap
buildInstanceMap(classes, instanceNameMap, instanceTypeMap);
// 開始注入
doIoc(instanceNameMap, instanceTypeMap);
// 注入完之後開始對映url和method
buildUrlMethodMapping(instanceNameMap, urlMethodMapping);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 完整路徑
String url = req.getRequestURI();
// 跟路徑
String path = req.getContextPath();
// 計算出method上配置的路徑
String finallyUrl = url.replace(path, "");
// 取出這個url對應的Controller和method
Map<String, Object> map = urlMethodMapping.get(finallyUrl);
if (map == null) {
throw new RuntimeException("請求地址不存在!");
}
Method method = (Method) map.get(METHOD_KEY);
try {
// 封裝需要注入的引數,目前只支援request和response以及加了@RequestParam標籤的基本資料型別的引數注入
List<Object> paramValue = buildParamObject(req, resp, method);
// 沒有引數的場合
if (paramValue.size() == 0) {
method.invoke(map.get(CONTROLLER_KEY));
}else {
// 有引數的場合
method.invoke(map.get(CONTROLLER_KEY), paramValue.toArray());
}
} catch (Exception e) {
throw new RuntimeException("執行url對應的method失敗!");
}
}
/**
* 封裝需要注入的引數,目前只支援request和response以及加了@RequestParam標籤的基本資料型別的引數注入
* @param req
* @param resp
* @param method
* @return
*/
private List<Object> buildParamObject(HttpServletRequest req, HttpServletResponse resp, Method method) {
// 封裝需要注入的引數,目前只支援request和response以及加了@RequestParam標籤的基本資料型別的引數注入
Parameter[] parameters = method.getParameters();
List<Object> paramValue = new ArrayList<>();
for (Parameter parameter : parameters) {
// 當前引數有別名註解並且別名不為空
if(parameter.isAnnotationPresent(RequestParam.class) && !parameter.getAnnotation(RequestParam.class).value().isEmpty()){
// 我們獲取
String value = req.getParameter(parameter.getAnnotation(RequestParam.class).value());
paramValue.add(value);
}else if (parameter.getParameterizedType().getTypeName().contains("HttpServletRequest")) {
paramValue.add(req);
}else if (parameter.getParameterizedType().getTypeName().contains("HttpServletResponse")) {
paramValue.add(resp);
}else{
paramValue.add(null);
}
// 這裡只做了request和response以及基本資料型別的引數注入,如果要做物件的注入也是可以寫,這裡暫時就不寫了
// TODO: 做物件的注入
}
return paramValue;
}
/**
* 注入完之後開始對映url和method
* @param instanceMap
* @param urlMethodMapping
*/
private void buildUrlMethodMapping(Map<String, Object> instanceMap,
Map<String, Map<String, Object>> urlMethodMapping) {
// 注入完之後開始對映url和method
// 組裝urlMethodMapping
for (Entry<String, Object> entry : instanceMap.entrySet()) {
// 迭代出所有的url
String parenturl = "";
// 判斷Controller上是否加了requestMapping
if (entry.getValue().getClass().isAnnotationPresent(RequestMapping.class)) {
parenturl = entry.getValue().getClass().getAnnotation(RequestMapping.class).value();
}
// 取出全部的method
Method[] methods = entry.getValue().getClass().getMethods();
// 迭代全部的方法,檢查哪些方法上加了requestMaping註解
for (Method method : methods) {
if (method.isAnnotationPresent(RequestMapping.class)) {
// 得到一個完整的url請求
String url = parenturl + method.getAnnotation(RequestMapping.class).value();
Map<String, Object> value = new HashMap<>();
value.put(CONTROLLER_KEY, entry.getValue());
value.put(METHOD_KEY, method);
urlMethodMapping.put(url, value );
}
}
}
}
/**
* 根據例項Map開始注入
* @param instanceMap
*/
private void doIoc(Map<String, Object> instanceMap, Map<Class<?>, Object> instanceTypeMap) {
// 開始注入,我們只對加了@Controller和@Service標籤中的,屬性加了@autowired的進行注入操作
for (Entry<String, Object> entry : instanceMap.entrySet()) {
// 取出全部的屬性
Field[] fields = entry.getValue().getClass().getDeclaredFields();
// 迴圈屬性校驗哪些是加了@autowired註解的
for (Field field : fields) {
field.setAccessible(true);// 可訪問私有屬性
// 有註解的時候
if (field.isAnnotationPresent(Autowired.class)) {
// 沒有配別名注入的時候
if (field.getAnnotation(Autowired.class).value().isEmpty()) {
// 直接獲取
try {
// 根據型別來獲取他的實現類
Object object = instanceTypeMap.get(field.getType());
field.set(entry.getValue(), object);
} catch (IllegalArgumentException | IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
try {
// 將被注入的物件
Object object = instanceMap.get(field.getAnnotation(Autowired.class).value());
field.set(entry.getValue(), object);
} catch (Exception e) {
throw new RuntimeException("開始注入時出現了異常");
}
}
}
}
}
}
/**
* 組裝instanceMap
* @param classes
* @param instanceMap
*/
private void buildInstanceMap(Set<Class<?>> classes, Map<String, Object> instanceMap, Map<Class<?>, Object> instanceTypeMap) {
// 開始迴圈全部class
for (Class<?> clasz : classes) {
// 組裝instanceMap
// 判斷是否是是加了Controller註解的java物件
if (clasz.isAnnotationPresent(Controller.class)) {
try {
// 例項化物件
Object obj = clasz.newInstance();
Controller controller = clasz.getAnnotation(Controller.class);
// 如果沒有設定別名,那麼用類名首字母小寫做key
if (controller.value().isEmpty()) {
instanceMap.put(firstLowerName(clasz.getSimpleName()), obj);
}else{
// 如果設定了別名那麼用別名做key
instanceMap.put(controller.value(), obj);
}
} catch (Exception e) {
throw new RuntimeException("初始化instanceMap時在處理Controller註解時出現了異常");
}
}else if(clasz.isAnnotationPresent(Service.class)) {
// 例項化物件
Object obj = null;
try {
// 例項化物件
obj = clasz.newInstance();
Service service = clasz.getAnnotation(Service.class);
// 如果沒有設定別名,那麼用類名首字母小寫做key
if (service.value().isEmpty()) {
instanceMap.put(firstLowerName(clasz.getSimpleName()), obj);
}else{
// 如果設定了別名那麼用別名做key
instanceMap.put(service.value(), obj);
}
} catch (Exception e) {
throw new RuntimeException("初始化instanceMap時在處理Service註解時出現了異常");
}
// 實現的介面陣列
Class<?>[] interfaces = clasz.getInterfaces();
for (Class<?> class1 : interfaces) {
if (instanceTypeMap.get(class1) != null) {
throw new RuntimeException(class1.getName() + "介面不能被多個類實現!");
}
instanceTypeMap.put(class1, obj);
}
}else {
continue;
}
}
}
/**
* 首字母小寫
* @param name
* @return
*/
private String firstLowerName(String name) {
name = name.substring(0, 1).toLowerCase() + name.substring(1);
return name;
}
}
因為掃包的程式碼太長了,所以我單獨寫成了工具類,程式碼如下
public class ClassTools {
/**
* 從包package中獲取所有的Class
*
* @param pack
* @return
*/
public static Set<Class<?>> getClasses(String pack) {
// 第一個class類的集合
Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
// 是否迴圈迭代
boolean recursive = true;
// 獲取包的名字 並進行替換
String packageName = pack;
String packageDirName = packageName.replace('.', '/');
// 定義一個列舉的集合 並進行迴圈來處理這個目錄下的things
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
// 迴圈迭代下去
while (dirs.hasMoreElements()) {
// 獲取下一個元素
URL url = dirs.nextElement();
// 得到協議的名稱
String protocol = url.getProtocol();
// 如果是以檔案的形式儲存在伺服器上
if ("file".equals(protocol)) {
System.err.println("file型別的掃描");
// 獲取包的物理路徑
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
// 以檔案的方式掃描整個包下的檔案 並新增到集合中
findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
} else if ("jar".equals(protocol)) {
// 如果是jar包檔案
// 定義一個JarFile
System.err.println("jar型別的掃描");
JarFile jar;
try {
// 獲取jar
jar = ((JarURLConnection) url.openConnection()).getJarFile();
// 從此jar包 得到一個列舉類
Enumeration<JarEntry> entries = jar.entries();
// 同樣的進行迴圈迭代
while (entries.hasMoreElements()) {
// 獲取jar裡的一個實體 可以是目錄 和一些jar包裡的其他檔案 如META-INF等檔案
JarEntry entry = entries.nextElement();
String name = entry.getName();
// 如果是以/開頭的
if (name.charAt(0) == '/') {
// 獲取後面的字串
name = name.substring(1);
}
// 如果前半部分和定義的包名相同
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
// 如果以"/"結尾 是一個包
if (idx != -1) {
// 獲取包名 把"/"替換成"."
packageName = name.substring(0, idx).replace('/', '.');
}
// 如果可以迭代下去 並且是一個包
if ((idx != -1) || recursive) {
// 如果是一個.class檔案 而且不是目錄
if (name.endsWith(".class") && !entry.isDirectory()) {
// 去掉後面的".class" 獲取真正的類名
String className = name.substring(packageName.length() + 1, name.length() - 6);
try {
// 新增到classes
classes.add(Class.forName(packageName + '.' + className));
} catch (ClassNotFoundException e) {
// log
// .error("新增使用者自定義檢視類錯誤
// 找不到此類的.class檔案");
e.printStackTrace();
}
}
}
}
}
} catch (IOException e) {
// log.error("在掃描使用者定義檢視時從jar包獲取檔案出錯");
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return classes;
}
/**
* 以檔案的形式來獲取包下的所有Class
*
* @param packageName
* @param packagePath
* @param recursive
* @param classes
*/
public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive,
Set<Class<?>> classes) {
// 獲取此包的目錄 建立一個File
File dir = new File(packagePath);
// 如果不存在或者 也不是目錄就直接返回
if (!dir.exists() || !dir.isDirectory()) {
// log.warn("使用者定義包名 " + packageName + " 下沒有任何檔案");
return;
}
// 如果存在 就獲取包下的所有檔案 包括目錄
File[] dirfiles = dir.listFiles(new FileFilter() {
// 自定義過濾規則 如果可以迴圈(包含子目錄) 或則是以.class結尾的檔案(編譯好的java類檔案)
public boolean accept(File file) {
return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
}
});
// 迴圈所有檔案
for (File file : dirfiles) {
// 如果是目錄 則繼續掃描
if (file.isDirectory()) {
findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,
classes);
} else {
// 如果是java類檔案 去掉後面的.class 只留下類名
String className = file.getName().substring(0, file.getName().length() - 6);
try {
// 新增到集合中去
// classes.add(Class.forName(packageName + '.' +
// className));
// 經過回覆同學的提醒,這裡用forName有一些不好,會觸發static方法,沒有使用classLoader的load乾淨
classes.add(
Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
} catch (ClassNotFoundException e) {
// log.error("新增使用者自定義檢視類錯誤 找不到此類的.class檔案");
e.printStackTrace();
}
}
}
}
/**
* 取出list物件中的某個屬性的值作為list返回
*
* @param objList
* @param fieldName
* @return
*/
public static <T, E> List<E> getPropertyValueList(List<T> objList, String fieldName) {
List<E> list = new ArrayList<E>();
try {
for (T object : objList) {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
list.add((E) field.get(object));
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
}
然後將這個Servlet配置到web.xml中,程式碼如下
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>SpringMvcSimulate</display-name>
<servlet>
<servlet-name>testServlet</servlet-name>
<servlet-class>chenbin.sun.servlet.DispatcherServlet</servlet-class>
</servlet>
<!-- ... -->
<servlet-mapping>
<servlet-name>testServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
相關推薦
SpringMvc的手寫版(PS:只是閒來無事寫的簡化版,僅供大家理解SpringMvc的運作原理)
最近手頭正好有些時間,想著寫點什麼好呢?後來看到了一篇帖子說面試的時候有面試官問他能不能手寫一套SpringMvc出來,不拉不拉的….不多說了。 所以想著就寫寫試試,捋了捋思路,無非就是三點(大神勿噴!): 1. 例項化 2. 注入 3. url對映
28、輸入兩棵二叉樹A,B,判斷B是不是A的子結構。(ps:我們約定空樹不是任意一個樹的子結構)
eno 技術分享 進行 結構 一個點 left courier mage new 題目描述 輸入兩棵二叉樹A,B,判斷B是不是A的子結構。(ps:我們約定空樹不是任意一個樹的子結構) 思路: 1、當Tree1和Tree2都不為零的時候,才進行比較。否則直接返回fals
2018年航空概論課後作業(PS:部分答案不正確, 綜合得分:83.6)
方式 效率 設備 形式 燃氣 產品 差值 原理 基礎 1 【單選題】航空是指載人或不載人的飛行器在地球____的航行活動。 ? A、高空? B、大氣層內? C、宇宙? D、大氣層外我的答案:B 得分: 33.3分 2 【多選題】軍用飛機可分為____兩大類。 ? A、作戰飛
ORACLE讀寫分離(注:根據網上資料搭建完成步奏總結)
研究問題:  
周賽第二題(ps:那時候沒做出來)
用1,2,…,n表示n個盤子,稱為1號盤,2號盤,…。號數大盤子就大。經典的漢諾塔問 題經常作為一個遞迴的經典例題存在。可能有人並不知道漢諾塔問題的典故。漢諾塔來源於 印度傳說的一個故事,上帝創造世界時作了三根金剛石柱子,在一根柱子上從下往上按大小 順序摞著64片黃金圓盤。上帝命令婆羅門把圓
java常見邏輯練習題求出100內的素數個數,平切打印出當前數是什麼?(素數:只能夠被自己和1整除的數 )
題目:求出100內的素數個數,平切打印出當前數是什麼?(素數:只能夠被自己和1整除的數 ) 分析:要求100內的素數個數,我們首先要拿到1-100的所有數字,用for迴圈遍歷1-100的所有數字,即: 第一步 for (int i = 1; i < 100
niceScroll滾動條出現在div的左側(PS:原本應該出現在div右側)
一、問題 在用jquery.nicescroll.js時,發現滾動條出現在div的左側,正常情況下應該出現在右側的。如圖:![這裡寫圖片描述](https://img-blog.csdn.net/20171019102658672?watermark/2/te
JAVA中分為基本數據類型及引用數據類型(問題:堆和棧的區別,系統根據什麽區分堆棧內存)
復雜 復合 小寫 name 布爾 語言 內存空間 結構 抽象 一、基本數據類型: byte:Java中最小的數據類型,在內存中占8位(bit),即1個字節,取值範圍-128~127,默認值0 short:短整型,在內存中占16位,即2個字節,取值範圍-32768~32717
leetcode35題:搜索插入位置(不是最優解法,僅供參考)
重復 mce 僅供參考 sea elf () 參考 lis 位置 給定一個排序數組和一個目標值,在數組中找到目標值,並返回其索引。如果目標值不存在於數組中,返回它將會被按順序插入的位置。你可以假設數組中無重復元素。 示例 1:輸入: [1,3,5,6], 5輸出: 2示例
Ajax異步請求返回文件流(eg:導出文件時,直接將導出數據用文件流的形式返回客戶端供客戶下載)
usermode table logs param onload img height tle http 在異步請求中要返回文件流,不能使用JQuery,因為$.ajax,$.post 不支持返回二進制文件流的類型,可以看到下圖,dataType只支持xml,json,sc
Linux 使用Mycat實現讀寫分離(基於Mysql的讀寫分離)
各位同學大家好,今天給大家分享一下用Mycat進行資料庫的讀寫分離,本篇文章是基於上一篇的mysql主從複製。Linux上實現Mysql的主從複製(為Mycat讀寫分離作準備) 在上一篇文章中,我們在兩個伺服器使用同版本的作業系統和mysql: 伺服器1:centos7.3,mysql5.6 伺服器
工具類:防抖動(極短時間多次點選,導致介面彈出多個dialog)
工具類: public class OnClickUtils { // 兩次點選按鈕之間的點選間隔不能少於500毫秒 private static final int MIN_CLICK_DELAY_TIME = 500; &
二叉樹層序遍歷(關鍵詞:樹/二叉樹/遍歷/層序遍歷/層次遍歷)
二叉樹層序遍歷 實現 def levelOrder(self, root): if root is None: return [] res = [] queue = [root]
python檔案讀寫(從file1中讀出資料並計算,然後將結果寫入到file2中)
要求新建兩個檔案,file1、file2,要求開啟file1檔案,分別對每一行數字進行求和,並將每一行的結果寫在file2中。 file1: 20 30 40 20 52 63 52 52 85 52 8 456 522 25 36 85 96 74 程式原始碼: 定義一個求和函式
小豬動圖:只需一鍵搜尋,瞬間獲取百萬張魔性的GIF動圖……
GIF動圖格式已有30年曆史,它是唯一一種不需要播放器或外掛就可以在任何網路上實現類視訊效果的檔案格式。如今GIF成為了一種網路文化,在微信、QQ等社交APP上流行的鬥圖,其中最魔性的絕對是GIF動圖;在微信公眾號、頭條號、微博等新媒體上傳播最快的也是GIF動圖,它就如同空氣一般的存在,給我們帶來了
SparkStreaming部分的學習(包括:sparkStreaming與storm的區別, Sparkstreaming處理資料的過程等)【業務邏輯圖及文字說明】
sparkStreaming與storm的區別: Sparkstreaming處理資料的過程: sparkstreaming:資料是一段時間處理的,是一個微批處理,這個時間是由自己人為設定的。sparkstreaming的吞吐量高。 Storm:是純實時處理資料的,
CentOS7,MySQL主從配置和讀寫分離(MySQL主從、讀寫分離、分散式、資料庫讀寫分離、主從配置)
一、實驗目標搭建兩臺MySQL伺服器,一臺作為主伺服器,一臺作為從伺服器,主伺服器進行寫操作,從伺服器進行讀操作。二、測試環境主資料庫: CentOS7, MySQL15.1 , 192.168.1.233從資料庫: CentOS7, MySQL15.1 , 192.168.
百度文庫免費下載(附:分享一些有趣的網站,最後一個可以免飛下載百度文庫)
1:現實工具箱 現實工具箱是一個整合很多實用功能和實用工具的網站。 包括:有趣網站,實用工具,網頁特效,網頁遊戲,API應用等等功能;有一些實用工具:線上進位制轉換,微博視訊解析,快手視訊解析,線上網頁製作,短網址生成,等等工具還是比較實用的。 2:超高無損音樂
python解析:Java環境配置簡化版
1. 下載安裝jdk 怎樣判斷JDK已經安裝成功 windows下:開始->執行->鍵入cmd->在視窗中輸入 javac-> 回車,看看是否出來java相關命令的引數。 ja
程式設計師要讓你的電腦宕機需要多久?黑客:只需要3個按鍵,5個字元
諸多行業中,程式設計師應該是一個比較特殊的群體,許多人一提起程式設計師,腦子裡除了高新之外,總會不自覺的和木訥、拖沓聯絡在一起。而且現在的網路發達,網路中大量的黑程式設計師的段子,許多人根本沒有接觸過程式設計師,但看完那些段子之後,就會形成這樣的印象。 多數程式設計師並不木