三天吃透Spring面試八股文

来源:https://www.cnblogs.com/tyson03/archive/2023/03/25/17255817.html
-Advertisement-
Play Games

本文已經收錄到Github倉庫,該倉庫包含電腦基礎、Java基礎、多線程、JVM、資料庫、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分散式、微服務、設計模式、架構、校招社招分享等核心知識點,歡迎star~ Github地址 Spring的優點 通過控制反轉 ...


本文已經收錄到Github倉庫,該倉庫包含電腦基礎、Java基礎、多線程、JVM、資料庫、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分散式、微服務、設計模式、架構、校招社招分享等核心知識點,歡迎star~

Github地址

Spring的優點

  • 通過控制反轉和依賴註入實現松耦合
  • 支持面向切麵的編程,並且把應用業務邏輯和系統服務分開。
  • 通過切麵和模板減少樣板式代碼。
  • 聲明式事務的支持。可以從單調繁冗的事務管理代碼中解脫出來,通過聲明式方式靈活地進行事務的管理,提高開發效率和質量。
  • 方便集成各種優秀框架。內部提供了對各種優秀框架的直接支持(如:Hessian、Quartz、MyBatis等)。
  • 方便程式的測試。Spring支持Junit4,添加註解便可以測試Spring程式。

Spring 用到了哪些設計模式?

1、簡單工廠模式BeanFactory就是簡單工廠模式的體現,根據傳入一個唯一標識來獲得 Bean 對象。

@Override
public Object getBean(String name) throws BeansException {
    assertBeanFactoryActive();
    return getBeanFactory().getBean(name);
}

2、工廠方法模式FactoryBean就是典型的工廠方法模式。spring在使用getBean()調用獲得該bean時,會自動調用該bean的getObject()方法。每個 Bean 都會對應一個 FactoryBean,如 SqlSessionFactory 對應 SqlSessionFactoryBean

3、單例模式:一個類僅有一個實例,提供一個訪問它的全局訪問點。Spring 創建 Bean 實例預設是單例的。

4、適配器模式:SpringMVC中的適配器HandlerAdatper。由於應用會有多個Controller實現,如果需要直接調用Controller方法,那麼需要先判斷是由哪一個Controller處理請求,然後調用相應的方法。當增加新的 Controller,需要修改原來的邏輯,違反了開閉原則(對修改關閉,對擴展開放)。

為此,Spring提供了一個適配器介面,每一種 Controller 對應一種 HandlerAdapter 實現類,當請求過來,SpringMVC會調用getHandler()獲取相應的Controller,然後獲取該Controller對應的 HandlerAdapter,最後調用HandlerAdapterhandle()方法處理請求,實際上調用的是Controller的handleRequest()。每次添加新的 Controller 時,只需要增加一個適配器類就可以,無需修改原有的邏輯。

常用的處理器適配器:SimpleControllerHandlerAdapterHttpRequestHandlerAdapterAnnotationMethodHandlerAdapter

// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

public class HttpRequestHandlerAdapter implements HandlerAdapter {

    @Override
    public boolean supports(Object handler) {//handler是被適配的對象,這裡使用的是對象的適配器模式
        return (handler instanceof HttpRequestHandler);
    }

    @Override
    @Nullable
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {

        ((HttpRequestHandler) handler).handleRequest(request, response);
        return null;
    }
}

5、代理模式:spring 的 aop 使用了動態代理,有兩種方式JdkDynamicAopProxyCglib2AopProxy

6、觀察者模式:spring 中 observer 模式常用的地方是 listener 的實現,如ApplicationListener

7、模板模式: Spring 中 jdbcTemplatehibernateTemplate 等,就使用到了模板模式。

什麼是AOP?

面向切麵編程,作為面向對象的一種補充,將公共邏輯(事務管理、日誌、緩存等)封裝成切麵,跟業務代碼進行分離,可以減少系統的重覆代碼和降低模塊之間的耦合度。切麵就是那些與業務無關,但所有業務模塊都會調用的公共邏輯。

AOP有哪些實現方式?

AOP有兩種實現方式:靜態代理和動態代理。

靜態代理

靜態代理:代理類在編譯階段生成,在編譯階段將通知織入Java位元組碼中,也稱編譯時增強。AspectJ使用的是靜態代理。

缺點:代理對象需要與目標對象實現一樣的介面,並且實現介面的方法,會有冗餘代碼。同時,一旦介面增加方法,目標對象與代理對象都要維護。

動態代理

動態代理:代理類在程式運行時創建,AOP框架不會去修改位元組碼,而是在記憶體中臨時生成一個代理對象,在運行期間對業務方法進行增強,不會生成新類。

Spring AOP的實現原理

SpringAOP實現原理其實很簡單,就是通過動態代理實現的。如果我們為Spring的某個bean配置了切麵,那麼Spring在創建這個bean的時候,實際上創建的是這個bean的一個代理對象,我們後續對bean中方法的調用,實際上調用的是代理類重寫的代理方法。而SpringAOP使用了兩種動態代理,分別是JDK的動態代理,以及CGLib的動態代理

JDK動態代理和CGLIB動態代理的區別?

Spring AOP中的動態代理主要有兩種方式:JDK動態代理和CGLIB動態代理。

JDK動態代理

如果目標類實現了介面,Spring AOP會選擇使用JDK動態代理目標類。代理類根據目標類實現的介面動態生成,不需要自己編寫,生成的動態代理類和目標類都實現相同的介面。JDK動態代理的核心是InvocationHandler介面和Proxy類。

