Spring學習總結 + 【手寫Spring底層機制核心】

来源:https://www.cnblogs.com/zydevelop/p/18114318/zy_spring
-Advertisement-
Play Games

Spring學習總結 Spring基本介紹 Spring 學習的核心內容 1.IOC: 控制反轉, 可以管理java 對象 2.AOP : 切麵編程 3.JDBCTemplate : 是spring 提供一套訪問資料庫的技術, 應用性強,相對好理解 4.聲明式事務: 基於ioc/aop 實現事務管理 ...


Spring學習總結

Spring基本介紹

Spring 學習的核心內容

1.IOC: 控制反轉, 可以管理java 對象

2.AOP : 切麵編程

3.JDBCTemplate : 是spring 提供一套訪問資料庫的技術, 應用性強,相對好理解

4.聲明式事務: 基於ioc/aop 實現事務管理

spring價值

Spring 最大的價值,通過配置,給程式提供需要使用的
web 層[Servlet(Action/Controller)]/Service/Dao/[JavaBean/entity]對象,
這個是核心價值所在,也是ioc 的具體體現, 實現解耦.

IOC容器

對比傳統模式

  • 傳統的開發模式[JdbcUtils / 反射]

​ 1.程式員編寫程式, 在程式中讀取配置信息

​ 2.創建對象, new Object???() // 反射方式

​ 3.使用對象完成任務

  • IOC 的開發模式

    1、Spring 根據配置文件xml/註解, 創建對象, 並放入到容器(ConcurrentHashMap)中,
    並且可以完成對象之間的依賴
    2、當需要使用某個對象實例的時候, 就直接從容器中獲取即可
    3、程式員可以更加關註如何使用對象完成相應的業務, (以前是new ... ==> 註解/配置
    方式)

Spring底層結構

DI 依賴註入

  • 可以理解成是IOC 的另外叫法.

Spring管理 IOC容器

基於XML配置Bean

通過類型獲取Bean

ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster monster = ioc.getBean(Monster.class);
  • 按類型來獲取bean, 要求ioc 容器中的同一個類的bean 只能有一個, 否則會拋出異常
    NoUniqueBeanDefinitionException
  • 應用場景:比如XxxAction/Servlet/Controller, 或XxxService 在一個線程中只需要一個對象實例(單例)的情況
  • 在容器配置文件(比如beans.xml)中給屬性賦值, 底層是通過
    setter 方法完成的, 這也是為什麼我們需要提供setter 方法的原因

通過構造器獲取Bean

按索引index

<bean id="monster02" class="com.spring.beans.Monster">
<constructor-arg value="2" index="0"/>
<constructor-arg value="蜘蛛精" index="1"/>
<constructor-arg value="吐口水" index="2"/>
</bean>

按類型type

<bean id="monster03" class="com.spring.beans.Monster">
<constructor-arg value="3" type="java.lang.Integer"/>
<constructor-arg value="白骨精" type="java.lang.String"/>
<constructor-arg value="白骨鞭" type="java.lang.String"/>
</bean>
  • 通過index 屬性來區分是第幾個參數
  1. 通過type 屬性來區分是什麼類型(按照順序)

通過p名稱空間配置Bean

<bean id="monster04" class="com.spring.beans.Monster"
p:monsterId="4"
p:name="紅孩兒"
p:skill="吐火~"
/>

bean 對象的相互引用

  1. 其它含義和前面一樣
  2. ref 表示memberDAO 這個屬性將引用/指向id = memberDAOImpl 對象

引用/註入其它bean 對象

<bean id="memberServiceImpl" class="com.spring.service.MemberServiceImpl">
<property name="memberDAO" ref="memberDAOImpl"/>
</bean>
<bean id="memberDAOImpl" class="com.spring.dao.MemberDAOImpl"/>

引用/註入內部bean 對象

<bean id="memberServiceImpl02"
class="com.spring.service.MemberServiceImpl">
<property name="memberDAO">
<bean class="com.spring.dao.MemberDAOImpl"/>
</property>
</bean>

引用/註入集合/數組類型

  • 給集合屬性註入值
