《三角戰略》一週目普通難度英雄強度淺析
1、Spring基礎
1.1簡介
- 由一個叫Rod Johnson的程式設計師在 2002 年最早提出(interface 21框架)並隨後建立,是為了解決企業級程式設計開發中的複雜性,實現敏捷開發的應用型框架 。
- Spring理念:使現有技術更加容易使用,本身是一個大雜燴,整合現有框架。
- 官網:https://spring.io/projects/spring-framework#overview
- 下載地址:
1.2 核心部分
- IOC:控制反轉,把建立物件過程交給 Spring 進行管理 。
- Aop:面向切面,不修改原始碼進行功能增強。
1.3優點
- Spring是一個開源的免費框架(容器)。
- Spring是一個輕量級的,非入侵的框架。
- 控制反轉(IOC),面向切面程式設計(AOP)。
- 支援事物的處理,對框架整合的支援
總結:Spring是一個輕量級的控制反轉(IOC)和麵向切面程式設計(AOP)的框架!
1.4拓展
-
Spring Boot
- 一個快速開發的腳手架
- 基於SpringBoot可以快速地開發單個微服務
- 約定大於配置
-
Spring Cloud
- SPringCloud是基於SpringBoot實現地
很多公司都在使用SpringBoot進行快速開發,學習SpringBoot的前提是完全掌握Spring以及SpringMVC!
2、IOC容器
2.1 什麼是IOC
-
控制反轉,把物件建立和物件之間的呼叫過程交給Spring進行管理。
-
使用IOC的目的:為了降低耦合。
-
IOC的底層原理:xml解析、工廠模式、反射。
-
IOC 思想基於 IOC 容器完成,IOC 容器底層就是物件工廠。
-
Spring 提供 IOC 容器實現兩種方式:(兩個介面)
-
BeanFactory
:IOC 容器基本實現,是 Spring 內部使用 介面,不提供開發人員進行使用 。載入配置檔案時候不會建立物件,在獲取物件(使用)才去建立物件。 -
ApplicationContext
:BeanFactory 介面的子介面,提供更多更強大的功能,一般由開發人 員進行使用。載入配置檔案時候就會把在配置檔案物件進行建立。- ApplicationContext 介面有實現類有
FileSystemXmlApplicationContext
和ClassPathXmlApplicationContext
。
- ApplicationContext 介面有實現類有
-
2.2 IOC 操作(Bean管理)
- 什麼是 Bean 管理
- Spring 建立物件
- Spirng 注入屬性
- Bean 管理操作有兩種方式
- 基於 xml 配置檔案方式實現
- 基於註解方式實現
2.3 Bean 管理——基於 xml 方式
xml配置
alias
:用於設定別名
<!--別名的設定,不常用,可以用name代替-->
<alias name="user" alias="uu"/>
import
:用於將多個配置檔案匯入合併為一個,多用於團隊開發,applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="beans.xml"/>
<import resource="beans2.xml"/>
</beans>
建立物件 使用
bean
標籤
<bean id="helloSpring" class="com.th.pojo.Hello" name="u1 u2,u3;u4">
<property name="str" value="HelloSpring"/>
</bean>
在 spring 配置檔案中,使用 bean
標籤,並對應屬性,實現物件建立。
在 bean 標籤有很多屬性,介紹常用的屬性 :
-
id
: bean的唯一標識 。 -
class
:類全路徑(包類路徑) 。 -
name
: 早期版本使用,可以用於別名設定,可以取多個別名,用逗號或者分號分開即可。 -
scope
:prototype、request、session、singleton
建立物件時候,預設使用無參構造方法完成物件建立。所以存在有參構造時最好把無參構造顯示的寫出來。
注入屬性
DI:依賴注入,就是注入屬性。
方式:
- 使用setter方法。
- 使用使用有引數構造進行注入。
Spring可以根據這兩種方式分別使用property
和constructor-arg
標籤進行配置,從而注入屬性。
-
property
注入<bean id="helloSpring" class="com.th.pojo.Hello"> <property name="str" value="HelloSpring"/> </bean>
name
: 類裡面屬性名稱value
:向屬性注入的值ref
:標識引用Spring容器中已經建立好的的物件各種資料型別的注入
<bean id="student" class="com.th.pojo.Student"> <!--第一種:普通值注入,用value--> <property name="name" value="ThreePure"/> <!--第二種:Bean注入,用ref--> <property name="address" ref="address"/> <!--第三種:陣列--> <property name="books"> <array> <value>紅樓夢</value> <value>水滸傳</value> <value>西遊記</value> <value>三國演義</value> </array> </property> <!--第四種:List--> <property name="hobbys"> <list> <value>籃球</value> <value>書法</value> <value>跑步</value> </list> </property> <!--第五種:Map--> <property name="card"> <map> <entry key="身份證" value="13512626346"/> <entry key="銀行卡" value="13512624327236346"/> </map> </property> <!--第六種:Set--> <property name="games"> <set> <value>LOL</value> <value>COC</value> <value>BOB</value> </set> </property> <!--第七種:空指標--> <property name="wife"> <null/> </property> <!--第八種:Properties--> <property name="info"> <props> <prop key="學號">13891</prop> <prop key="性別">男</prop> <prop key="年齡">21</prop> </props> </property> </bean>
引入外部屬性檔案 以druid連線資料庫為例
-
建立外部檔案,properties 格式檔案,寫資料庫資訊
prop.driverClass=com.mysql.jdbc.Driver prop.url=jdbc:mysql://localhost:3306/mybatis prop.userName=root prop.password=mysqlpw
-
把外部 properties 屬性檔案引入到 spring 配置檔案中
<!--引入外部屬性檔案--> <context:property-placeholder location="druid.properties"/>
注意:使用這個標籤時必須在配置檔案中匯入
context
約束。 -
獲取外部檔案的值 (使用
${}
獲取properties的值)<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driverClass}"/> <property name="url" value="${prop.url}"/> <property name="username" value="${prop.userName}"/> <property name="password" value="${prop.password}"/> </bean>
-
獲取使用
DruidDataSource
物件public void testOne(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); DruidDataSource dataSource = context.getBean("dataSource", DruidDataSource.class); //jdbc:mysql://localhost:3306/mybatis System.out.println(dataSource.getUrl()); }
p 名稱空間注入(瞭解):如果對於一個屬性很多的實體類,那麼
property
使用過於繁瑣,p名稱空間是一種簡化的方法。第一步:新增p名稱空間到配置檔案
xmlns:p="http://www.springframework.org/schema/p"
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
第二步:進行屬性注入,在
bean
標籤裡面進行操作<bean id="user1" class="com.th.pojo.User" p:age="20" p:name="TH"/>
注意:p名稱空間依賴於getter和setter方法注入,所以需要設定getter和setter方法。
-
-
constructor-arg
注入<!--方式一:使用下標的方式--> <bean id="user" class="com.th.pojo.User"> <constructor-arg index="0" value="狂神"/> </bean> <!--方式二:通過引數型別--> <bean id="user" class="com.th.pojo.User"> <constructor-arg type="java.lang.String" value="kunagshen"/> </bean> <!--方式三:通過屬性名--> <!--name:可以設定別名,可以同時設定多個別名,空格、逗號,分號分隔--> <bean id="user" class="com.th.pojo.User" name="u1 u2,u3;u4"> <constructor-arg name="name" value="KS"/> </bean>
name
: 類裡面屬性名稱。value
:向屬性注入的值。type
: 類屬性的型別。index
:類屬性的下標(0表示第一個屬性)。ref
:標識引用Spring容器中已經建立好的的物件。- 通過引數型別注入屬性只能對於一些簡單的不含相同屬性實體類,侷限性較大。
- 最推薦通過屬性名進行屬性注入。
c 名稱空間注入(瞭解):
第一步:新增c名稱空間到配置檔案
xmlns:c="http://www.springframework.org/schema/c""
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
第二步:進行屬性注入,在
bean
標籤裡面進行操作<bean id="user2" class="com.th.pojo.User" c:age="18" c:name="三淳" scope="prototype"/>
注意:c名稱空間依賴於構造器注入,所以需要設定有參構造方法。
-
Bean的作用域
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes
singleton
:單例模式,Spring預設使用單例模式。載入 spring 配置檔案時候就會建立單例項物件<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
prototype
:原型模式,每次從容器中get的時候,都會產生新的物件。<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
request
、session
、application
、websocket
在Web開發種使用。 在呼叫getBean
方法時候建立多例項物件。 -
Bean的自動裝配
Spring種有3種裝配方式:
- 在xml中顯示配置
- 在java中顯示配置
- 隱式的自動裝配bean : Spring在上下文種自動尋找,並自動給bean裝配屬性。
byName自動裝配。 會自動在容器上下文中查詢和自己物件set方法後面的值對應的bean的id!
<!--這個id必須小寫,否則報異常--> <bean id="cat" class="com.th.pojo.Cat"/> <bean id="dog" class="com.th.pojo.Dog"/> <bean id="man" class="com.th.pojo.People" autowire="byName"> <property name="name" value="ThreePure1"/> <!--其他物件屬性在配置後可以自動裝配--> </bean>
注意: 使用byName需要保證所有的bean的id唯一,並且這個bean需要跟自動注入的類屬性名稱一樣!
byType自動裝配。 會自動在容器上下文中查詢,和自己物件屬性型別相同的bean。
<!--因為是按照型別裝配,所以id可以省略--> <bean class="com.th.pojo.Cat"/> <bean id="dog" class="com.th.pojo.Dog"/> <bean id="man" class="com.th.pojo.People" autowire="byType"> <property name="name" value="ThreePure2"/> </bean>
注意: 使用byType需要保證所有的bean的class唯一,並且這個bean需要跟自動注入的屬性的型別一致!
使用註解實現自動裝配 (見下一節)
2.4 Bean 管理——基於註解方式
-
使用註解須知
在Spring4之後,使用註解開發必須要匯入AOP包。匯入
spring-webmvc
後包含了AOP的包。-
匯入約束
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
-
配置註解的支援
<context:annotation-config/>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <!--掃描指定的包,這個包下所有使用的註解就會生效--> <context:component-scan base-package="com.th"/> </beans>
-
開啟元件掃描,配置掃描指定的包,使註解完全生效 ,多個包使用逗號隔開。
<context:component-scan base-package="com.th"/>
.當然也可以對掃描進行更加細緻的配置:
use-default-filters="false"
:不使用預設的過濾器,可以自定義配置。context:include-filter
標籤: 設定掃描哪些內容,可以定義型別以及表示式。<!--掃描com.th包下的Controller註解--> <context:component-scan base-package="com.th" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
context:exclude-filter
標籤: 設定哪些內容不進行掃描,可以定義型別以及表示式。<!--掃描com.th包下的除Controller以外的註解--> <context:component-scan base-package="com.th"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
-
使用註解
-
-
建立物件
@Component
: 元件、放於類上,表示建立這個類的物件,相當於<bean id="user" class="com.th.pojo.User" />
。import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component public class User { //相當於:<property name="name" value="TH"/> 也可以放在setter方法前 //@Value("TH") public String name; @Value("sanchun") public void setName(String name) { this.name = name; } }
在註解裡面
@component
的value
屬性值可以省略不寫,預設值是類名稱,首字母小寫。比如User
類的user
。 -
注入屬性
public class User { //相當於:<property name="name" value="TH"/> 也可以放在setter方法前 @Value("TH") public String name; }
public String name; @Value("sanchun") public void setName(String name) { this.name = name; }
對於複雜的資料注入到屬性中,建議使用xml方式進行注入。
-
Component的衍生的註解
- dao層
@Repository
- service層
@Service
- controller
@Controller
這四個註解的功能一致,都是代表將某個類註冊到Spring中,裝配bean,只是後三者在Web開發中按照MVC架構分層特定使用。
- dao層
-
註解實現自動裝配 (@Autowired、@Qualifier、Resource) 一般時物件注入或者引用注入,而注入普通型別屬性使用
@Value
在匯入註解約束以及對註解支援的情況下使用
@Autowired
作用範圍:屬性前、setter方法前。
Autowired 我們可以不用編寫Setter方法了,前提是你這個自動裝配的屬性在IOC(Spring)容器中存在,且符合byType型別。
public @interface Autowired { boolean required() default true; }
根絕以上原始碼,可以知道
@Autowired
註解可以傳遞引數,required
用於設定裝配屬性是否允許為空。如果定義了@Autowired
的required
屬性為false
,說明這個屬性可以為空,否則不能為空。@Nullable
:有相似作用,欄位標記了這個註解,說明這個欄位可以為null。@Qualifier
:當一個Spring容器中含有多個相同類的物件時,自動裝配的環境較為複雜,可以結合使用@Qualifier
註解來實現選取具體的某一個物件。其value
值為確定的其中bean
的id
值。典型案例就是一個介面含有多個實現類。Resource
:java原生對自動裝配的支援。預設使用byName
實現,其次才是對byType
的支援,若二則都不存在則會報錯。Resource
的name
屬性可以實現與@Qualifier
類似的功能。<bean id="cat11" class="com.th.pojo.Cat"/> <!-- <bean id="cat22" class="com.th.pojo.Cat"/>--> <bean id="dog11" class="com.th.pojo.Dog"/> <bean id="dog22" class="com.th.pojo.Dog"/> <bean id="man" class="com.th.pojo.People"> <property name="name" value="ThreePure2"/> </bean>
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.lang.Nullable; import javax.annotation.Resource; public class People { @Nullable private String name; @Resource(name = "cat11") private Cat cat; @Autowired @Qualifier(value = "dog22") private Dog dog; public String getName() { return name; } public void setName(String name) { this.name = name; } public Cat getCat() { return cat; } public Dog getDog() { return dog; } }
-
作用域 (
@Scope
)使用
@Scope
註解實現對作用域範圍的控制,括號內直接輸入具體的作用域並用""包起來。@Component @Scope("prototype") public class User { …… }
2.5 使用JavaConfig實現配置
以上即便是使用註解方式也並沒有完全脫離xml檔案,而JavaConfig就是完全使用註解來實現。
-
實體類中同樣可以通過
@Component
註解來進行建立物件,通過@Value
實現屬性注入。 -
需要一個配置類來替代
applicationContext.xml
的功能-
@Configuration
註解標識的類表示一個配置類 - 被
@Configuration
註解標識的類也會被Spring容器託管,註冊到容器中,因為他本來就是一個@Component
。
-
-
需要配置類中的方法代替
applicationContext.xml
中bean
標籤進行bean的註冊。- 這個方法的名字,就相當於bean標籤的id屬性
- 這個方法的返間值,就相當bean標籤中的class屬性,也就是要注入到容器的物件!
-
通過Annotationconfig 上下文來獲收容器,通過配置類的class物件載入!
AnnotationConfigApplicationContext
ApplicationContext context = new AnnotationConfigApplicationContext(UserConfig.class);
-
其他註解
-
@ComponentScan
: 用於自動掃描包 -
@Import
: 用於匯入其他配置類
-
實體類
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
private String name;
public String getName() {
return name;
}
@Value("THREEPURE")
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
配置類
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
//這個也會被Spring容器託管,註冊到容器中,因為他本來就是一個@Component
//@Configuration代表這是一個配置類,相當於beans.xml/ applicationContext.xml
@Configuration
@ComponentScan("com.th.pojo")
@Import(UserConfig2.class)
public class UserConfig {
//註冊一個bean,就相當於我們之前寫的一個bean標籤
//這個方法的名字,就相當於bean標籤的id屬性
// 這個方法的返間值,就相當bean標籤中的class屬性
@Bean
public User getUser(){
//就是返回要注入到bean的物件!
return new User();
}
}
測試類
public class MyTest {
public static void main(String[] args) {
//如果完全使用了配置類方式去做,我們就只能通過Annotationconfig 上下文來獲收容器,通過配置類的class物件載入!
ApplicationContext context = new AnnotationConfigApplicationContext(UserConfig.class);
User getUser = context.getBean("getUser", User.class);
System.out.println(getUser);
}
}
2.6 Bean的生命週期
- 通過構造器建立 bean 例項(無引數構造)
- 為 bean 的屬性設定值和對其他 bean 引用(呼叫 set 方法)
- 呼叫 bean 的初始化的方法(需要進行配置初始化的方法)
- bean 可以使用了(物件獲取到了)
- 當容器關閉時候,呼叫 bean 的銷燬的方法(需要進行配置銷燬的方法)
測試
public class Orders {
private String name;
/**無參構造方法*/
public Orders() {
System.out.println("第一步 執行無引數構造建立 bean 例項");
}
public void setName(String name) {
this.name = name;
System.out.println("第二步 呼叫 set 方法設定屬性值");
}
/**建立執行的初始化方法*/
public void initMethod(){
System.out.println("第三步,執行初始化方法");
}
/**建立執行的銷燬的方法*/
public void destroyMethod(){
System.out.println("第五步 執行銷燬的方法");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="orders" class="com.th.pojo.Orders" init-method="initMethod" destroy-method="destroyMethod" >
<property name="name" value="Phone"></property>
</bean>
</beans>
import com.th.pojo.Orders;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestOne {
@Test
public void test(){
//ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println(orders);
System.out.println("第四步 獲取建立 bean 例項物件");
System.out.println(orders);
//手動讓 bean 例項銷燬 (ClassPathXmlApplicationContext物件才能實現close操作)
context.close();
}
}
結果:
第一步 執行無引數構造建立 bean 例項
第二步 呼叫 set 方法設定屬性值
第三步,執行初始化方法
com.th.pojo.Orders@1060b431
第四步 獲取建立 bean 例項物件
第五步 執行銷燬的方法
注意:需要使用ApplicationContext的實現類來實現手動銷燬操作。
另外,Bean的完整生命週期還有包括兩項在初始化之前的狀態。實現這兩個狀態需要為Spring配置後置處理器。實現後置處理器需要建立一個類並實現BeanPostProcessor
介面並重寫postProcessBeforeInitialization
和postProcessAfterInitialization
兩個方法,這兩個方法對應這額外的兩種狀態。
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化方法之前執行的方法");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化方法之後執行的方法");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
有了後置處理器後需要將其配置到Spring中。在Spring中,只需要將這個後置處理器像配置bean一樣在applicationContext.xml
中配置那麼這個Spring容器中的全部bean都會加上後置處理器。
<bean id="myBeabPostProcessor" class="com.th.pojo.MyBeanPostProcessor" ></bean>
輸出結果為:
第一步 執行無引數構造建立 bean 例項
第二步 呼叫 set 方法設定屬性值
在初始化方法之前執行的方法
第三步,執行初始化方法
在初始化方法之後執行的方法
com.th.pojo.Orders@e720b71
第四步 獲取建立 bean 例項物件
第五步 執行銷燬的方法
3、AOP
3.1 什麼是AOP
- 面向切面程式設計(方面),利用 AOP 可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
- 通俗地講就是不修改原有程式碼實現新功能的新增。
- 案例:原有的資料增刪改查前後新增日誌功能。
Spring 框架一般都是基於 AspectJ
實現 AOP 操作。AspectJ 不是 Spring 組成部分,獨立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使用,AOP 操作可以基於 xml 配置檔案實現 或者基於註解方式實現(使用)。
3.2 AOP原理
AOP 底層使用動態代理,動態代理有兩種,分別為JDK動態代理
和CGLIB動態代理
。
JDK動態代理 面向有介面的情況
-
原理:建立介面實現類代理物件,增強類的方法。
使用:
- java.lang.Object
-
- java.lang.reflect.Proxy
-
newProxyInstance(ClassLoader loader, 類<?>[] interfaces, InvocationHandler h)
- 第一引數,類載入器 。
- 第二引數,增強方法所在的類,這個類實現的介面,支援多個介面 。
- 第三引數,實現這個介面
InvocationHandler
,建立代理物件,寫增強的部分。
-
- java.lang.reflect.Proxy
-
例項
使用動態代理前:
public interface UserDao { public int add(int a, int b); public String update(String id); }
public class UserDaoImpl implements UserDao{ @Override public int add(int a, int b) { System.out.println("add() executed"); return a+b; } @Override public String update(String id) { System.out.println("update()executed"); return id; } }
使用動態代理增加功能:
-
第一步:建立代理物件類、 實現
InvocationHandler
介面。class UserDaoProxy implements InvocationHandler { private Object obj; /**把建立的是誰的代理物件,就把誰傳遞過來,通過有參構造傳遞。比如這裡產生的是UserdaoImpl的代理物件。*/ /**為例讓這個代理類使用更加廣泛,直接傳遞Object物件,*/ public UserDaoProxy(Object obj) { this.obj = obj; } /**增強的邏輯程式碼*/ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 被增強方法之前 System.out.println("It was executed before the "+method.getName()+",and the args :"+ Arrays.toString(args)); // 被增強方法執行 Object res = method.invoke(obj, args); // 被增強方法之後 System.out.println("It was executed before ... :"+obj.toString()); return res; } }
-
在建立代理物件程式碼時,需要代理哪個物件就傳遞哪個物件。通過代理類的有參構造實現代理物件的傳遞。
-
通過重寫
invoke
方法,在其內部新增擴充套件功能。其中引數:- proxy : 呼叫該方法的代理例項,
- method:執行的方法(需要增強功能的方法)
- orgs: 為這個方法傳遞的引數。
-
通過
method.invoke(obj, args)
方法執行原來UserDao中的方法,method
就是傳遞過來的方法。可以在其之前或者之後對功能進行擴充套件,
-
-
第二步:使用
Proxy
類建立介面代理物件(獲取代理例項執行方法)public class JDKProxy { public static void main(String[] args) { Class[] interfaces = {UserDao.class}; UserDaoImpl userDao = new UserDaoImpl(); //建立介面實現類的介面物件 UserDao proxyDao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao)); //使用代理物件執行方法 int addResult = proxyDao.add(1, 2); System.out.println("add method result:"+addResult); } }
newProxyInstance()
方法的三個引數:- loader:類載入器定義代理類
- interfaces:被代理類實現的介面列表
- h:排程方法呼叫的呼叫處理函式(代理物件)
-
CGLIB動態代理 面向沒有介面的情況
-
原理:建立子類的代理物件,增強類的方法。
3.3 術語
- 連線點:類中可以增強功能的方法。
- 切入點:最終實際進行了功能增強的方法。
- 通知(增強):增加的那部分功能。含有多種型別:
- 前置通知
- 後置通知
- 環繞通知
- 異常通知
- 最終通知
- 切面:是一個動作,把一個通知應用到切入點的過程。
3.4 切入點表示式
- 作用:知道對哪個類裡面的哪個方法進行增強
- 語法結構:
execution([許可權修飾符] [返回型別] [類全路徑].[方法名稱]([引數列表]))
- 許可權修飾符可以用*代表全部許可權,返回型別一般省略。
舉例:
-
對 com.atguigu.dao.BookDao 類裡面的 add 方法進行增強 。
execution(* com.atguigu.dao.BookDao.add(..))
-
對 com.atguigu.dao.BookDao 類裡面的所有的方法進行增強
execution(* com.atguigu.dao.BookDao.* (..))
-
對 com.atguigu.dao 包裡面所有類,類裡面所有方法進行增強
execution(* com.atguigu.dao.*.* (..))
3.5 AOP 操作
AspectJ 註解方式實現AOP
-
建立類(被增強功能的類),在類裡面定義方法。
public class User { public void add(){ System.out.println("add method..."); } }
-
建立增強類(編寫增強邏輯)。
public class UserProxy { public void before(){ System.out.println("before..."); } }
-
進行通知的配置。
-
在 spring 配置檔案中,開啟註解掃描。
<!--開啟註解掃描--> <context:component-scan base-package="com.th" />
-
使用註解建立 User 和 UserProxy 物件。
@Component
@Component public class User { } @Component public class UserProxy { }
-
在增強類上面添加註解
@Aspect
。 @Aspect 用於生成代理物件@Component @Aspect public class UserProxy { }
-
在 spring 配置檔案中開啟生成代理物件。
proxy-target-class
預設狀態為false
,proxy-target-class="false"
表示使用JDK動態代理,proxy-target-class="true"
表示使用cglib動態代理,<!--- 開啟 Aspect 生成代理物件--> <aop:aspectj-autoproxy/>
-
-
配置不同型別的通知。
在增強類裡的通知方法上面新增通知型別註解,使用切入點表示式配置。
@Component @Aspect public class UserProxy { /**前置通知 @Before 註解表示作為前置通知*/ @Before("execution(* com.th.atguigu.aopanno.User.add(..))") public void before(){ System.out.println("before..."); } /**後置通知(返回通知)*/ @AfterReturning(value = "execution(* com.th.atguigu.aopanno.User.add(..))") public void afterReturning() { System.out.println("afterReturning........."); } /**最終通知*/ @After(value = "execution(* com.th.atguigu.aopanno.User.add(..))") public void after() { System.out.println("after........."); } /**異常通知*/ @AfterThrowing(value = "execution(* com.th.atguigu.aopanno.User.add(..))") public void afterThrowing() { System.out.println("afterThrowing........."); } /**環繞通知*/ @Around(value = "execution(* com.th.atguigu.aopanno.User.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("環繞之前........."); //被增強的方法執行 proceedingJoinPoint.proceed(); System.out.println("環繞之後........."); } }
結果:
環繞之前......... before... add method... afterReturning......... after......... 環繞之後......... //如果發生異常: 環繞之前......... before... afterThrowing......... after.........
異常通知:只有在被增強方法內部發生異常時呼叫。
最終通知:無論是否發生異常都會被執行。而後置通知如果發生異常就不會被執行。
-
相同的切入點抽取
如上所示,有多種不同型別的通知,在每個通知上都需要相同的註解表示式會比較麻煩,那麼可以抽取公共部分簡化操作。在通知的註解上使用配置了的方法名即可。
@Pointcut("execution(* com.th.atguigu.aopanno.User.add(..))") public void pointDemo(){ } /**前置通知 @Before 註解表示作為前置通知*/ @Before("pointDemo()") public void before(){ System.out.println("before..."); }
-
設定增強類優先順序
當有多個增強類對同一個方法進行增強時,可以設定增強類的優先順序。
在增強類上面添加註解
@Order(數字型別值)
,數字型別值越小優先順序越高。@Component @Aspect @Order(0) public class PersonProxy { @Before("execution(* com.th.atguigu.aopanno.User.add(..))") public void beforePerson(){ System.out.println("before of PersonProxy"); } }
before of PersonProxy 環繞之前......... before... add method... afterReturning......... after......... 環繞之後.........
-
完全使用註解開發 (建立配置類,不需要建立 xml 配置檔案)
@Configuration @ComponentScan(basePackages = {"com.atguigu"}) @EnableAspectJAutoProxy(proxyTargetClass = true) public class ConfigAop { }
@EnableAspectJAutoProxy(proxyTargetClass = true)
相當於<aop:aspectj-autoproxy/>
。
AspectJ XML配置檔案實現AOP
-
建立兩個類,增強類和被增強類,建立方法 。
-
在 spring 配置檔案中(使用
bean
)建立兩個類物件。<!--建立增強類和被增強類物件--> <bean id="book" class="com.th.atguigu.aopxml.Book"/> <bean id="bookProxy" class="com.th.atguigu.aopxml.BookProxy"/>
-
在 spring 配置檔案中配置切入點。
<!--配置 aop 增強--> <aop:config> <!--切入點--> <aop:pointcut id="p" expression="execution(* com.th.atguigu.aopxml.Book.buy(..))"/> <!--配置切面--> <aop:aspect ref="bookProxy"> <!--增強作用在具體的方法上--> <aop:before method="before" pointcut-ref="p"/> </aop:aspect> </aop:config>
-
測試
public void testTwo(){ ApplicationContext context = new ClassPathXmlApplicationContext("beanXml.xml"); Book book = context.getBean("book", Book.class); book.buy(); }
4、整合資料庫
4.1 整合mybatis
Mybatis使用步驟:
- 匯入依賴(
mybatis
、mysql-connector-java
) - 配置mybatis(
mybatis-config.xml
) - 構建工具類,獲取SqlSessionFactory和SqlSession
- 編寫實體類(可使用
lombok
) - Dao介面
- 介面實現類Impl【XXXMapper.xml】
- 在Mybatis核心配置檔案中註冊每一個Mapper.xml
- 測試
整合
-
導包
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.9.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.3</version> </dependency>
mybatis-spring
:將 MyBatis 程式碼無縫地整合到 Spring 中,允許 MyBatis 參與到 Spring 的事務管理之中,建立對映器 mapper 和SqlSession
並注入到 bean 中,以及將 Mybatis 的異常轉換為 Spring 的DataAccessException
。 最終,可以做到應用程式碼不依賴於 MyBatis,Spring 或 MyBatis-Spring。 -
配置mybatis資料來源
資料來源:用Spring的資料來源代替Mybatis的配置 ——————
org.springframework.jdbc.datasource
<bean id="DataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="mysqlpw"/> </bean>
資料來源的形式是多樣的,也可以使用其他形式對資料來源進行配置。
-
獲取
SqlSessionFactory
<!--sqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="DataSource" /> <!--繫結mybatis的配置檔案--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:com/th/mapper/*.xml"/> </bean>
可以在這個
SqlSessionFactoryBean
配置Mybatis的一些其他配置(別名等)。最主要的是可以直接匯入Mybatis的原有配置檔案mybatis-config.xml
,兩種配置可以配合使用。mapperLocations
獲取Mapper的路徑,相當於在Mybatis對Mappper的註冊。 -
獲取和
SqlSession
,SqlSessionTemplate
:就是mybatis中的sqlSession。<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--只能通過構造器注入sqlSessionFactory,因為它沒有set方法--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean>
-
編寫實體類
-
Dao
介面 -
介面實現類
XXXMapper.xml
和Impl
實現類,Impl實現類
是必須的,而在Mybatis中實現類不需要,在Spring中Impl實現類
是必須的,用於獲取sqlSessionTemplate
。public class UserMapperImpl implements UserMapper{ //我們的所有操作,都用sqlSession來執行,在原來,現在使用SqlSessionTemplate; private SqlSessionTemplate sqlSessionTemplate; public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSessionTemplate = sqlSession; } @Override public List<User> selectUserList() { UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class); return mapper.selectUserList(); } }
當然
Impl實現類
也可以通過繼承SqlSessionDaoSupport
類,通過呼叫getSqlSession()
方法得到一個SqlSessionTemplate
來執行 SQL 方法。public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{ @Override public List<User> selectUserList() { return getSqlSession().getMapper(UserMapper.class).selectUserList(); } }
-
Spring配置檔案中建立實現類物件。
<bean id="userMapperImpl" class="com.th.mapper.UserMapperImpl"> <property name="sqlSession" ref="sqlSessionTemplate"/> </bean>
通過繼承
SqlSessionDaoSupport
類的Impl實現類
那麼在建立其物件時需要傳遞sqlSessionFactory
。<bean id="userMapperImpl2" class="com.th.mapper.UserMapperImpl2"> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean>
-
通過實現類物件進行業務操作
public void testSelectUserList() throws IOException { ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml"); UserMapper userMapper = context.getBean("userMapperImpl2", UserMapper.class); for (User user : userMapper.selectUserList()) { System.out.println(user); } }
5、宣告式事務管理
5.1 什麼事務
事務是資料庫操作最基本單元,邏輯上一組操作,要麼都成功,如果有一個失敗所有操作都失敗。
事務分類:
- 宣告式事物:通過AOP實現。
- 程式設計式事物:在程式碼中進行事務管理。
Spring均支援這兩種事物,但是推薦使用宣告式事物。
5.2、事務四個特性(ACID)
- 原子性
- 一致性
- 隔離性
- 永續性
5.3 Spring中的事務管理
Spring提供一個介面PlatformTransactionManager
,代表事務管理器,這個介面針對不同的框架提供不同的實現類,DataSourceTransactionManager
為mybatis
和JdbcTemplate
提供使用。
1、基於註解方式
-
在 spring 配置檔案配置事務管理器
要開啟 Spring 的事務處理功能,在 Spring 的配置檔案中建立一個
DataSourceTransactionManager
物件:<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入資料來源--> <property name="dataSource" ref="dataSource"/> </bean>
-
在 spring 配置檔案,開啟事務註解
-
在 spring 配置檔案引入名稱空間 tx
-
開啟事務註解
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
-
-
在 service 類上面(或者 service 類裡面方法上面)新增事務註解
@Transactional
@Transactional
:這個註解作用在類上,也可以作用在方法上。@Service @Transactional() public class AccountService { }
宣告式事務管理引數配置,
@Transactional()
屬性引數的設定。-
propagation
:事務傳播行為多事務方法(對資料庫表資料進行變換的操作)直接進行呼叫,這個過程中事務是如何進行管理的。如一個事物方法呼叫另一個事務方法,這個被呼叫的事務方法是否進行事務管理。Spring有7種事務傳播行為。
@Transactional(propagation = Propagation.REQUIRED)
-
ioslation
:事務隔離級別事務具有隔離性,不考慮隔離性可能會產生髒讀、不可重複讀、虛(幻)讀問題。
髒讀
:一個未提交事務讀取到另一個未提交事務的資料。
不可重複讀
:一個未提交事務讀取到另一提交事務修改資料。
虛讀
:一個未提交事務讀取到另一提交事務新增資料。Spring通過設定事務隔離級別,解決讀問題:
髒讀 不可重複讀 幻讀 READ_UNCOMMITTED 有 有 有 READ_COMMITTED 無 有 有 REPEATABLE_READ 無 無 有 SERIALIZABLE 無 無 無 @Transactional(isolation = Isolation.SERIALIZABLE)
-
timeout
:超時時間-
事務需要在一定時間內進行提交,如果不提交則進行回滾 。
-
預設值是 -1(不超時) ,設定時間以秒單位進行計算。
@Transactional(timeout = -1)
-
-
readOnly
:是否只讀-
讀:查詢操作,寫:新增修改刪除操作
-
readOnly
預設值false
,表示可以查詢,可以新增修改刪除操作 -
設定
readOnly
值是true
,設定成 true 之後,只能查詢.@Transactional(readOnly = true)
-
-
rollbackFor
:回滾- 設定出現哪些異常進行事務回滾
-
noRollbackFor
:不回滾- 設定出現哪些異常不進行事務回滾
-
2、基於 xml 配置檔案方式
-
在 spring 配置檔案配置事務管理器
<!--開啟 Spring 的事務處理功能--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <constructor-arg ref="dataSource" /> </bean>
-
配置事物通知,結合AOP實現事物的織入
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <!--給哪些方法配置事物--> <!--new 配置事物的傳播特性:propagation--> <tx:attributes> <tx:method name="add" propagation="REQUIRED"/> <tx:method name="delete" propagation="REQUIRED"/> <tx:method name="update" propagation="REQUIRED"/> <tx:method name="query" read-only="true"/> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
-
配置事物切入
<aop:config> <aop:pointcut id="txPointCut" expression="execution(* com.th.kuang.mapper.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" /> </aop:config>
3、完全註解開發
@Configuration //配置類
@ComponentScan(basePackages = "com.th.atguigu") //元件掃描
@EnableTransactionManagement //開啟事務
public class TxConfig {
//建立資料庫連線池
@Bean
public DriverManagerDataSource getDruidDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("mysqlpw");
return dataSource;
}
//建立 JdbcTemplate 物件
@Bean
public JdbcTemplate getJdbcTemplate(DriverManagerDataSource dataSource) {
//到 ioc 容器中根據型別找到 dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入 dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//建立事務管理器
@Bean
public DataSourceTransactionManager
getDataSourceTransactionManager(DriverManagerDataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
關於No qualifying bean of type 'org.springframework.transaction.PlatformTransactionManager' available: expected single matching bean but found 2: transactionManager,getDataSourceTransactionManager
報錯問題。
由於在原來註解方式進行事務管理時存在一個數據源和事務管理器,@Transactional()
註解時不直接指定,Spring就不知道具體使用哪一個事務管理器來進行事務管理了。使用只要@Transactional(transactionManager = “aaaTransactionManager”)
來進行指定具體的事務管理即可,當然最直接的方法是直接刪除原來的事務管理。
6、Spring5 框架新功能
6.1 基礎
整個 Spring5 框架的程式碼基於 Java8,執行時相容 JDK9,許多不建議使用的類和方 法在程式碼庫中刪除
-
Spring 5.0 框架自帶了通用的日誌封裝。
Spring5 已經移除
Log4jConfigListener
,官方建議使用Log4j2
。 -
Spring5 框架核心容器支援
@Nullable
註解@Nullable
註解可以使用在方法、屬性,引數上,分別表示方法返回可以為空,屬性值可以為空,引數值可以為空 。 -
Spring5 核心容器支援函式式風格
GenericApplicationContext
//函式式風格建立物件,交給 spring 進行管理 @Test public void testGenericApplicationContext() { //1 建立 GenericApplicationContext 物件 GenericApplicationContext context = new GenericApplicationContext(); //2 呼叫 context 的方法物件註冊 context.refresh(); context.registerBean("user1",User.class,() -> new User()); //3 獲取在 spring 註冊的物件 // User user = (User)context.getBean("com.atguigu.spring5.test.User"); User user = (User)context.getBean("user1"); System.out.println(user); }
-
Spring5 支援整合 JUnit4和 JUnit5。
@ExtendWith(SpringExtension.class) @ContextConfiguration("classpath:bean1.xml") public class JTest5 { @Autowired private UserService userService; @Test public void test1() { userService.accountMoney(); } } //使用一個複合註解替代上面兩個註解完成整合 @SpringJUnitConfig(locations = "classpath:bean1.xml") public class JTest5 { @Autowired private UserService userService; @Test public void test1() { userService.accountMoney(); } }
6.1 Webflux
1、SpringWebflux 介紹
-
是 Spring5 新增新的模組,用於 web 開發的,功能和
SpringMVC
類似的,Webflux
使用 當前一種比較流行的響應式程式設計出現的框架。 -
使用傳統 web 框架,比如 SpringMVC這些基於
Servlet
容器,Webflux 是一種非同步非阻塞的框架,非同步非阻塞的框架在Servlet3.1
以後才支援,核心是基於Reactor
的相關 API 實現 的。 -
非同步和同步針對呼叫者,呼叫者傳送請求,如果等著對方迴應之後才去做其他事情就是同步,如果傳送請求之後不等著對方迴應就去做其他事情就是非同步。
-
阻塞和非阻塞針對被呼叫者,被呼叫者受到請求之後,做完請求任務之後才給出反饋就是阻塞,受到請求之後馬上給出反饋然後再去做事情就是非阻塞。
-
Webflux 特點:
-
非阻塞式:在有限資源下,提高系統吞吐量和伸縮性,以
Reactor
為基礎實現響應式程式設計。 - 函數語言程式設計:Spring5 框架基於 java8,Webflux 使用 Java8 函數語言程式設計方式實現路由請求
-
非阻塞式:在有限資源下,提高系統吞吐量和伸縮性,以
-
比較 SpringMVC
- 兩個框架都可以使用註解方式,都執行在
Tomet
等容器中。 -
SpringMVC
採用指令式程式設計(程式碼一行一行執行),Webflux
採用非同步響應式程式設計。
- 兩個框架都可以使用註解方式,都執行在
2、響應式程式設計
響應式程式設計程式設計RP,即Reactive Programming
,響應式程式設計是一種面向資料流和變化傳播的程式設計正規化。這意味著可以在程式語言中很方便地表達靜態或動態的資料流,而相關的計算模型會自動將變化的值通過資料流進行傳播。
電子表格程式就是響應式程式設計的一個例子。單元格可以包含字面值或類似"=B1+C1"的公 式,而包含公式的單元格的值會依據其他單元格的值的變化而變化。
響應式程式設計的思想是觀察者模式。Java8 及其之前版本提供的觀察者模式兩個類Observer
和 Observable
。