【Java】模擬Sping,實現其IOC和AOP核心(一)

来源:https://www.cnblogs.com/a526583280/archive/2018/10/28/9866488.html
-Advertisement-
Play Games

在這裡我要實現的是Spring的IOC和AOP的核心,而且有關IOC的實現,註解+XML能混合使用! 參考資料: IOC:控制反轉(Inversion of Control,縮寫為IoC),是面向對象編程中的一種設計原則,可以用來減低電腦代碼之間的耦合度。其中最常見的方式叫做依賴註入(Depend ...


在這裡我要實現的是Spring的IOC和AOP的核心,而且有關IOC的實現,註解+XML能混合使用!

參考資料:
IOC:控制反轉(Inversion of Control,縮寫為IoC),是面向對象編程中的一種設計原則,可以用來減低電腦代碼之間的耦合度。其中最常見的方式叫做依賴註入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查找”(Dependency Lookup)。通過控制反轉,對象在被創建的時候,由一個調控系統內所有對象的外界實體,將其所依賴的對象的引用傳遞給它。也可以說,依賴被註入到對象中。
AOP:Aspect Oriented Programming的縮寫,意為:面向切麵編程,通過預編譯方式和運行期動態代理實現程式功能的統一維護的一種技術。
(以上是度娘給出的說法,可以看到這樣的說法很不容易讓人理解,過於官方化,下麵是我的理解)

IOC:我們平時在寫Java程式時,總要通過new的方式來產生一個對象,對象的生死存亡是與我們直接掛鉤的,我們需要它時,就new一個,不需要他時就通過GC幫我們回收;控制反轉的意思就是將對象的生死權交給第三方,由第三方來生成和銷毀對象,而我們只在需要時從第三方手中取獲取,其餘不管,這樣,對象的控制權就在第三方手裡,我們只有使用權!這就是所謂的控制反轉!
在Spring中,提供了XML文件配置和註解的方式來向第三方表明我們需要第三方幫我們創建什麼對象,Spring就是這個第三方!它負責通過XML文件的解析或者包掃描的方式,找到我們給出的映射關係,利用反射機制,在其內部幫我們“new”出對象,再存儲起來,供我們使用!

AOP :就是動態代理的體現,在Spring中就是利用JDKProxy或者CGLibProxy技術,對方法進行攔截!
比如說有一個叫做fun()的方法,我們在其執行前後對其攔截:

 

就像這樣,fun看成是縱向的線,那麼就相當於用平面把這條線截斷了!

 

有了上面的鋪墊,我們可以大概知道,Sping的IOC和AOP可以幫我們創建並管理對象,可以對對象的方法進行攔截,那麼這兩個技術合在一起,就可以達到自動幫我們創建、管理、並對對象進行攔截!

下麵先給出我簡化的SpringIOC容器框圖:
模擬的IOC框圖

這是我簡化後的IOC框圖,實際上的SpringIOC是非常龐大的,裡面包含了許多介面,以及繼承關係,它把要處理的事務區分的非常細膩,將問題由大化小,層層遞減,把面向介面,高內聚低耦合體現的淋漓盡致。
Spring提供了註解和Xml方式的註入,所以後面會有兩個分支,分別處理註解和XML文件的配置!

BeanFactory:
在別的地方說法是一個最底層容器,其實不要被其“誤導”,在我這它僅僅只是一個介面,只提供了最基礎的一些方法,而方法的具體實現就需要真正的高級容器了!代碼如下:

 1 public interface BeanFactory {
 2     String FACTORY_BEAN_PREFIX = "&";
 3     Object getBean(String var1) throws BeansException;
 4     <T> T getBean(String var1, Class<T> var2) throws BeansException;
 5     <T> T getBean(Class<T> var1) throws BeansException;
 6     Object getBean(String var1, Object... var2) throws BeansException;
 7     <T> T getBean(Class<T> var1, Object... var2) throws BeansException;
 8     boolean containsBean(String var1);
 9     boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
10     boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
11     boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;
12     Class<?> getType(String var1) throws NoSuchBeanDefinitionException;
13     String[] getAliases(String var1);
14 }

(這裡我直接挪用了Spring的源碼,由於是模擬實現,所以後面只實現了其getBean的方法)

 

ApplicationContext:
在別的地方的說法是一個高級容器,其實,它還是一個介面,只不過在源碼中其繼承了許多介面(核心還是BeanFactory),是一個集大成者,提供了遠比BeanFactory更多的功能。但目前所要實現的核心暫時用不上它,所以暫時留一個空介面吧...

1 public interface ApplicationContext extends BeanFactory {
2     // TODO
3 }

說到這裡,就不能往下繼續了,因為在上面我們看到的,所謂的“容器”,僅僅是定義了介面,完全不能裝東西啊,還有,所謂的容器里又要裝什麼?這裡就要引入Bean!

Bean:
其實就是IOC容器里存放的東西!前面我說過,Spring會根據我們給出的映射關係,幫我們創建對象並存儲起來,那麼是否這個對象就是Bean?是!但也不是!如果說Spring僅僅只是幫我們管理對象,那麼它的功能也太單一了,那麼,現在不得不再次提到前面說過的AOP!
前面說到Spring中的AOP使用了JDKProxy和CGLibProxy這兩種代理技術,這兩種技術的目的都是產生代理對象,對方法進行攔截。那麼,是否這個代理對象就是我們的Bean?不完全是!Bean其實是原對象+代理對象!先不急,看到後面就會明白!

下麵介紹兩種動態代理技術:
JDKProxy
使用JDK代理,其所代理的類,必須要實現某一個介面:

 1 @SuppressWarnings("unchecked")
 2 public <E> E getJDKProxy(Class<?> klass, Object tagObject) {
 3     return (E) Proxy.newProxyInstance(klass.getClassLoader(), 
 4             klass.getInterfaces(),
 5             new InvocationHandler() {
 6                 @Override
 7                 public Object invoke(Object proxy, Method method, Object[] args) 
 8                         throws Throwable {
 9                     // TODO 置前攔截,可對參數args進行判斷
10                     Object result = null;
11                     try {
12                         result = method.invoke(tagObject, args);
13                     } catch (Exception e) {
14                         // TODO 對異常進行攔截
15                         throw e;
16                     }
17                     // TODO 置後攔截,可對方法返回值進行修改
18                     return result;
19                 }
20             });
21 }

使用JDK代理的話就不得不傳入一個原始對象,所以如果不考慮CGLib代理,那麼Bean就是原始對象+代理對象!

 

CGLibProxy:
使用CGLib代理,是讓被代理的類作為代理對象的父類,故原類不能被final修飾,也不能對final修飾的方法攔截!

以下是網上絕大多數人給出的用法:

 1 @SuppressWarnings("unchecked")
 2 public <E> E getCGLibProxy(Class<?> klass) {
 3     Enhancer enhancer = new Enhancer();
 4     enhancer.setSuperclass(klass); // 從這裡可以明顯看到,讓被代理的類作為了代理對象的父類
 5     enhancer.setCallback(new MethodInterceptor() {
 6         @Override
 7         public Object intercept(Object proxyObject, Method method, Object[] args, MethodProxy methodProxy) 
 8                 throws Throwable {
 9             // TODO 置前攔截,可對參數args進行判斷
10             Object result = null;
11             try {
12                 result = methodProxy.invokeSuper(proxyObject, args);
13             } catch (Exception e) {
14                 // TODO 對異常進行攔截
15                 throw e;
16             }
17             // TODO 置後攔截,可對方法返回值進行修改
18             return result;
19         }
20     });
21     return (E) enhancer.create();
22 }

這種方式是沒錯,但是不適用於後面要做的,至於原因,後面分析到了就會明白!
所以使用如下方式:

 1 @SuppressWarnings("unchecked")
 2 public <E> E getCGLibProxy(Class<?> klass, Object tagObject) {
 3     Enhancer enhancer = new Enhancer();
 4     enhancer.setSuperclass(klass);
 5     enhancer.setCallback(new MethodInterceptor() {
 6         @Override
 7         public Object intercept(Object proxyObject, Method method, Object[] args, MethodProxy methodProxy) 
 8                 throws Throwable {
 9             // TODO 置前攔截,可對參數args進行判斷
10             Object result = null;
11             try {
12                 result = method.invoke(tagObject, args);
13             } catch (Exception e) {
14                 // TODO 對異常進行攔截
15                 throw e;
16             }
17             // TODO 置後攔截,可對方法返回值進行修改
18             return result;
19         }
20     });
21     return (E) enhancer.create();
22 }

由於是模擬實現,後面就全部採用CGLib代理!
可以看到以上面這種方式進行CGLib代理就需要原始對象,那麼前面說到的Bean就必須是原對象+代理對象!
當然我知道以invokeSuper那種方式是不需要原始對象,但是原因不是因為Bean,還在後面!

綜上,Bean的定義如下:

1 public interface BeanElement {
2     <E> E getProxy();
3     Object getObject();
4     boolean isDI(); // 用於以後判斷是否完成註入
5 }

在這裡我把BeanElement定義為了一個介面,後面會產生兩個分支,會產生兩種不同處理方式的Bean,用介面更靈活,耦合也低!

現在知道了Bean到底是什麼,我們就可以往下繼續進行:


AbstractApplicationContext:
ApplicationContext的具體實現類,但它是一個抽象類,只能實現部分功能,往後在它的基礎上還要分化成兩支,那麼,把所有的Bean存在這裡面再合適不過了!

 1 public abstract class AbstractApplicationContext implements ApplicationContext {
 2     protected static final Map<String, String> beanNameMap;  // key : id      value : className
 3     protected static final Map<String, BeanElement> beanMap; // key : className value : Bean
 4     protected AopFactory aopFactory; // Aop工廠,生產代理對象
 5     
 6     static {
 7         beanNameMap = new HashMap<>();
 8         beanMap = new HashMap<>();
 9     }
10     
11     protected AbstractApplicationContext() {
12         aopFactory = new AopFactory();
13         // 設置切麵
14         aopFactory.setAdvice(
15                 (new AdviceHander())
16                 .setIntercepterFactory(new IntercepterLoader()));
17     }
18     
19     protected void add(String id, String className, BeanElement bean) throws BeansException {
20         if (beanMap.containsKey(className)) {
21             // TODO 可以拋異常!
22             return;
23         }
24         beanMap.put(className, bean);
25         if (id.length() <= 0) return;
26         if (beanNameMap.containsKey(id)) {
27             throw new BeansException("bean:" + id + "已定義!");
28         }
29         beanNameMap.put(id, className);
30     }
31 }

其中的aopFactory是代理工廠,負責生產代理,會在後面給出,先不急。
可以看到,AbstractApplicationContext這個類持有了兩張靜態的map,第一組是用來存取Bean的別名(id),第二組用來存放真正的Bean,這就是我們真正意義上的容器,由於其map都是static修飾的,在類載入的時候就存在了,所以往後有別的類繼承它時,這兩個map是共用的!只增加了一個add方法,只要是繼承自它的子類,都會通過這種方式添加Bean!並且這個類是protected的,不對外提供使用!

 

我們先進行左邊的分支,用註解的方式完成IOC
這裡說的註解都是自定義註解,屬於RUNTIME,就必須通過反射機制來獲取,反射機制就要得到類或者類名稱,那麼就先得到符合要求的類,這裡就要用到包掃描,我在前面的博客中有介紹過:【Java】包、jar包的掃描
首先是對類的註解:
@Component

1 @Retention(RetentionPolicy.RUNTIME)
2 @Target(ElementType.TYPE)
3 public @interface Component {
4     String id() default "";
5 }

其中的id相當於類名稱的別名,具有唯一性,如果不設置則不處理!通過這個註解我們就可以判斷哪個類是需要進行操作的,就應該自動地為這個類生成一個對象和代理對象,將其添加到beanMap中,就是bean的標誌!
如果說用過Spring的XML配置,這其實就相當於一個bean標簽:

1 <bean id="XXX" class="XXX">
2     ......
3 </bean>

註解中的id就相當於id屬性,我們是通過反射機制得到註解的,當然能得到類名稱,那就相當於有了class屬性!

但是僅僅有這個註解是完全不夠的,我們只能通過反射機制產生一個對象,但它的成員都沒賦值,僅僅是一具軀殼,所以就需要對成員添加註解,將我們需要的值註入進來;當然也可以給方法添加註解,通過setter方法給成員賦值,Spring就是使用的這種方式!
這是對成員的註解:
@Autowired

1 @Retention(RetentionPolicy.RUNTIME)
2 @Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
3 public @interface Autowired {
4     boolean requisite() default true;
5 }

這裡直接使用的spring源碼,在源碼中,這個註解可以成員、方法以及構造方法添加。其中的requisite是是否必須註入,這是Spring提供的更為細膩的操作,我的實現暫時不考慮它。

如果說這個註解是給成員添加的,那麼標志著它需要賦值!使用這個註解的前提是它本身是一個複雜類型,不是基本類型,它的賦值,是將我們beanMap中的符合要求的Bean註入進去!至於基本類型後面有別的解決辦法。
用Component 和Autowired註解其實就相當於如下XML的配置:

1 <bean id="XXX" class="XXX">
2     <property name="XXX" ref="XXX">
3 </bean>

我們同樣是通過反射機制得到的Autowired註解,那麼一定可以得到成員名稱,和成員類型,成員名稱就相當於name屬性,通過成員類型就可以得到類型名稱,就相當於ref屬性!

如果說是給構造方法添加,那麼就規定了我們在反射機制執行時需要調用哪個構造方法,相當於如下:

1 <bean id="XXX" class="XXX">
2     <constructor-arg index="0" ref="XXX">
3     <constructor-arg index="1" ref="XXX">
4 </bean>

對於構造方法的處理我覺得使用註解的方式比XML配置要更好,註解可以直接定位到某一個構造方法,但是XML文件的方式就需要遍歷比較,找出符合要求的,而且關於這個符合要求這一說還有更為複雜的問題,我會在後面用XML的方式詳細介紹!

還有一種就是對方法添加,實際上就是提供給setter方法使用的,通過執行setter方法給成員賦值,可能看到這裡會覺得這樣做是不是多此一舉,其實不是,因為這樣做會避免我後面會談到的迴圈依賴的問題!所以給方法添加,和對成員添加等效:

1 <bean id="XXX" class="XXX">
2     <property name="XXX" ref="XXX">
3 </bean>


上面我說過使用Autowired 是不處理基本類型的,它是將bean註入的,基本類型完全沒有必要作為bean,那麼,我們就可以給出一個註解,直接賦值:

@Value

1 @Retention(RetentionPolicy.RUNTIME)
2 @Target({ElementType.FIELD, ElementType.PARAMETER})
3 public @interface Value {
4     String value();
5 }

其中value就是我們要註入的值,但是它是String類型的,可能需要強制類型轉換
演示如下:

 1 @Component
 2 public class TestA {
 3     @Autowired
 4     private TestB testB;
 5     @Value(value="直接賦值")
 6     private String member;
 7     
 8     public TestA() {
 9     }
10 
11 }
12 
13 @Component(id = "testB")
14 public class TestB {
15     private int a;
16     private TestA testA;
17     
18     @Autowired
19     public TestB(@Value(value="10")int a, TestA testA) {
20         this.a = a;
21         this.testA = testA;
22     }
23     
24 }

就相當於:

1 <bean class="xxx.xxx.TestA">
2     <property name="testB" ref="xxx.xxx.TestB">
3     <property name="member" value="直接賦值">
4 </bean>
5 <bean id="testB" class="xxx.xxx.TestB">
6     <constructor-arg index="0" value="10"></constructor-arg>
7     <constructor-arg index="1" ref="xxx.xxx.TestA"></constructor-arg>
8 </bean>

為了簡單處理,Autowired註解我在後面就只處理成員的。

有了上面的兩個註解是否夠呢?當然不夠。仔細想一想,如果說我們需要Spring幫我們創建的對象,其對應的類又被打成了jar包,那麼我們完全沒有辦法對已經形成jar包的代碼添加註解;又或者說是我們需要創建的對象不是通過反射機制就能產生的,它是一個工廠對象,需要走工廠模式那一套來創建,上面的兩個註解就遠遠不能滿足我們的要求了!因此,我們還需要一個作為補充的註解:

 

@Bean

1 @Retention(RetentionPolicy.RUNTIME)
2 @Target(ElementType.METHOD)
3 public @interface Bean {
4     String id() default "";
5 }

可以看出這是對方法的註解,因為我們可以通過反射機制執行方法,將方法的返回值作為Bean的原始對象,再產生一個代理對象,這樣就能解決上面的所說的問題!

 1 @Component
 2 public class Action {
 3     
 4     @Bean(id="getDocumentBuilderFactory")
 5     public DocumentBuilderFactory getDocumnet() throws Exception {
 6         return DocumentBuilderFactory.newInstance();
 7     }
 8     
 9     @Bean
10     public DocumentBuilder getDocumentBuilder(DocumentBuilderFactory factory) throws Exception {
11         return factory.newDocumentBuilder();
12     }
13     
14 }

就相當於:

1 <bean class="xxx.xxx.Action "></bean>
2 <bean class="javax.xml.parsers.DocumentBuilderFactory" id="getDocumentBuilderFactory" 
3     factory-method="newInstance"></bean>
4 <bean class="javax.xml.parsers.DocumentBuilderFactory" 
5     factory-bean="getDocumentBuilderFactory" factory-method="newDocumentBuilder">
6 </bean>

有了這些註解,我們就能對包進行掃描,可以繼續向下進行!

AnnotationContext:
這個類繼承自AbstractApplicationContext,它是protected的,不對外提供,我用它來進行有關註解的掃描和解析,但它的功能有限,不能完成全部的註入,這會涉及到註入的順序,以及註入之間的依賴關係:

  1 /**
  2  * 執行包掃描
  3  * 將符合要求的結果添加到父類AbstractApplicationContext的beanMap中
  4  * 只處理@Component和@Bean註解
  5  * @Autowired註解留給下一級處理
  6  */
  7 public class AnnotationContext extends AbstractApplicationContext {
  8     // method緩衝區,保存暫時不能執行的方法,其中的MethodBuffer用來封裝method,以及invoke時所需要的內容
  9     private List<MethodBuffer> methodList; 
 10 
 11     protected AnnotationContext() {
 12     }
 13     
 14     protected AnnotationContext(String packageName) {
 15         scanPackage(packageName);
 16     }
 17     
 18     /**
 19      * 通過aopFactory產生代理對象,將代理對象和原始對象封裝成bean添加到父類的map中
 20      */
 21     private void addBean(Class<?> klass, Object object, String id, String className) {
 22         // aopFactory是其父類AbstractApplicationContext的成員,原來產生代理對象
 23         Object proxy = aopFactory.creatCGLibProxy(klass, object);
 24         // AnnoationBean是BeanElement介面的一個實現類
 25         AnnotationBean bean = new AnnotationBean(object, proxy);
 26         // 父類AbstractApplicationContext的add方法
 27         add(id, className, bean);
 28     }
 29     
 30     protected void scanPackage(String packageName) {
 31         new PackageScanner() {
 32             @Override
 33             public void dealClass(Class<?> klass) {
 34                 if (!klass.isAnnotationPresent(Component.class)) return;
 35                 Component component = klass.getAnnotation(Component.class);
 36                 String className = klass.getName();
 37                 String name = component.id();
 38                 try {
 39                     // 這裡簡單起見,不考慮構造方法的重載,預設執行無參構造 
 40                     Object object = klass.newInstance();
 41                     // 產生BeanElement,加入到beanMap中
 42                     addBean(klass, object, name, className);
 43                     // 處理帶有@Bean註解的方法
 44                     dealMethod(klass, object);
 45                 } catch (Exception e) {
 46                     // TODO 
 47                     e.printStackTrace();
 48                 }
 49             }
 50         }.scanPackage(packageName);
 51         if (methodList == null) return;
 52         // 執行緩存的所有方法
 53         for (MethodBuffer methodBuffer : methodList) {
 54             // 獲得方法執行所需要的東西
 55             String id = methodBuffer.getId();
 56             Class<?> returnClass = methodBuffer.getReturnClass();
 57             Method method = methodBuffer.getMethod();
 58             Object object = methodBuffer.getObject();
 59             Parameter[] parameters = methodBuffer.getParameters();
 60             
 61             try {
 62                 dealMultiParaMethod(returnClass, method, object, parameters, id);
 63             } catch (Exception e) {
 64                 // TODO 
 65                 e.printStackTrace();
 66             } 
 67         }
 68         methodList.clear();
 69     }
 70     
 71     private void dealMultiParaMethod(Class<?> returnClass, Method method, 
 72             Object object, Parameter[] parameters, String id) 
 73                     throws BeansException, IllegalAccessException, IllegalArgumentException, 
 74                     InvocationTargetException, ValueOnlyPrimitiveType {
 75         int count = parameters.length;
 76         Object[] result = new Object[count];
 77         
 78         for (int index = 0; index < count; index++) {
 79             Parameter para = parameters[index];
 80             // 判斷參數是否帶有@Value註解
 81             if (para.isAnnotationPresent(Value.class)) { 
 82                 Class<?> type = para.getType();
 83                 // 判斷@Value註解標識的參數是否是基本類型(八大類型和String)
 84                 if (!type.isPrimitive() && !type.equals(String.class)) {
 85                     throw new ValueOnlyPrimitiveType("Value只能用基本類型!");
 86                 }
 87                 // TypeConversion是我自己的一個工具類,用於將字元串轉換成基本類型!
 88                 result[index] = TypeConversion.getValue(para.getAnnotation(Value.class).value(), 
 89                                             para.getType().getSimpleName());
 90             } else {
 91                 // 如果不是基本類型,那麼就需要從beanMap中獲取符合要求的bean
 92                 result[index] = getBean(para.getType());
 93             }
 94         }
 95         // 執行方法,獲得方法返回值
 96         Object returnObject = method.invoke(object, result);
 97         // 為方法執行結果創建bean,添加到beanMap中
 98         addBean(returnClass, returnObject , id, returnClass.getName());
 99     }
100     
101     /**
102      * 遍歷所有方法,處理帶有@Bean註解的方法
103      */
104     private void dealMethod(Class<?> klass, Object object) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
105         Method[] methods = klass.getDeclaredMethods();
106         for (Method method : methods) {
107             if (!method.isAnnotationPresent(Bean.class)) continue; 
108             
109             Class<?> returnType = method.getReturnType();
110             if (returnType.equals(void.class)) {
111                 // TODO 如果沒有返回值,那麼根本沒有必要做
112                 return;
113             }
114             String id= method.getAnnotation(Bean.class).id();
115             Parameter[] parameters = method.getParameters();
116             // 判斷方法是否有參數,沒有參數,直接執行,添加Bean
117             // 有參數就先緩存起來,等所有都掃描完成後再執行
118             if (parameters.length <= 0) {
119                 Object returnObject = method.invoke(object);
120                 addBean(returnType, returnObject, id, returnType.getName());
121             } else {
122                 (methodList = methodList == null ? new ArrayList<>() : methodList)
123                     .add(new MethodBuffer(returnType, object, method, parameters, id));
124             }
125         }
126     }
127     
128 }