缺點:目標類必須有實現的介面。如果某個類沒有實現介面,那麼這個類就不能用JDK動態代理。

CGLIB動態代理

通過繼承實現。如果目標類沒有實現介面,那麼Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB(Code Generation Library)可以在運行時動態生成類的位元組碼,動態創建目標類的子類對象,在子類對象中增強目標類。

CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記為final,那麼它是無法使用CGLIB做動態代理的。

優點:目標類不需要實現特定的介面,更加靈活。

什麼時候採用哪種動態代理?

  1. 如果目標對象實現了介面,預設情況下會採用JDK的動態代理實現AOP
  2. 如果目標對象實現了介面,可以強制使用CGLIB實現AOP
  3. 如果目標對象沒有實現了介面,必須採用CGLIB庫

兩者的區別

  1. jdk動態代理使用jdk中的類Proxy來創建代理對象,它使用反射技術來實現,不需要導入其他依賴。cglib需要引入相關依賴:asm.jar,它使用位元組碼增強技術來實現。
  2. 當目標類實現了介面的時候Spring Aop預設使用jdk動態代理方式來增強方法,沒有實現介面的時候使用cglib動態代理方式增強方法。

Spring AOP相關術語

(1)切麵(Aspect):切麵是通知和切點的結合。通知和切點共同定義了切麵的全部內容。

(2)連接點(Join point):指方法,在Spring AOP中,一個連接點總是代表一個方法的執行。連接點是在應用執行過程中能夠插入切麵的一個點。這個點可以是調用方法時、拋出異常時、甚至修改一個欄位時。切麵代碼可以利用這些點插入到應用的正常流程之中,並添加新的行為。

(3)通知(Advice):在AOP術語中,切麵的工作被稱為通知。

(4)切入點(Pointcut):切點的定義會匹配通知所要織入的一個或多個連接點。我們通常使用明確的類和方法名稱,或是利用正則表達式定義所匹配的類和方法名稱來指定這些切點。

(5)引入(Introduction):引入允許我們向現有類添加新方法或屬性。

(6)目標對象(Target Object): 被一個或者多個切麵(aspect)所通知(advise)的對象。它通常是一個代理對象。

(7)織入(Weaving):織入是把切麵應用到目標對象並創建新的代理對象的過程。在目標對象的生命周期里有以下時間點可以進行織入:

  • 編譯期:切麵在目標類編譯時被織入。AspectJ的織入編譯器是以這種方式織入切麵的。
  • 類載入期:切麵在目標類載入到JVM時被織入。需要特殊的類載入器,它可以在目標類被引入應用之前增強該目標類的位元組碼。AspectJ5的載入時織入就支持以這種方式織入切麵。
  • 運行期:切麵在應用運行的某個時刻被織入。一般情況下,在織入切麵時,AOP容器會為目標對象動態地創建一個代理對象。SpringAOP就是以這種方式織入切麵。

Spring通知有哪些類型?

在AOP術語中,切麵的工作被稱為通知。通知實際上是程式運行時要通過Spring AOP框架來觸發的代碼段。

Spring切麵可以應用5種類型的通知:

  1. 前置通知(Before):在目標方法被調用之前調用通知功能;
  2. 後置通知(After):在目標方法完成之後調用通知,此時不會關心方法的輸出是什麼;
  3. 返回通知(After-returning ):在目標方法成功執行之後調用通知;
  4. 異常通知(After-throwing):在目標方法拋出異常後調用通知;
  5. 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之後執行自定義的邏輯。

什麼是依賴註入?

在Spring創建對象的過程中,把對象依賴的屬性註入到對象中。依賴註入主要有兩種方式:構造器註入和屬性註入。什麼是IOC?

IOC:控制反轉,由Spring容器管理bean的整個生命周期。通過反射實現對其他對象的控制,包括初始化、創建、銷毀等,解放手動創建對象的過程,同時降低類之間的耦合度。

IOC的好處?

ioc的思想最核心的地方在於,資源不由使用資源者管理,而由不使用資源的第三方管理,這可以帶來很多好處。第一,資源集中管理,實現資源的可配置和易管理。第二,降低了使用資源雙方的依賴程度,也就是我們說的耦合度。

也就是說,甲方要達成某種目的不需要直接依賴乙方,它只需要達到的目的告訴第三方機構就可以了,比如甲方需要一雙襪子,而乙方它賣一雙襪子,它要把襪子賣出去,並不需要自己去直接找到一個賣家來完成襪子的賣出。它也只需要找第三方,告訴別人我要賣一雙襪子。這下好了,甲乙雙方進行交易活動,都不需要自己直接去找賣家,相當於程式內部開放介面,賣家由第三方作為參數傳入。甲乙互相不依賴,而且只有在進行交易活動的時候,甲才和乙產生聯繫。反之亦然。這樣做什麼好處麽呢,甲乙可以在對方不真實存在的情況下獨立存在,而且保證不交易時候無聯繫,想交易的時候可以很容易的產生聯繫。甲乙交易活動不需要雙方見面,避免了雙方的互不信任造成交易失敗的問題。因為交易由第三方來負責聯繫,而且甲乙都認為第三方可靠。那麼交易就能很可靠很靈活的產生和進行了。