<bean id="master01" class="com.code_study.spring.beans.Master">
<property name="name" value="太上老君"/>
<!-- 給bean 對象的list 集合賦值-->
<property name="monsterList">
<list>
<ref bean="monster03"/>
<ref bean="monster02"/>
</list>
</property>
  • 給bean 對象的map 集合賦值

    <property name="monsterMap">
    <map>
    <entry>
    <key>
    <value>monsterKey01</value>
    </key>
    <ref bean="monster01"/>
    </entry>
    <entry>
    <key>
    <value>monsterKey02</value>
    </key>
    <ref bean="monster02"/>
    </entry>
    </map>
    </property>
    
  • 給bean 對象的properties 集合賦值

    <property name="pros">
    <props>
    <prop key="k1">Java 工程師</prop>
    <prop key="k2">前端工程師</prop>
    <prop key="k3">大數據工程師</prop>
    </props>
    </property>
    
  • 給bean 對象的properties 集合賦值

    <property name="pros">
    <props>
    <prop key="k1">Java 工程師</prop>
    <prop key="k2">前端工程師</prop>
    <prop key="k3">大數據工程師</prop>
    </props>
    </property>
    
  • 給bean 對象的數組屬性註入值

    <property name="monsterName">
    <array>
    <value>銀角大王</value>
    <value>金角大王</value>
    </array>
    </property>
    
  • 給bean 對象的set 屬性註入值

    <property name="monsterSet">
    <set>
    <ref bean="monster01"/>
    <bean class="com.spring.beans.Monster">
    <property name="monsterId" value="10"/>
    <property name="name" value="玉兔精"/>
    <property name="skill" value="鑽地洞"/>
    </bean>
    </set>
    </property>
    </bean>
    

bean 配置信息重用(繼承)

<bean id="monster10" class="com.hspedu.spring.beans.Monster">
<property name="monsterId" value="10"/>
<property name="name" value="蜈蚣精"/>
<property name="skill" value="蜇人"/>
</bean>

<!-- parent="monster10" 就是繼承使用了monster10 的配置信息-->
<bean id="monster11" class="com.hspedu.spring.beans.Monster"
parent="monster10"/>

抽象類

<!-- 當我們把某個bean設置為abstract="true" 這個bean只能被繼承,而不能實例化了-->
<bean id="monster12" class="com.hspedu.spring.beans.Monster" abstract="true">
<property name="monsterId" value="12"/>
<property name="name" value="美女蛇"/>
<property name="skill" value="吃人"/>
</bean>

bean 創建順序

  • 在spring 的ioc 容器, 預設是按照配置的順序創建bean 對象

  • 如果這樣配置,會先創建department01 對象,再創建student01 對象.

    <bean id="student01" class="com.hspedu.bean.Student" depends-on="department01"/>
    <bean id="department01" class="com.hspedu.bean.Department" />
    

bean 對象的單例和多例

  • 在spring 的ioc 容器, 在預設是按照單例創建的,即配置一個bean 對象後,ioc 容器只會創建一個bean 實例。

  • 如果,我們希望ioc 容器配置的某個bean 對象,是以多個實例形式創建的則可以通過配置scope="prototype" 來指定

  1. 預設是單例singleton, 在啟動容器時, 預設就會創建, 並放入到singletonObjects 集合

  2. 設置為多實例機制後, 該bean 是在getBean()時才創建

  3. 如果是單例singleton, 同時希望在getBean 時才創建, 可以指定懶載入lazy-init="true" (註意預設是false)

  4. 通常情況下, lazy-init 就使用預設值false , 在開發看來, 用空間換時間是值得的, 除非有特殊的要求.

  5. 如果scope="prototype" 這時你的lazy-init 屬性的值不管是ture, 還是false 都是在getBean 時候,才創建對象.

bean 的生命周期

說明: bean 對象創建是由JVM 完成的,然後執行如下方法

  1. 執行構造器
  2. 執行set 相關方法
  3. 調用bean 的初始化的方法(需要配置)
  4. 使用bean
  5. 當容器關閉時候,調用bean 的銷毀方法(需要配置)

細節:

  1. 初始化init 方法和destory 方法, 是程式員來指定
  2. 銷毀方法就是當關閉容器時,才會被調用.

配置bean 的後置處理器

  1. 在spring 的ioc 容器,可以配置bean 的後置處理器
  2. 該處理器/對象會在bean 初始化方法調用前和初始化方法調用後被調用
  3. 程式員可以在後置處理器中編寫自己的代碼

1、怎麼執行到這個方法?=> 使用AOP(反射+動態代理+IO+容器+註解)

2、有什麼用?=> 可以對IOC 容器中所有的對象進行統一處理,比如日誌處理/許可權的校驗/安全的驗證/事務管理.

3、針對容器的所有對象嗎? 是的=>切麵編程特點

通過屬性文件給bean 註入值