這個類只負責掃描包,為帶有@Component註解的類通過反射機制生成對象,再通過代理工廠將其加工成代理對象,然後封裝再AnnotationBean中作為bean,將其添加到BeanMap中!
其次還處理了帶有@Bean註解的的方法,如果說是方法帶有參數,那麼就像將這個方法的執行延後,等所有東西都掃描完成後再執行;而對於無參的方法,則可以直接執行,併為執行結果產生的對象創建代理,生成AnnotationBean,添加進beanMap中。至於@Autowired註解這個類暫不處理,留給下一級處理!


AnnotationBean類的定義如下:

 1 /**
 2  * 註解分支中BeanElement的具體實現類
 3  */
 4 public class AnnotationBean implements BeanElement {
 5     private boolean DI; // 判斷是否完成註入
 6     private Object object; // 原始對象
 7     private Object proxy; // 代理對象
 8     
 9     AnnotationBean() {
10         this(false, null, null);
11     }
12     
13     AnnotationBean(Object object, Object proxy) {
14         this(false, object, proxy);
15     }
16     
17     AnnotationBean(boolean dI, Object object, Object proxy) {
18         DI = dI;
19         setObject(object);
20         setProxy(proxy);
21     }
22     
23     @Override
24     public Object getObject() {
25         return object;
26     }
27
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • list是C++標準模版庫(STL,Standard Template Library)中的部分內容。實際上,list容器就是一個雙向鏈表,可以高效地進行插入刪除元素。 使用list容器之前必須加上STL的list容器的頭文件:#include ...
  • 多線程 參數傳遞 1,值傳遞,拷貝一份新的給新的線程。線程1中有個int變數a,線上程1中啟動線程2,參數是a的值,這時就會拷貝a,線程1和線程2不共用a。 2,引用傳遞,不拷貝一份新的給新的線程。線程1中有個int變數a,線上程1中啟動線程2,參數是a的引用,這時就不會拷貝a,線程1和線程2共用a ...
  • T1 帽子戲法 問題描述 小 Y 有一個$n n n$的“帽子立方體” ,即一個$n$層的立方體,每層的帽子都 可以排成$n n$的矩陣。 “帽子立方體”中的每一個帽子都有一個顏色,顏色共 26 種,用 26 個大寫字母來表示。 現在,小 Y 邀請小 F 來表演她的帽子戲法。小 F 會 $2$ 種帽 ...
  • 1.EL表達式簡介 EL全名為Expression Language。EL的主要作用為: 獲取數據:EL表達式主要用於替換jsp頁面中的腳本表達式,以從各種類型的web域中檢索java對象,獲取數據。(某個web域中的對象,訪問JavaBean的屬性,訪問list集合,訪問map集合,訪問數組) 執 ...
  • 在開發過程中,需要修改資料庫模型,而且還要在修改之後更新資料庫。最直接的方式就是刪除舊表,但這樣會丟失數據。 更好的解決辦法是使用資料庫遷移框架,它可以追蹤資料庫模式的變化,然後把變動應用到資料庫中。 在Flask中可以使用Flask-Migrate擴展,來實現數據遷移。並且集成到Flask-Scr ...
  • 我們都知道程式員花費大量的時間在編寫、閱讀和編輯代碼上,因此一定要使用高效的文本編輯器才能夠提高並很好的完成工作的效率和保證工作的質量。 什麼是高效的文本編輯器呢?除了自己用的得心應手外,小U認為還應該包含以下幾個特點: ·突出代碼的結構,讓你在編寫代碼時就能夠發現常見的bug; 本人微信:mmp9 ...
  • 題目描述 已知:Sn​=1+1/2+1/3+…+1/n。顯然對於任意一個整數K,當n足夠大的時候,Sn​大於K。 現給出一個整數K(1≤K≤15),要求計算出一個最小的n;使得Sn​>K。 輸入輸出格式 輸入格式: 一個正整數K 輸出格式: 一個正整數N 輸入樣例:1 輸出樣例:2 答案代碼: #i ...
  • 一、前言 之前ZYNQ與PC之間的網路連接依賴於外接硬體協議棧晶元,雖然C驅動非常簡單,但網路帶寬受限。現採用LWIP+PS端MAC控制器+PHY晶元的通用架構。關於LWIP庫,已經有很多現成的資料和書籍。其有兩套API,一個是SOCKET,另一個是本例中要用到的RAW。RAW API理解起來較為復 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...