這就是ioc的核心思想。生活中這種例子比比皆是,支付寶在整個淘寶體系裡就是龐大的ioc容器,交易雙方之外的第三方,提供可靠性可依賴可靈活變更交易方的資源管理中心。另外人事代理也是,雇佣機構和個人之外的第三方。

參考鏈接:https://www.zhihu.com/question/23277575/answer/24259844

IOC容器初始化過程?

  1. 從XML中讀取配置文件。
  2. 將bean標簽解析成 BeanDefinition,如解析 property 元素, 並註入到 BeanDefinition 實例中。
  3. 將 BeanDefinition 註冊到容器 BeanDefinitionMap 中。
  4. BeanFactory 根據 BeanDefinition 的定義信息創建實例化和初始化 bean。

單例bean的初始化以及依賴註入一般都在容器初始化階段進行,只有懶載入(lazy-init為true)的單例bean是在應用第一次調用getBean()時進行初始化和依賴註入。

// AbstractApplicationContext
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

多例bean 在容器啟動時不實例化,即使設置 lazy-init 為 false 也沒用,只有調用了getBean()才進行實例化。

loadBeanDefinitions採用了模板模式,具體載入 BeanDefinition 的邏輯由各個子類完成。

Bean的生命周期

1.調用bean的構造方法創建Bean

2.通過反射調用setter方法進行屬性的依賴註入

3.如果Bean實現了BeanNameAware介面,Spring將調用setBeanName(),設置 Bean的name(xml文件中bean標簽的id)

4.如果Bean實現了BeanFactoryAware介面,Spring將調用setBeanFactory()把bean factory設置給Bean

5.如果存在BeanPostProcessor,Spring將調用它們的postProcessBeforeInitialization(預初始化)方法,在Bean初始化前對其進行處理

6.如果Bean實現了InitializingBean介面,Spring將調用它的afterPropertiesSet方法,然後調用xml定義的 init-method 方法,兩個方法作用類似,都是在初始化 bean 的時候執行

7.如果存在BeanPostProcessor,Spring將調用它們的postProcessAfterInitialization(後初始化)方法,在Bean初始化後對其進行處理

8.Bean初始化完成,供應用使用,這裡分兩種情況:

8.1 如果Bean為單例的話,那麼容器會返回Bean給用戶,並存入緩存池。如果Bean實現了DisposableBean介面,Spring將調用它的destory方法,然後調用在xml中定義的 destory-method方法,這兩個方法作用類似,都是在Bean實例銷毀前執行。

8.2 如果Bean是多例的話,容器將Bean返回給用戶,剩下的生命周期由用戶控制。

public interface BeanPostProcessor {
	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
}
public interface InitializingBean {
	void afterPropertiesSet() throws Exception;
}

BeanFactory和FactoryBean的區別?

BeanFactory:管理Bean的容器,Spring中生成的Bean都是由這個介面的實現來管理的。

FactoryBean:通常是用來創建比較複雜的bean,一般的bean 直接用xml配置即可,但如果一個bean的創建過程中涉及到很多其他的bean 和複雜的邏輯,直接用xml配置比較麻煩,這時可以考慮用FactoryBean,可以隱藏實例化複雜Bean的細節。

當配置文件中bean標簽的class屬性配置的實現類是FactoryBean時,通過 getBean()方法返回的不是FactoryBean本身,而是調用FactoryBean#getObject()方法所返回的對象,相當於FactoryBean#getObject()代理了getBean()方法。如果想得到FactoryBean必須使用 '&' + beanName 的方式獲取。

Mybatis 提供了 SqlSessionFactoryBean,可以簡化 SqlSessionFactory的配置:

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
  @Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
	//複雜邏輯
  }
    
  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }
    return this.sqlSessionFactory;
  }
}

在 xml 配置 SqlSessionFactoryBean:

<bean id="tradeSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="trade" />
    <property name="mapperLocations" value="classpath*:mapper/trade/*Mapper.xml" />
    <property name="configLocation" value="classpath:mybatis-config.xml" />
    <property name="typeAliasesPackage" value="com.bytebeats.mybatis3.domain.trade" />
</bean>

Spring 將會在應用啟動時創建 SqlSessionFactory,並使用 sqlSessionFactory 這個名字存儲起來。

BeanFactory和ApplicationContext有什麼區別?

BeanFactory和ApplicationContext是Spring的兩大核心介面,都可以當做Spring的容器。其中ApplicationContext是BeanFactory的子介面。

兩者區別如下:

1、功能上的區別。BeanFactory是Spring裡面最底層的介面,包含了各種Bean的定義,讀取bean配置文檔,管理bean的載入、實例化,控制bean的生命周期,維護bean之間的依賴關係。

ApplicationContext介面作為BeanFactory的派生,除了提供BeanFactory所具有的功能外,還提供了更完整的框架功能,如繼承MessageSource、支持國際化、統一的資源文件訪問方式、同時載入多個配置文件等功能。

2、載入方式的區別。BeanFactroy採用的是延遲載入形式來註入Bean的,即只有在使用到某個Bean時(調用getBean()),才對該Bean進行載入實例化。這樣,我們就不能發現一些存在的Spring的配置問題。如果Bean的某一個屬性沒有註入,BeanFacotry載入後,直至第一次使用調用getBean方法才會拋出異常。