<context:property-placeholder location="classpath:my.properties"/>
<bean id="monster100" class="com.code_study.spring.beans.Monster">
<property name="monsterId" value="${id}"/>
<property name="name" value="${name}"/>
<property name="skill" value="${skill}"/>
</bean>

基於XML 的bean 的自動裝配

<bean id="orderAction" autowire="byName"
class="com.code_study.spring.action.OrderAction"/>
<bean id="orderService" autowire="byName"
class="com.code_study.spring.service.OrderService"/>
<bean id="orderDao" class="com.code_study.spring.dao.OrderDao"/>

特別說明:

  • autowire = "byName" 會自動去找id 為setXxxx 後面Xxxx 的bean 自動組裝.,如果找到就裝配,如果找不到就報錯,

  • 比如這裡的

    就會去找OrderAction 類中定義的setOrderService 的id 為orderService 的OrderServicebean 組裝,找到就組裝,找不到就組裝失敗

基於註解配置Bean

基本介紹

基於註解的方式配置bean, 主要是項目開發中的組件,比如Controller、Service、和Dao.

組件註解的形式有

  1. @Component 表示當前註解標識的是一個組件
  2. @Controller 表示當前註解標識的是一個控制器,通常用於Servlet
  3. @Service 表示當前註解標識的是一個處理業務邏輯的類,通常用於Service 類
  4. @Repository 表示當前註解標識的是一個持久化層的類,通常用於Dao 類
  • !!!Spring 的IOC 容器不能檢測一個使用了@Controller 註解的類到底是不是一個真正的控
    制器。註解的名稱是用於程式員自己識別當前標識的是什麼組件。其它的@Service
    @Repository 也是一樣的道理[也就是說spring 的IOC 容器只要檢查到註解就會生成對象,
    但是這個註解的含義spring 不會識別,註解是給程式員編程方便看的]!!!

自動掃描包

<!-- 配置自動掃描的包,註意需要加入context 名稱空間-->
<context:component-scan base-package="com.code_study.spring.component" />

掃描時排除某些類 -expression

<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Service"/>

指定自動掃描哪些註解類

<!--
1. use-default-filters="false": 不再使用預設的過濾機制
2. context:include-filter: 表示只是掃描指定的註解的類
3.expression="org.springframework.stereotype.Controller": 註解的全類名
-->
<context:component-scan base-package="com.code_study.spring.component"
use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Service"/>
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

修改beanName

標記註解後,類名首字母小寫作為id 的值。也可以使用註解的value 屬性
指定id 值,並且value 可以省略

AOP

動態代理

// 1.獲取類載入對象
ClassLoader loader = target_obj.getClass().getClassLoader();
// 2.獲取介面類型數組
Class<?>[] interfaces = target_obj.getClass().getInterfaces();
// 3.獲取InvocationHandler 以匿名內部類的方式方式來獲取InvocationHandler
InvocationHandler h = new InvocationHandler() {
// 4.以動態代理的方式調用目標對象的目標方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
String methodName = method.getName();
try {
// 1. 在調用目標方法之前列印“方法開始”日誌
System.out.println("日誌--方法名:" + methodName + "--方法開始--參數:"
+ Arrays.asList(args));
// 2. 調用目標方法並接收返回值
result = method.invoke(target_obj, args);
// 3. 在目標方法結束後列印“方法結束”日誌
System.out.println("日誌--方法名:" + methodName
+ "--方法正常結束--結果:result=" + result);
} catch (Exception e) {
// 4.如果目標方法拋出異常,列印“方法異常”日誌
e.printStackTrace();
System.out.println("日誌--方法名:" + methodName
+ "--方法拋出異常--異常類型:" + e.getClass().getName());
} finally {
// 5.在finally 中列印“方法最終結束”日誌
System.out.println("日誌--方法名:" + methodName + "--方法最終結
束");
}
// 6. 返回目標方法的返回值
return result;
}
};
//生成SmartAnimaleable 的代理對象
//需要三個參數,
//1.就是loader(獲取類載入對象)
//2.介面數組
//3.InvocationHandler 對象[這個相對麻煩..]
SmartAnimalable proxy = (SmartAnimalable) Proxy.newProxyInstance(
loader, interfaces, h);
return proxy;
}
}

AOP 的基本介紹

AOP 的全稱(aspect oriented programming) ,面向切麵編程


在切麵類中聲明通知方法

  1. 前置通知:@Before
  2. 返回通知:@AfterReturning
  3. 異常通知:@AfterThrowing
  4. 後置通知:@After
  5. 環繞通知:@Around