而ApplicationContext是在容器啟動時,一次性創建了所有的Bean。這樣,在容器啟動時,我們就可以發現Spring中存在的配置錯誤,這樣有利於檢查所依賴屬性是否註入。 ApplicationContext啟動後預載入所有的單例Bean,那麼在需要的時候,不需要等待創建bean,因為它們已經創建好了。

相對於基本的BeanFactory,ApplicationContext 唯一的不足是占用記憶體空間。當應用程式配置Bean較多時,程式啟動較慢。

3、創建方式的區別。BeanFactory通常以編程的方式被創建,ApplicationContext還能以聲明的方式創建,如使用ContextLoader。

4、註冊方式的區別。BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但兩者之間的區別是:BeanFactory需要手動註冊,而ApplicationContext則是自動註冊。

Bean註入容器有哪些方式?

1、@Configuration + @Bean

@Configuration用來聲明一個配置類,然後使用 @Bean 註解,用於聲明一個bean,將其加入到Spring容器中。

@Configuration
public class MyConfiguration {
    @Bean
    public Person person() {
        Person person = new Person();
        person.setName("大彬");
        return person;
    }
}

2、通過包掃描特定註解的方式

@ComponentScan放置在我們的配置類上,然後可以指定一個路徑,進行掃描帶有特定註解的bean,然後加至容器中。

特定註解包括@Controller、@Service、@Repository、@Component

@Component
public class Person {
    //...
}
 
@ComponentScan(basePackages = "com.dabin.test.*")
public class Demo1 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Demo1.class);
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean);
    }
}

3、@Import註解導入

@Import註解平時開發用的不多,但是也是非常重要的,在進行Spring擴展時經常會用到,它經常搭配自定義註解進行使用,然後往容器中導入一個配置文件。

@ComponentScan
/*把用到的資源導入到當前容器中*/
@Import({Person.class})
public class App {
    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
        System.out.println(context.getBean(Person.class));
        context.close();
    }
}

4、實現BeanDefinitionRegistryPostProcessor進行後置處理。

在Spring容器啟動的時候會執行 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry 方法,就是等beanDefinition載入完畢之後,對beanDefinition進行後置處理,可以在此進行調整IOC容器中的beanDefinition,從而干擾到後面進行初始化bean。

在下麵的代碼中,我們手動向beanDefinitionRegistry中註冊了person的BeanDefinition。最終成功將person加入到applicationContext中。

public class Demo1 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        MyBeanDefinitionRegistryPostProcessor beanDefinitionRegistryPostProcessor = new MyBeanDefinitionRegistryPostProcessor();
        applicationContext.addBeanFactoryPostProcessor(beanDefinitionRegistryPostProcessor);
        applicationContext.refresh();
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean);
    }
}
 
class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Person.class).getBeanDefinition();
        registry.registerBeanDefinition("person", beanDefinition);
    }
    
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }
}

5、使用FactoryBean介面

如下圖代碼,使用@Configuration + @Bean的方式將 PersonFactoryBean 加入到容器中,這裡沒有向容器中直接註入 Person,而是註入 PersonFactoryBean,然後從容器中拿Person這個類型的bean。

@Configuration
public class Demo1 {
    @Bean
    public PersonFactoryBean personFactoryBean() {
        return new PersonFactoryBean();
    }
 
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Demo1.class);
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean);
    }
}
 
class PersonFactoryBean implements FactoryBean<Person> {
    @Override
    public Person getObject() throws Exception {
        return new Person();
    }

    @Override
    public Class<?> getObjectType() {
        return Person.class;
    }
}

Bean的作用域

1、singleton:單例,Spring中的bean預設都是單例的。

2、prototype:每次請求都會創建一個新的bean實例。

3、request:每一次HTTP請求都會產生一個新的bean,該bean僅在當前HTTP request內有效。

4、session:每一次HTTP請求都會產生一個新的bean,該bean僅在當前HTTP session內有效。

5、global-session:全局session作用域。

Spring自動裝配的方式有哪些?

Spring的自動裝配有三種模式:byType(根據類型),byName(根據名稱)、constructor(根據構造函數)。

byType

找到與依賴類型相同的bean註入到另外的bean中,這個過程需要藉助setter註入來完成,因此必須存在set方法,否則註入失敗。

當xml文件中存在多個相同類型名稱不同的實例Bean時,Spring容器依賴註入仍然會失敗,因為存在多種適合的選項,Spring容器無法知道該註入那種,此時我們需要為Spring容器提供幫助,指定註入那個Bean實例。可以通過<bean>標簽的autowire-candidate設置為false來過濾那些不需要註入的實例Bean

<bean id="userDao"  class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />

<!-- autowire-candidate="false" 過濾該類型 -->
<bean id="userDao2" autowire-candidate="false" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />

<!-- byType 根據類型自動裝配userDao-->
<bean id="userService" autowire="byType" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />

byName

將屬性名與bean名稱進行匹配,如果找到則註入依賴bean。

<bean id="userDao"  class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />

<!-- byName 根據名稱自動裝配,找到UserServiceImpl名為 userDao屬性並註入-->
<bean id="userService" autowire="byName" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />

constructor

存在單個實例則優先按類型進行參數匹配(無論名稱是否匹配),當存在多個類型相同實例時,按名稱優先匹配,如果沒有找到對應名稱,則註入失敗。

@Autowired和@Resource的區別?