細節

  • 關於切麵類方法命名可以自己規範一下, 比如showBeginLog() . showSuccessEndLog()
    showExceptionLog() , showFinallyEndLog()

  • 切入表達式的更多配置,比如使用模糊配置
    @Before(value="execution(* com.code_study.aop.proxy.SmartDog.*(..))")

  • 表示所有訪問許可權,所有包的下所有有類的所方法,都會被執行該前置通知方法
    @Before(value="execution(* .(..))")

  • 當spring 容器開啟了

  • <!-- 開啟基於註解的AOP 功能--> <aop:aspectj-autoproxy/> 
    

    , 我們獲取註入的對象, 需要以介面的類型來獲取, 因為你註入的對象.getClass() 已經是代理類型了!

  • 當spring 容器開啟了

    <!-- 開啟基於註解的AOP 功能--> <aop:aspectj-autoproxy/> 
    

    , 我們獲取註入的對象, 也可以通過id 來獲取, 但是也要轉成介面類型.

開啟基於註解的AOP 功能

<aop:aspectj-autoproxy/>

AOP-切入表達式



  1. 切入表達式也可以指向類的方法, 這時切入表達式會對該類/對象生效
  2. 切入表達式也可以指向介面的方法, 這時切入表達式會對實現了介面的類/對象生效
  3. 切入表達式也可以對沒有實現介面的類,進行切入

通過JoinPoint 可以獲取到調用方法的簽名

public void beforeMethod(JoinPoint joinPoint){
joinPoint.getSignature().getName(); // 獲取目標方法名
joinPoint.getSignature().getDeclaringType().getSimpleName(); // 獲取目標方法所屬
類的簡單類名
joinPoint.getSignature().getDeclaringTypeName(); // 獲取目標方法所屬類的類名
joinPoint.getSignature().getModifiers(); // 獲取目標方法聲明類型(public、private、
protected)
Object[] args = joinPoint.getArgs(); // 獲取傳入目標方法的參數,返回一個數組
joinPoint.getTarget(); // 獲取被代理的對象
joinPoint.getThis(); // 獲取代理對象自己
}

AOP-返回通知獲取結果

添加屬性 returning = "res"

@AfterReturning(value = "execution(public float
com.code_study.spring.aop.joinpoint.SmartDog.getSum(float, float))",
returning = "res")
public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
System.out.println("返回通知" + "--結果是--" + res );
}

AOP-異常通知中獲取異常

添加屬性 throwing = "throwable"

@AfterThrowing(value = "execution(public float
com.code_study.spring.aop.joinpoint.SmartDog.getSum(float, float))",
throwing = "throwable")
public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {
System.out.println("異常通知-- 異常信息--" + throwable);
}

AOP-切入點表達式重用

@Pointcut(value = "execution(public float
com.code_study.spring.aop.joinpoint.SmartDog.getSum(float, float))")
public void myPointCut() {
}
@Before(value = "myPointCut()")
public void showBeginLog(JoinPoint joinPoint) { //前置方法

AOP-切麵優先順序問題

如果同一個方法,有多個切麵在同一個切入點切入,那麼執行的優先順序如何控制.?

使用:註解@order(value=n) 來控制n 值越小,優先順序越高.

AOP輸出的信息順序

  1. 不能理解成:優先順序高的每個消息通知都先執行,這個和方法調用機制(和Filter 過濾器鏈式調用類似)

AOP-基於XML 配置AOP

在spring 中,我們也可以通過xml 的方式來配置AOP

<!-- 配置SmartAnimalAspect bean -->
<bean id="smartAnimalAspect"
class="com.code_study.spring.aop.xml.SmartAnimalAspect"/>
<!--配置SmartDog-->
<bean class="com.code_study.spring.aop.xml.SmartDog" id="smartDog"/>
<aop:config>
<!-- 配置統一切入點-->
<aop:pointcut expression="execution(public float
com.code_study.spring.aop.xml.SmartDog.getSum(float, float))"
id="myPointCut"/>
<aop:aspect ref="smartAnimalAspect" order="1">
<!-- 配置各個通知對應的切入點-->
<aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
<aop:after-returning method="showSuccessEndLog"
pointcut-ref="myPointCut" returning="res"/>
<aop:after-throwing method="showExceptionLog"
pointcut-ref="myPointCut" throwing="throwable"/>
<aop:after method="showFinallyEndLog" pointcut-ref="myPointCut"/>
<!-- 還可以配置環繞通知-->
<!-- <aop:around method=""/> -->
</aop:aspect>
</aop:config>
</beans>

JdbcTemplate

JdbcTemplate-基本介紹

  1. 通過Spring 可以配置數據源,從而完成對數據表的操作

  2. JdbcTemplate 是Spring 提供的訪問資料庫的技術。可以將JDBC 的常用操作封裝為模板方
    法。[JdbcTemplate 類圖].

JdbcTemplate創建配置文件

引入外部屬性文件

<!-- 引入外部屬性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>

配置數據源

<!-- 配置數據源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.userName}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
</bean>
</beans>

配置JdbcTemplate

<!-- 配置JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 將上面的數據源分配給jdbcTemplate -->
<property name="dataSource" ref="dataSource"/>
</bean>

JdbcTemplate對資料庫的CRUD操作

添加 execute&update

ApplicationContext ioc = new
ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
//得到JdbcTemplate bean
JdbcTemplate bean = ioc.getBean(JdbcTemplate.class);
// 1. 添加方式1
// String sql = "INSERT INTO monster VALUES(400, '紅孩兒', '槍法厲害')";
// bean.execute(sql);
//2. 添加方式2, 綁定參數
String sql = "INSERT INTO monster VALUES(?, ?, ?)";
int affected = bean.update(sql, 700, "紅孩兒2", "槍法厲害2");
System.out.println("add ok affected= " + affected);

修改 update

ApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
//得到JdbcTemplate bean
JdbcTemplate bean = ioc.getBean(JdbcTemplate.class);
String sql = "UPDATE monster SET skill = ? WHERE id=?";
int affected = bean.update(sql, "美女計", 300);
System.out.println("affected= " + affected);
System.out.println("update data ok~");

批量添加 batchUpdate

ApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
//得到JdbcTemplate bean
JdbcTemplate bean = ioc.getBean(JdbcTemplate.class);//添加..
String sql = "INSERT INTO monster VALUES(?, ?, ?)";
List<Object[]> param_list = new ArrayList<Object[]>();
param_list.add(new Object[]{500, "白蛇精", "吃人"});
param_list.add(new Object[]{600, "青蛇精", "吃小孩"});
bean.batchUpdate(sql, param_list);
System.out.println("batch add ok");

查詢並封裝到Monster 實體對象

ApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
//得到JdbcTemplate bean
JdbcTemplate bean = ioc.getBean(JdbcTemplate.class);
String sql = "SELECT id as monsterId,name,skill FROM monster WHERE id =?";
//下麵這個rowmapper 是一個介面,可以將查詢的結果,封裝到你指定的Monster 對象中.
RowMapper<Monster> rowMapper =
new BeanPropertyRowMapper<Monster>(Monster.class);
Monster monster = bean.queryForObject(sql, rowMapper, 100);

查詢並批量封裝到Monster 實體對象

ApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
//得到JdbcTemplate bean
JdbcTemplate bean = ioc.getBean(JdbcTemplate.class);
String sql = "SELECT id as monsterId,name,skill FROM monster WHERE id >=?";
//下麵這個rowmapper 是一個介面,可以將查詢的結果,封裝到你指定的Monster 對象中.
RowMapper<Monster> rowMapper = new
BeanPropertyRowMapper<Monster>(Monster.class);
List<Monster> monster_list =
bean.query(sql, rowMapper, 200);
for (Monster monster : monster_list) {
System.out.println(monster);
}

查詢單行單列

ApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
//得到JdbcTemplate bean
JdbcTemplate bean = ioc.getBean(JdbcTemplate.class);
String sql = "SELECT name FROM monster WHERE id =100";
String name = bean.queryForObject(sql, String.class);
System.out.println(name);

使用Map 傳入具名參數

1.配置NamedParameterJdbcTemplate,支持具名參數

<bean id="namedParameterJdbcTemplate"
class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<!-- 這裡需要使用構造器關聯數據源-->
<constructor-arg name="dataSource" ref="dataSource"/>
</bean>

2.使用Map 傳入具名參數完成操作

ApplicationContext ioc =
new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
//得到NamedParameterJdbcTemplate bean
NamedParameterJdbcTemplate namedParameterJdbcTemplate =
ioc.getBean(NamedParameterJdbcTemplate.class);
String sql = "INSERT INTO monster VALUES(:my_id, :name, :skill)";
Map<String, Object> map_parameter = new HashMap<String, Object>();
map_parameter.put("my_id", 800);
map_parameter.put("name", "螃蟹精");
map_parameter.put("skill", "鉗子無敵大法");
namedParameterJdbcTemplate.update(sql, map_parameter);