Autowire是spring的註解。預設情況下@Autowired是按類型匹配的(byType)。如果需要按名稱(byName)匹配的話,可以使用@Qualifier註解與@Autowired結合。@Autowired 可以傳遞一個required=false的屬性,false指明當userDao實例存在就註入不存就忽略,如果為true,就必須註入,若userDao實例不存在,就拋出異常。

public class UserServiceImpl implements UserService {
    //標註成員變數
    @Autowired
    @Qualifier("userDao1")
    private UserDao userDao;   
 }

Resource是j2ee的註解,預設按 byName模式自動註入。@Resource有兩個中重要的屬性:name和type。name屬性指定bean的名字,type屬性則指定bean的類型。因此使用name屬性,則按byName模式的自動註入策略,如果使用type屬性,則按 byType模式自動註入策略。倘若既不指定name也不指定type屬性,Spring容器將通過反射技術預設按byName模式註入。

@Resource(name="userDao")
private UserDao  userDao;//用於成員變數

//也可以用於set方法標註
@Resource(name="userDao")
public void setUserDao(UserDao userDao) {
   this.userDao= userDao;
}

上述兩種自動裝配的依賴註入並不適合簡單值類型,如int、boolean、long、String以及Enum等,對於這些類型,Spring容器也提供了@Value註入的方式。

@Value和@Autowired、@Resource類似,也是用來對屬性進行註入的,只不過@Value是用來從Properties文件中來獲取值的,並且@Value可以解析SpEL(Spring表達式)。

比如,jdbc.properties文件如下:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8&allowMultiQueries=true
jdbc.username=root
jdbc.password=root

利用註解@Value獲取jdbc.url和jdbc.username的值,實現如下:

public class UserServiceImpl implements UserService {
    //占位符方式
    @Value("${jdbc.url}")
    private String url;
    //SpEL表達方式,其中代表xml配置文件中的id值configProperties
    @Value("#{configProperties['jdbc.username']}")
    private String userName;

}

@Qualifier 註解有什麼作用

當需要創建多個相同類型的 bean 並希望僅使用屬性裝配其中一個 bean 時,可以使用@Qualifier 註解和 @Autowired 通過指定應該裝配哪個 bean 來消除歧義。

@Bean和@Component有什麼區別?

都是使用註解定義 Bean。@Bean 是使用 Java 代碼裝配 Bean,@Component 是自動裝配 Bean。

@Component 註解用在類上,表明一個類會作為組件類,並告知Spring要為這個類創建bean,每個類對應一個 Bean。

@Bean 註解用在方法上,表示這個方法會返回一個 Bean。@Bean 需要在配置類中使用,即類上需要加上@Configuration註解。

@Component
public class Student {
    private String name = "lkm";
 
    public String getName() {
        return name;
    }
}

@Configuration
public class WebSocketConfig {
    @Bean
    public Student student(){
        return new Student();
    }
}

@Bean 註解更加靈活。當需要將第三方類裝配到 Spring 容器中,因為沒辦法源代碼上添加@Component註解,只能使用@Bean 註解的方式,當然也可以使用 xml 的方式。

@Component、@Controller、@Repositor和@Service 的區別?

@Component:最普通的組件,可以被註入到spring容器進行管理。

@Controller:將類標記為 Spring Web MVC 控制器。

@Service:將類標記為業務層組件。

@Repository:將類標記為數據訪問組件,即DAO組件。

Spring 事務實現方式有哪些?

事務就是一系列的操作原子執行。Spring事務機制主要包括聲明式事務和編程式事務。

  • 編程式事務:通過編程的方式管理事務,這種方式帶來了很大的靈活性,但很難維護。
  • 聲明式事務:將事務管理代碼從業務方法中分離出來,通過aop進行封裝。Spring聲明式事務使得我們無需要去處理獲得連接、關閉連接、事務提交和回滾等這些操作。使用 @Transactional 註解開啟聲明式事務。

@Transactional相關屬性如下:

屬性 類型 描述
value String 可選的限定描述符,指定使用的事務管理器
propagation enum: Propagation 可選的事務傳播行為設置
isolation enum: Isolation 可選的事務隔離級別設置
readOnly boolean 讀寫或只讀事務,預設讀寫
timeout int (in seconds granularity) 事務超時時間設置
rollbackFor Class對象數組,必須繼承自Throwable 導致事務回滾的異常類數組
rollbackForClassName 類名數組,必須繼承自Throwable 導致事務回滾的異常類名字數組
noRollbackFor Class對象數組,必須繼承自Throwable 不會導致事務回滾的異常類數組
noRollbackForClassName 類名數組,必須繼承自Throwable 不會導致事務回滾的異常類名字數組

有哪些事務傳播行為?

在TransactionDefinition介面中定義了七個事務傳播行為:

  1. PROPAGATION_REQUIRED如果存在一個事務,則支持當前事務。如果沒有事務則開啟一個新的事務。如果嵌套調用的兩個方法都加了事務註解,並且運行在相同線程中,則這兩個方法使用相同的事務中。如果運行在不同線程中,則會開啟新的事務。
  2. PROPAGATION_SUPPORTS 如果存在一個事務,支持當前事務。如果沒有事務,則非事務的執行。
  3. PROPAGATION_MANDATORY 如果已經存在一個事務,支持當前事務。如果不存在事務,則拋出異常IllegalTransactionStateException
  4. PROPAGATION_REQUIRES_NEW 總是開啟一個新的事務。需要使用JtaTransactionManager作為事務管理器。
  5. PROPAGATION_NOT_SUPPORTED 總是非事務地執行,並掛起任何存在的事務。需要使用JtaTransactionManager作為事務管理器。
  6. PROPAGATION_NEVER 總是非事務地執行,如果存在一個活動事務,則拋出異常。
  7. PROPAGATION_NESTED 如果一個活動的事務存在,則運行在一個嵌套的事務中。如果沒有活動事務, 則按PROPAGATION_REQUIRED 屬性執行。