使用sqlparametersoruce 來封裝具名參數

ApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
//得到NamedParameterJdbcTemplate bean
NamedParameterJdbcTemplate namedParameterJdbcTemplate =
ioc.getBean(NamedParameterJdbcTemplate.class);
String sql = "INSERT INTO monster VALUES(:monsterId, :name, :skill)";
Monster monster = new Monster(900, "狐狸精", "狐媚之術");
SqlParameterSource source = new BeanPropertySqlParameterSource(monster);
namedParameterJdbcTemplate.update(sql, source);
System.out.println("add ok~");

聲明式事務

  • 使用Spring 的聲明式事務處理, 可以將一個事務的多個子步驟(sql語句)分別寫成一個方法,然後統一管理.
  • 這個是Spring 很牛的地方,在開發使用的很多,優點是無代碼冗餘,效率高,擴展方便
  • 底層使用AOP (動態代理+動態綁定+反射+註解)

配置聲明式事務

引入外部屬性文件

<context:property-placeholder location="classpath:jdbc.properties"/>

配置數據源

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.userName}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
</bean>

配置JdbcTemplate

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 將上面的數據源分配給jdbcTemplate -->
<property name="dataSource" ref="dataSource"/>
</bean>

配置事務管理器

<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

開啟基於註解的聲明式事務功能

<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

事務的傳播機制

事務傳播機制種類

事務傳播的屬性/種類機制分析,重點 REQUIRED 和REQUIRED_NEW 兩種事務

事務的傳播機制的設置方法

REQUIRES_NEW 和REQUIRED 在處理事務的策略

  1. 如果設置為REQUIRES_NEW
    方法2 如果錯誤,不會影響到Tx1反之亦然,即它們的事務是獨立的.
  2. 如果設置為REQUIRED 方法2和Tx1是一個整體,只要有方法的事務錯誤,那麼兩個方法都不會執行成功.!

事務的隔離級別

  1. 預設的隔離級別, 就是mysql 資料庫預設的隔離級別一般為REPEATABLE_READ

  2. 看源碼可知Isolation.DEFAULT 是:Use the default isolation level of the underlying
    datastore

  3. 查看資料庫預設的隔離級別SELECT @@global.tx_isolation

事務的超時回滾

  1. 如果一個事務執行的時間超過某個時間限制,就讓該事務回滾。

  2. 可以通過設置事務超時回顧來實現

  3. 使用註解 @Transactional(timeout = 2)

​ 超時時間,設置為2 秒)

手寫Spring底層機制

IOC容器

    //定義 BeanDefinitionMap 存放 beanDefinition
    private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap =
            new ConcurrentHashMap<>();

    //定義 singletonObjects 存放 單例
    private ConcurrentHashMap<String,Object> singletonObjects =
            new ConcurrentHashMap<>();


    //定義beanPostProcessorList 存放 BeanPostProcessor
    private ArrayList<BeanPostProcessor> beanPostProcessorList=
            new ArrayList<>();


構造器

//構造器
public ZyApplicationContext(Class configClass) {
    this.configClass = configClass;

    beanDefinitionsByScan();

    //初始化單例池
    initSingletonObjects();
}

掃描包

private void beanDefinitionsByScan() {
        //獲得掃描的包
        ComponentScan componentScan =
                (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);

        //獲取路徑
        String path = componentScan.value();

        path = path.replace(".","/");
        //獲取工作路徑
        ClassLoader classLoader = ZyApplicationContext.class.getClassLoader();

        URL resource = classLoader.getResource(path);

        File file = new File(resource.getFile());
        if (file.isDirectory()){
            File[] files = file.listFiles();
            for (File f : files) {
                //獲取絕對路徑
                String fileAbsolutePath = f.getAbsolutePath();
                if (fileAbsolutePath.endsWith(".class")) {
                    //獲取className
                    String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));

                    String fullPath = path.replace("/", ".") + "." + className;

                    try {
                        Class<?> clazz = classLoader.loadClass(fullPath);
                        if (clazz.isAnnotationPresent(Component.class)) {

                            //初始化beanPostProcessorList
                            if (BeanPostProcessor.class.isAssignableFrom(clazz)){
                              BeanPostProcessor beanPostProcessor =
                                      (BeanPostProcessor)clazz.newInstance();
                                beanPostProcessorList.add(beanPostProcessor);
                                continue;
                            }


                            //處理className
                            String value = clazz.getDeclaredAnnotation(Component.class).value();
                            if ("".equals(value)){
                                className = StringUtils.uncapitalize(className);
                            }else {
                                className = value;
                            }
                            System.out.println("是一個bean 類名= " + className);
                            //設置 beanDefinition
                            BeanDefinition beanDefinition = new BeanDefinition();
                            //設置scope
                            if (clazz.isAnnotationPresent(Scope.class)){
                                beanDefinition.setScope(clazz.getDeclaredAnnotation(Scope.class).value());
                            }else{
                                beanDefinition.setScope("singleton");
                            }
                            beanDefinition.setClazz(clazz);

                            //放入 beanDefinitionMap
                            beanDefinitionMap.put(className,beanDefinition);




                        } else {
                            System.out.println("不是一個bean 類名= " + className);
                        }
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

初始化單例池

private void initSingletonObjects() {
        Enumeration<String> keys = beanDefinitionMap.keys();
        while (keys.hasMoreElements()){
            String beanName = keys.nextElement();
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            String scope = beanDefinition.getScope();
            if ("singleton".equals(scope)){
                Object bean = createBean(beanDefinition,beanName);
                singletonObjects.put(beanName,bean);
            }

        }
    }

getBean()

 public Object getBean(String name){
        if (beanDefinitionMap.containsKey(name)){
            BeanDefinition beanDefinition = beanDefinitionMap.get(name);
            String scope = beanDefinition.getScope();
            if ("singleton".equals(scope)){
                return singletonObjects.get(name);
            }else{
                return createBean(beanDefinition,name);
            }
        }
        return null;
    }

createBean()

 private Object createBean(BeanDefinition beanDefinition,String beanName){
        try {
            Class clazz = beanDefinition.getClazz();
            Object instance = clazz.getDeclaredConstructor().newInstance();
            
            return instance;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

依賴註入

  • 加入到createBean()中

  private Object createBean(BeanDefinition beanDefinition,String beanName){
        try {
            Class clazz = beanDefinition.getClazz();
            Object instance = clazz.getDeclaredConstructor().newInstance();

            //依賴註入
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if (declaredField.isAnnotationPresent(Autowired.class)){
                    if (declaredField.getDeclaredAnnotation(Autowired.class).required()){
                        //獲的欄位名
                        String fieldName = declaredField.getName();
                        //獲取實例
                        Object bean = getBean(fieldName);
                        declaredField.setAccessible(true);
                        declaredField.set(instance,bean);
                    }
                }
            }

          
            return instance;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

後置處理器

  • 加入到createBean()中

  private Object createBean(BeanDefinition beanDefinition,String beanName){
        try {
            Class clazz = beanDefinition.getClazz();
            Object instance = clazz.getDeclaredConstructor().newInstance();

            //依賴註入
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if (declaredField.isAnnotationPresent(Autowired.class)){
                    if (declaredField.getDeclaredAnnotation(Autowired.class).required()){
                        //獲的欄位名
                        String fieldName = declaredField.getName();
                        //獲取實例
                        Object bean = getBean(fieldName);
                        declaredField.setAccessible(true);
                        declaredField.set(instance,bean);
                    }
                }
            }

            //後置處理器 before()
            //遍歷 beanPostProcessorList
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                Object current = beanPostProcessor.
                        postProcessBeforeInitialization(instance, beanName);
                if (current!=null){
                    instance = current;
                }

            }


            //初始化bean
            if (instance instanceof InitializingBean){
                try {
                    ((InitializingBean)instance).afterPropertiesSet();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }



            //後置處理器 after()
            //遍歷 beanPostProcessorList
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                Object current = beanPostProcessor.
                        postProcessAfterInitialization(instance, beanName);
                if (current!=null){
                    instance = current;
                }

            }


            System.out.println("");
            System.out.println("-------------------------------------");
            System.out.println("");
            return instance;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

AOP

  • AOP需要在後置處理器的before方法中實現

 @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("ZyBeanPostProcessor後置處理器-After-beanName= "+beanName);


        //aop實現

        if("smartDog".equals(beanName)) {
            Object proxyInstance = Proxy.newProxyInstance(ZyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {


                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Object result = null;

                    if ("getSum".equals(method.getName())) {
                        SmartAnimalAspect.showBeginLog();
                        result = method.invoke(bean, args);
                        SmartAnimalAspect.showBeginLog();
                    }else {
                        result = method.invoke(bean, args);//執行目標方法
                    }

                    return result;
                }
            });
            return proxyInstance;
        }

        return bean;
    }

幾個Spring的問題

1.單例/多例怎麼實現的?@scope為什麼可以實現?

回答:@scope 的value屬性可以設置為singleton /prototype

通過getBean()方法 如果bean中的屬性scope為singleton 就從單例池直接拿,如果是prototype 就調用createBean()創建一個實例

2.如何實現依賴註入?@Autowired Spring容器如何實現依賴註入?

回答: 遍歷clazz的所有屬性 通過@Autowired註解 如果有 先獲取欄位名 再通過getBean()獲取對應的bean 最後用filed.set()方法將實例的該屬性設置為 獲取到的bean 實現依賴註入

3.後置處理器 為什麼在創建bean 時 調用bean 的 init方法初始化前/後 調用?

//後置處理器 before()
            //遍歷 beanPostProcessorList
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                Object current = beanPostProcessor.
                        postProcessBeforeInitialization(instance, beanName);
                if (current!=null){
                    instance = current;
                }

            }


            //初始化bean
            if (instance instanceof InitializingBean){
                try {
                    ((InitializingBean)instance).afterPropertiesSet();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }



            //後置處理器 after()
            //遍歷 beanPostProcessorList
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                Object current = beanPostProcessor.
                        postProcessAfterInitialization(instance, beanName);
                if (current!=null){
                    instance = current;
                }

            }

4.後置處理器和AOP有什麼關係,Spring Aop如何實現??

回答:aop的實現實在後置處理器的before中實現的,底層使用動態代理

 //aop實現

        if("smartDog".equals(beanName)) {
            Object proxyInstance = Proxy.newProxyInstance(ZyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {


                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Object result = null;

                    if ("getSum".equals(method.getName())) {
                        SmartAnimalAspect.showBeginLog();
                        result = method.invoke(bean, args);
                        SmartAnimalAspect.showBeginLog();
                    }else {
                        result = method.invoke(bean, args);//執行目標方法
                    }

                    return result;
                }
            });
            return proxyInstance;
        }

        return bean;

本文學習內容來自韓順平老師的課程

僅供個人參考學習


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

-Advertisement-
Play Games
更多相關文章
  • React 學習之 createElement React 元素 在 React 中,元素是 React 應用的最小構建塊。 一個 React 元素是 React 對象的一個輕量級、靜態的表示。 它們被 React 用於知道屏幕上什麼應該被渲染,併在數據改變時保持 UI 的更新。 React 元素是 ...
  • 一、三次握手 三次握手(Three-way Handshake)其實就是指建立一個TCP連接時,需要客戶端和伺服器總共發送3個包 主要作用就是為了確認雙方的接收能力和發送能力是否正常、指定自己的初始化序列號為後面的可靠性傳送做準備 過程如下: 第一次握手:客戶端給服務端發一個 SYN 報文,並指明客 ...
  • 首先寫一個bind的簡單示例: 'use strict' function fn() { console.log('this::', this) console.log('arguments::', arguments) } // fn() // 這裡調用時this 在嚴格模式下是undefined ...
  • React 學習之 Hello World React 簡介 React是一個用於構建用戶界面的JavaScript庫,由Facebook開發並維護。React通過聲明式的方式來構建UI,使得代碼更易於理解和測試。React的核心概念包括組件(Component)和虛擬DOM(Virtual DOM ...
  • nvm nvm(Node Version Manager)是一個Node.js的版本管理器。 安裝nvm windows安裝nvm 1. 下載nvm 下載地址:nvm-windows,下載 nvm-noinstall 或者 nvm-setup.exe 如果使用 nvm-noinstall 可以運行 ...
  • 多租戶的概念是我在畢業後不久進第一家公司接觸到的,當時所在部門的業務是計劃建設一套基於自研的、基於開放 API 的、基於 PaaS 的、面向企業(ToB)的多租戶架構平臺,將我們的服務可以成規模地、穩定高效地交付給客戶使用。 ...
  • 什麼是客戶管理系統? 客戶管理系統,也稱為CRM(Customer Relationship Management),主要目標是建立、發展和維護好客戶關係。 CRM系統圍繞客戶全生命周期的管理,吸引和留存客戶,實現縮短銷售周期、降低銷售成本、增加銷售收入的目的,從而提高企業的盈利能力和競爭力。 CR ...
  • 布隆過濾器 極簡概括 英文名稱Bloom Filter,用於判斷一個元素是否在一個大數據集合中,如果檢測到存在則有可能存在,如果不存在則一定不存在。 Redis官網對於布隆過濾器的說明:https://redis.io/docs/data-types/probabilistic/bloom-filt ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...