PROPAGATION_NESTED 與PROPAGATION_REQUIRES_NEW的區別:

使用PROPAGATION_REQUIRES_NEW時,內層事務與外層事務是兩個獨立的事務。一旦內層事務進行了提交後,外層事務不能對其進行回滾。兩個事務互不影響。

使用PROPAGATION_NESTED時,外層事務的回滾可以引起內層事務的回滾。而內層事務的異常並不會導致外層事務的回滾,它是一個真正的嵌套事務。

Spring事務在什麼情況下會失效?

1.訪問許可權問題

java的訪問許可權主要有四種:private、default、protected、public,它們的許可權從左到右,依次變大。

如果事務方法的訪問許可權不是定義成public,這樣會導致事務失效,因為spring要求被代理方法必須是public的。

翻開源碼,可以看到,在AbstractFallbackTransactionAttributeSource類的computeTransactionAttribute方法中有個判斷,如果目標方法不是public,則返回null,即不支持事務。

protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
    // Don't allow no-public methods as required.
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
      return null;
    }
	...
}

2. 方法用final修飾

如果事務方法用final修飾,將會導致事務失效。因為spring事務底層使用了aop,也就是通過jdk動態代理或者cglib,幫我們生成了代理類,在代理類中實現的事務功能。

但如果某個方法用final修飾了,那麼在它的代理類中,就無法重寫該方法,而添加事務功能。

同理,如果某個方法是static的,同樣無法通過動態代理,變成事務方法。

3.對象沒有被spring管理

使用spring事務的前提是:對象要被spring管理,需要創建bean實例。如果類沒有加@Controller、@Service、@Component、@Repository等註解,即該類沒有交給spring去管理,那麼它的方法也不會生成事務。

4.表不支持事務

如果MySQL使用的存儲引擎是myisam,這樣的話是不支持事務的。因為myisam存儲引擎不支持事務。

5.方法內部調用

如下代碼所示,update方法上面沒有加 @Transactional 註解,調用有 @Transactional 註解的 updateOrder 方法,updateOrder 方法上的事務會失效。

因為發生了自身調用,調用該類自己的方法,而沒有經過 Spring 的代理類,只有在外部調用事務才會生效。

@Service
public class OrderServiceImpl implements OrderService {

    public void update(Order order) {
        this.updateOrder(order);
    }

    @Transactional
    public void updateOrder(Order order) {
        // update order
    }
}

解決方法:

1、再聲明一個service,將內部調用改為外部調用

2、使用編程式事務

3、使用AopContext.currentProxy()獲取代理對象

@Servcie
public class OrderServiceImpl implements OrderService {
    
   public void update(Order order) {
        ((OrderService)AopContext.currentProxy()).updateOrder(order);
   }

    @Transactional
    public void updateOrder(Order order) {
        // update order
    }
 }

6.未開啟事務

如果是spring項目,則需要在配置文件中手動配置事務相關參數。如果忘了配置,事務肯定是不會生效的。

如果是springboot項目,那麼不需要手動配置。因為springboot已經在DataSourceTransactionManagerAutoConfiguration類中幫我們開啟了事務。

7.吞了異常

有時候事務不會回滾,有可能是在代碼中手動catch了異常。因為開發者自己捕獲了異常,又沒有手動拋出,把異常吞掉了,這種情況下spring事務不會回滾。

如果想要spring事務能夠正常回滾,必須拋出它能夠處理的異常。如果沒有拋異常,則spring認為程式是正常的。

Spring怎麼解決迴圈依賴的問題?

首先,有兩種Bean註入的方式。

構造器註入和屬性註入。

對於構造器註入的迴圈依賴,Spring處理不了,會直接拋出BeanCurrentlylnCreationException異常。

對於屬性註入的迴圈依賴(單例模式下),是通過三級緩存處理來迴圈依賴的。

而非單例對象的迴圈依賴,則無法處理。

下麵分析單例模式下屬性註入的迴圈依賴是怎麼處理的:

首先,Spring單例對象的初始化大略分為三步:

  1. createBeanInstance:實例化bean,使用構造方法創建對象,為對象分配記憶體。
  2. populateBean:進行依賴註入。
  3. initializeBean:初始化bean。

Spring為瞭解決單例的迴圈依賴問題,使用了三級緩存:

singletonObjects:完成了初始化的單例對象map,bean name --> bean instance

earlySingletonObjects :完成實例化未初始化的單例對象map,bean name --> bean instance

singletonFactories : 單例對象工廠map,bean name --> ObjectFactory,單例對象實例化完成之後會加入singletonFactories。

在調用createBeanInstance進行實例化之後,會調用addSingletonFactory,將單例對象放到singletonFactories中。

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

假如A依賴了B的實例對象,同時B也依賴A的實例對象。

  1. A首先完成了實例化,並且將自己添加到singletonFactories中
  2. 接著進行依賴註入,發現自己依賴對象B,此時就嘗試去get(B)
  3. 發現B還沒有被實例化,對B進行實例化
  4. 然後B在初始化的時候發現自己依賴了對象A,於是嘗試get(A),嘗試一級緩存singletonObjects和二級緩存earlySingletonObjects沒找到,嘗試三級緩存singletonFactories,由於A初始化時將自己添加到了singletonFactories,所以B可以拿到A對象,然後將A從三級緩存中移到二級緩存中
  5. B拿到A對象後順利完成了初始化,然後將自己放入到一級緩存singletonObjects中
  6. 此時返回A中,A此時能拿到B的對象順利完成自己的初始化

由此看出,屬性註入的迴圈依賴主要是通過將實例化完成的bean添加到singletonFactories來實現的。而使用構造器依賴註入的bean在實例化的時候會進行依賴註入,不會被添加到singletonFactories中。比如A和B都是通過構造器依賴註入,A在調用構造器進行實例化的時候,發現自己依賴B,B沒有被實例化,就會對B進行實例化,此時A未實例化完成,不會被添加到singtonFactories。而B依賴於A,B會去三級緩存尋找A對象,發現不存在,於是又會實例化A,A實例化了兩次,從而導致拋異常。

總結:1、利用緩存識別已經遍歷過的節點; 2、利用Java引用,先提前設置對象地址,後完善對象。

Spring啟動過程

  1. 讀取web.xml文件。

  2. 創建 ServletContext,為 ioc 容器提供宿主環境。

  3. 觸發容器初始化事件,調用 contextLoaderListener.contextInitialized()方法,在這個方法會初始化一個應用上下文WebApplicationContext,即 Spring 的 ioc 容器。ioc 容器初始化完成之後,會被存儲到 ServletContext 中。

  4. 初始化web.xml中配置的Servlet。如DispatcherServlet,用於匹配、處理每個servlet請求。

Spring 的單例 Bean 是否有併發安全問題?

當多個用戶同時請求一個服務時,容器會給每一個請求分配一個線程,這時多個線程會併發執行該請求對應的業務邏輯,如果業務邏輯有對單例狀態的修改(體現為此單例的成員屬性),則必須考慮線程安全問題。

無狀態bean和有狀態bean

  • 有實例變數的bean,可以保存數據,是非線程安全的。
  • 沒有實例變數的bean,不能保存數據,是線程安全的。

在Spring中無狀態的Bean適合用單例模式,這樣可以共用實例提高性能。有狀態的Bean在多線程環境下不安全,一般用Prototype模式或者使用ThreadLocal解決線程安全問題。

Spring Bean如何保證併發安全?

Spring的Bean預設都是單例的,某些情況下,單例是併發不安全的。

Controller 舉例,假如我們在 Controller 中定義了成員變數。當多個請求來臨,進入的都是同一個單例的 Controller 對象,並對此成員變數的值進行修改操作,因此會互相影響,會有併發安全的問題。

應該怎麼解決呢?

為了讓多個HTTP請求之間不互相影響,可以採取以下措施:

1、單例變原型

對 web 項目,可以 Controller 類上加註解 @Scope("prototype")@Scope("request"),對非 web 項目,在 Component 類上添加註解 @Scope("prototype")

這種方式實現起來非常簡單,但是很大程度上增大了 Bean 創建實例化銷毀的伺服器資源開銷。

2、儘量避免使用成員變數

在業務允許的條件下,可以將成員變數替換為方法中的局部變數。這種方式個人認為是最恰當的。

3、使用併發安全的類

如果非要在單例Bean中使用成員變數,可以考慮使用併發安全的容器,如 ConcurrentHashMapConcurrentHashSet 等等,將我們的成員變數包裝到這些併發安全的容器中進行管理即可。

4、分散式或微服務的併發安全

如果還要進一步考慮到微服務或分散式服務的影響,方式3便不合適了。這種情況下可以藉助於可以共用某些信息的分散式緩存中間件,如Redis等。這樣即可保證同一種服務的不同服務實例都擁有同一份共用信息了。

@Async註解的原理

當我們調用第三方介面或者方法的時候,我們不需要等待方法返回才去執行其它邏輯,這時如果響應時間過長,就會極大的影響程式的執行效率。所以這時就需要使用非同步方法來並行執行我們的邏輯。在springboot中可以使用@Async註解實現非同步操作。

使用@Async註解實現非同步操作的步驟:

1.首先在啟動類上添加 @EnableAsync 註解。

@Configuration
@EnableAsync
public class App {
    public static void main(String[] args) {
         ApplicationContext ctx = new  
             AnnotationConfigApplicationContext(App.class);
        MyAsync service = ctx.getBean(MyAsync.class);
        System.out.println(service.getClass());
        service.async1();
        System.out.println("main thread finish...");
    }
}

2.在對應的方法上添加@Async註解。

@Component
public class MyAsync {
    @Async
    public void asyncTest() {
        try {
            TimeUnit.SECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("asyncTest...");
    }
}

運行代碼,控制台輸出:

main thread finish...
asyncTest...

證明asyncTest方法非同步執行了。

原理:

我們在主啟動類上貼了一個@EnableAsync註解,才能使用@Async生效。@EnableAsync的作用是通過@import導入了AsyncConfigurationSelector。在AsyncConfigurationSelector的selectImports方法將ProxyAsyncConfiguration定義為Bean註入容器。在ProxyAsyncConfiguration中通過@Bean的方式註入AsyncAnnotationBeanPostProcessor類。

代碼如下:

@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
}

public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
	public String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return new String[] { ProxyAsyncConfiguration.class.getName() };
			//...
		}
	}
}

public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
    @Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
    public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
        //創建postProcessor
        AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
        //...
    }
}

AsyncAnnotationBeanPostProcessor往往期創建了一個增強器AsyncAnnotationAdvisor。在AsyncAnnotationAdvisor的buildAdvice方法中,創建了AnnotationAsyncExecutionInterceptor。

public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor {
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        super.setBeanFactory(beanFactory);
        //創建一個增強器
        AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);
        //...
        advisor.setBeanFactory(beanFactory);
        this.advisor = advisor;
    }
}


public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
    public AsyncAnnotationAdvisor(
            @Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
        //增強方法
        this.advice = buildAdvice(executor, exceptionHandler);
        this.pointcut = buildPointcut(asyncAnnotationTypes);
    }

    // 委托給AnnotationAsyncExecutionInterceptor攔截器
    protected Advice buildAdvice(
            @Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
        //攔截器
        AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null);
        interceptor.configure(executor, exceptionHandler);
        return interceptor;
    }
}

AnnotationAsyncExecutionInterceptor繼承自AsyncExecutionInterceptor,間接實現了MethodInterceptor。該攔截器的實現的invoke方法把原來方法的調用提交到新的線程池執行,從而實現了方法的非同步。

public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport implements MethodInterceptor, Ordered {
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        //...
        //構建放到AsyncTaskExecutor執行Callable Task
        Callable<Object> task = () -> {
            //...
        };
        //提交到新的線程池執行
        return doSubmit(task, executor, invocation.getMethod().getReturnType());
    }
}

由上面分析可以看到,@Async註解其實是通過代理的方式來實現非同步調用的。

那使用@Async有什麼要註意的呢?

1.使用@Aysnc的時候最好配置一個線程池Executor以讓線程復用節省資源,或者為SimpleAsyncTaskExecutor設置基於線程池實現的ThreadFactory,在否則會預設使用SimpleAsyncTaskExecutor,該executor會在每次調用時新建一個線程。

2.調用本類的非同步方法是不會起作用的。這種方式繞過了代理而直接調用了方法,@Async註解會失效。


最後給大家分享一個Github倉庫,上面有大彬整理的300多本經典的電腦書籍PDF,包括C語言、C++、Java、Python、前端、資料庫、操作系統、電腦網路、數據結構和演算法、機器學習、編程人生等,可以star一下,下次找書直接在上面搜索,倉庫持續更新中~

Github地址


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 使用工具: IDEA2022 Tomcat9.0.4 1.下載Tomcat: 官網:https://tomcat.apache.org/ 找到需要的版本下載即可,下載完成解壓即可用: Tomcat目錄介紹: 1.1.Tomcat啟動、關閉。卸載: 啟動:雙擊bin\startup.bat 關閉:直接 ...
  • 委托模式(Delegation pattern):將一個對象的某個方法委托給另一個對象來執行,它可以幫助我們將對象之間的關係更加靈活地組織起來,從而提高代碼的可維護性和復用性。 在委托模式中,一個對象(稱為委托對象)將一些特定的任務委托給另一個對象(稱為代理對象)來執行。代理對象通常具有和委托對象相 ...
  • 1.聲明與變數 let聲明的變數可以多次賦值 let 變數名 = 值; const修飾叫常量,只能賦值一次,但是引用的值可以改變 var聲明的變數可以多次賦值 結論:能用let不用var ,因為作用域的問題 2.基本類型和對象類型 undefined 和 null undefined 指 未定義的對 ...
  • 代理模式(Proxy Pattern)是一種結構型設計模式,結構型模式描述如何將類或對象按某種佈局組成更大的結構。它允許你提供一個代理對象來控制對另一個對象的訪問。代理對象擁有與實際對象相同的介面,因此它可以被用來代替實際對象。 ...
  • 在軟體行業,對於什麼是架構,都有很多的爭論,每個人都有自己的理解。在不同的書籍上, 不同的作者, 對於架構的定義也不統一, 角度不同, 定義不同。此君說的架構和彼君理解的架構未必是一回事。因此我們在討論架構之前,我們先討論架構的概念定義, 因為概念是人認識這個世界的基礎和用來溝通的手段,如果對架構概... ...
  • 超級大的數做加減乘除 java有八大數據類型: 1、byte(位),最大存儲數據量是255; 2、short(短整數),最大數據存儲量是65536; 3、int(整數),最大數據存儲容量是2的32次方減1; 4、long(長整數),最大數據存儲容量是2的64次方減1; 5、float(單精度浮動數) ...
  • Java已經誕生20多年了,依然是企業級開發中使用最廣泛的語言,也是挨罵最多的語言。Java廣受批評的四個缺點是:性能差、記憶體消耗大、GUI弱、代碼啰嗦,我們應該如何看待這幾個問題呢?在微服務的背景下,提倡圍繞業務能力而非技術來構建應用,允許由不同的語言構建應用程式。一個超大的集群,往往有上萬個微服... ...
  • 什麼是Servlet? Servlet是使用Java語言編寫的運行在伺服器端的程式。狹義的Servlet是指Java語言實現的一個介面,廣義的Servlet是指任何實現了這個Servlet介面的類,一般情況下,人們將Servlet理解為後者。Servlet 主要用於處理客戶端傳來的 HTTP 請求, ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...