1.Spring
1.1 簡介
-
Spring : 春天 —>給軟件行業帶來了春天
-
2002年,Rod Jahnson首次推出了Spring框架雛形interface21框架
-
2004年3月24日,Spring框架以interface21框架爲基礎,經過重新設計,發佈了1.0正式版
-
Rod Johnson,Spring Framework創始人
-
Don’t Reinvent the Wheel(輪子理論)
-
Spring理念 : 整合現有的框架技術,使現有技術更加實用
-
SSH : Struct2+Spring+Hibernate
-
SSM : Spring+SpringMVC+Mybatis
1.2 優點
-
非侵入式設計
-
方便解耦、簡化開發
-
支持AOP
-
支持聲明式事務處理
-
方便程序的測試
-
方便集成各種優秀框架
-
降低Jave EE API的使用難度
Spring是一個輕量級的
控制反轉(Inversion of Control)和
面向切面(Aspect Oriented Programming)的容器(框架)
1.3 組成
Spring 框架是一個分層架構,由 7 個定義良好的模塊組成 , Spring 模塊構建在覈心容器之上,核心容器定義了創建、配置和管理 bean 的方式
組成 Spring 框架的每個模塊(或組件)都可以單獨存在,或者與其他一個或多個模塊聯合實現。每個模塊的功能如下:
- Spring Core 核心容器:核心容器提供 Spring 框架的基本功能 , 核心容器的主要組件是BeanFactory,它是工廠模式的實現。BeanFactory 使用控制反轉(IOC) 模式將應用程序的配置和依賴性規範與實際的應用程序代碼分開
- Spring Context上下文:Spring 上下文是一個配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企業服務,例如 JNDI、EJB、電子郵件、國際化、校驗和調度功能
- Spring AOP:通過配置管理特性,Spring AOP 模塊直接將面向切面的編程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的對象。Spring AOP 模塊爲基於 Spring 的應用程序中的對象提供了事務管理服務。通過使用 Spring AOP,不用依賴組件,就可以將聲明性事務管理集成到應用程序中
- Spring DAO:JDBC DAO 抽象層提供了有意義的異常層次結構,可用該結構來管理異常處理和不同數據庫供應商拋出的錯誤消息。異常層次結構簡化了錯誤處理,並且極大地降低了需要編寫的異常代碼數量(例如打開和關閉連接)。Spring DAO 的面向 JDBC 的異常遵從通用的 DAO 異常層次結構
- Spring ORM:Spring 框架插入了若干個 ORM 框架,從而提供了 ORM 的對象關係工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有這些都遵從 Spring 的通用事務和 DAO 異常層次結構
- Spring Web 模塊:Web 上下文模塊建立在應用程序上下文模塊之上,爲基於 Web 的應用程序提供了上下文。所以,Spring 框架支持與 Jakarta Struts 的集成。Web 模塊還簡化了處理多部分請求以及將請求參數綁定到域對象的工作。
- Spring MVC 框架:MVC 框架是一個全功能的構建 Web 應用程序的 MVC 實現。通過策略接口,MVC 框架變成爲高度可配置的,MVC 容納了大量視圖技術,其中包括 JSP、Velocity、Tiles、iText 和 POI
1.4 拓展
現代化的java開發,就是基於Spring的開發
-
Spring Boot
- 一個快速開發的腳手架
- 基於SpringBoot可以快速的開發單個微服務
- 約定大於配置
- Spring Boot專注於快速、方便集成的單個微服務個體
-
Spring Cloud
- Spring Cloud很大的一部分是基於Spring Boot來實現
- Spring Cloud離不開Spring Boot,屬於依賴的關係
- 要學習SpringCloud , 必須要學習SpringBoot
- Spring Cloud關注全局的服務治理框架
2.IOC理論推導
2.1 IOC基礎
新建一個空白的maven項目
分析實現
1.先寫一個UserDao接口
public interface UserDao {
public void getUser();
}
2.再寫Dao的實現類
public class UserDaoSqlImpl implements UserDao {
@Override
public void getUser() {
System.out.println("獲取Sql數據庫用戶數據");
}
}
3.然後去寫UserService的接口
public interface UserService {
public void getUser();
}
4.最後寫Service的實現類
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
5.測試
@Test
public void test(){
UserService service = new UserDaoSqlImpl();
service.getUser();
}
6.這是原來的方式 ,現在修改一下 ,把UserDao的實現類增加一個
public class UserDaoMySqlImpl implements UserDao {
@Override
public void getUser() {
System.out.println("獲取MySql數據庫用戶數據");
}
}
7.緊接着要去使用MySql的話 , 就需要去service實現類裏面修改對應的實現
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoMySqlImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
8.再假設, 再增加一個Userdao的實現類
public class UserDaoOracleImpl implements UserDao {
@Override
public void getUser() {
System.out.println("獲取Oracle數據庫用戶數據");
}
}
那麼要使用Oracle , 又需要去service實現類裏面修改對應的實現 ,假設這種需求非常大 , 這種方式就不適用了, 每次變動 , 都需要修改大量代碼 ,耦合性太高
那如何去解決呢 ?
9.在需要用到它的地方 , 不去實現它 , 而是留出一個接口 , 利用set方法去注入
public class UserServiceImpl implements UserService {
private UserDao userDao;
// 利用set實現
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
10.測試
@Test
public void test(){
UserServiceImpl service = new UserServiceImpl();
service.setUserDao( new UserDaoMySqlImpl() );
service.getUser();
//那現在又想用Oracle去實現呢
service.setUserDao( new UserDaoOracleImpl() );
service.getUser();
}
以前,程序是由程序員主動去創建對象
現在,使用了set注入之後,程序不在具有主動性,而是變成了被動的接收對象
以前,所有對象都是由程序去進行創建
現在,把創建對象的主動權交給了調用者,程序不用去管怎麼創建,怎麼實現了,它只負責提供一個接口
這種思想 , 從本質上解決了問題 , 程序員不再去管理對象的創建 , 更多的去關注業務的實現 ,耦合性降低 . 這也就是IOC的原型
2.2 IOC本質
控制反轉IoC(Inversion of Control),是一種設計思想,DI(依賴注入)是實現IoC的一種方法,也有人認爲DI只是IoC的另一種說法,沒有IoC的程序中 , 我們使用面向對象編程 , 對象的創建與對象間的依賴關係完全硬編碼在程序中,對象的創建由程序自己控制,控制反轉後將對象的創建轉移給第三方,個人認爲所謂控制反轉就是:獲得依賴對象的方式反轉了。
採用XML方式配置Bean的時候,Bean的定義信息是和實現分離的,而採用註解的方式可以把兩者合爲一體,Bean的定義信息直接以註解的形式定義在實現類中,從而達到了零配置的目的。
控制反轉是一種通過描述(XML或註解)並通過第三方去生產或獲取特定對象的方式。在Spring中實現控制反轉的是IoC容器,其實現方法是依賴注入(Dependency Injection,DI)
3.第一個Spring程序
1.導入Jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
2.編寫我們的spring文件 , 命名爲beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--bean就是java對象 , 由Spring創建和管理-->
<!--注意: 標籤property裏的name並不是屬性 , 而是set方法後面的那部分 , 首字母小寫-->
<!--引用另外一個bean , 不是用value 而是用 ref-->
<bean id="userServiceImpl" class="com.zr.service.UserServiceImpl">
<!-- 屬性通過set方法進行注入-->
<property name="userDao" ref="userDaoSqlImpl"></property>
</bean>
<bean id="userDaoMysqlImpl" class="com.zr.dao.UserDaoMysqlImpl"></bean>
<bean id="userDaoOracleImpl" class="com.zr.dao.UserDaoOracleImpl"></bean>
<bean id="userDaoSqlImpl" class="com.zr.dao.UserDaoSqlImpl"></bean>
</beans>
3.測試類
public class MyTest {
public static void main(String[] args) {
//解析beans.xml文件,生成管理相應的Bean對象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//getBean : 參數即爲spring配置文件中bean的id
UserServiceImpl userServiceImpl = (UserServiceImpl) applicationContext.getBean("userServiceImpl");
userServiceImpl.getUserMessage();
}
}
思考
-
Hello 對象是誰創建的 ?
由Spring創建的
-
Hello 對象的屬性是怎麼設置的 ?
由Spring容器設置的
-
控制反轉 :
控制 : 傳統應用程序的對象是由程序本身控制創建的 , 使用Spring後 , 對象是由Spring來創建的
反轉 : 程序本身不創建對象 , 而變成被動的接收對象
-
依賴注入 : 就是利用set方法來進行注入的
-
IOC是一種編程思想,由主動的編程變成被動的接收
-
要實現不同的操作 , 只需要在xml配置文件中進行修改 , 對象由Spring 來創建 , 管理 , 裝配
4.IOC創建對象方式
4.1 通過無參構造方法來創建(默認方式)
public class User {
private String name;
public User() {
System.out.println("user無參構造方法");
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name="+ name );
}
}
2、beans.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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.zr.pojo.User">
<property name="name" value="kuangshen"/>
</bean>
</beans>
3、測試類
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在執行getBean的時候, user已經創建好了 , 通過無參構造
User user = (User) context.getBean("user");
//調用對象的方法 .
user.show();
}
結果可以發現
new ClassPathXmlApplicationContext(“beans.xml”);一執行,user bean已經通過無參構造初始化了
4.2 通過有參構造方法來創建
1、UserT . java
public class UserT {
private String name;
public UserT(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name="+ name );
}
}
2、beans.xml 有三種方式編寫
<!-- 第一種根據index參數下標設置 -->
<bean id="userT" class="com.zr.pojo.UserT">
<!-- index指構造方法 , 下標從0開始 -->
<constructor-arg index="0" value="zr2"/>
</bean>
<!-- 第二種根據參數名字設置 -->
<bean id="userT" class="com.zr.pojo.UserT">
<!-- name指參數名 -->
<constructor-arg name="name" value="zr2"/>
</bean>
<!-- 第三種根據參數類型設置,不建議使用 -->
<bean id="userT" class="com.zr.pojo.UserT">
<constructor-arg type="java.lang.String" value="zr2"/>
</bean>
3、測試
@Test
public void testT(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserT user = (UserT) context.getBean("userT");
user.show();
}
結論:在配置文件加載的時候,其中管理的對象都已經初始化了
5.Spring配置
5.1 別名的配置
alias 設置別名 , 爲bean設置別名 , 可以設置多個別名
<!--設置別名:在獲取Bean的時候可以使用別名獲取-->
<alias name="userT" alias="userNew"/>
5.2 bean的配置
<!--bean就是java對象,由Spring創建和管理-->
<!--
id 是bean的標識符,要唯一,如果沒有配置id,name就是默認標識符
如果配置id,又配置了name,那麼name是別名
name可以設置多個別名,可以用逗號,分號,空格隔開
如果不配置id和name,可以根據applicationContext.getBean(.class)獲取對象
class是bean的全限定名=包名+類名
-->
<bean id="hello" name="hello2 h2,h3;h4" class="com.zr.pojo.Hello">
<property name="name" value="Spring"/>
</bean>
5.3 import的配置
團隊的合作通過import來實現,它可以將多個配置文件,導入合併爲一個
<import resource="{path}/beans.xml"/>
6.依賴注入
概念
-
依賴注入(Dependency Injection,DI)
-
依賴 : 指Bean對象的創建依賴於容器 . Bean對象的依賴資源
-
注入 : 指Bean對象所依賴的資源 , 由容器來設置和裝配
6.1 構造器注入
IOC創建對象方式已經說過
6.2 set注入
要求被注入的屬性 , 必須有set方法 , set方法的方法名由set + 屬性首字母大寫 , 如果屬性是boolean類型 , 沒有set方法 , 是 is方法
1.複雜類型
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Address{" +
"address='" + address + '\'' +
'}';
}
}
2.真實測試對象
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String[] getBooks() {
return books;
}
public void setBooks(String[] books) {
this.books = books;
}
public List<String> getHobbys() {
return hobbys;
}
public void setHobbys(List<String> hobbys) {
this.hobbys = hobbys;
}
public Map<String, String> getCard() {
return card;
}
public void setCard(Map<String, String> card) {
this.card = card;
}
public Set<String> getGames() {
return games;
}
public void setGames(Set<String> games) {
this.games = games;
}
public String getWife() {
return wife;
}
public void setWife(String wife) {
this.wife = wife;
}
public Properties getInfo() {
return info;
}
public void setInfo(Properties info) {
this.info = info;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", address=" + address +
", books=" + Arrays.toString(books) +
", hobbys=" + hobbys +
", card=" + card +
", games=" + games +
", wife='" + wife + '\'' +
", info=" + info +
'}';
}
}
3.beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<property name="name" value="鄭"></property>常量注入
<property name="address" ref="address"></property>Bean注入
<property name="books">數組注入
<array>
<value>紅樓夢</value>
<value>三國演義</value>
</array>
</property>
<property name="hobbys">List注入
<list>
<value>睡覺</value>
<value>吃飯</value>
</list>
</property>
<property name="card">Map注入
<map>
<entry key="中國" value="l"></entry>
<entry key="美國" value="2"></entry>
</map>
</property>
<property name="games">set注入
<set>
<value>q</value>
<value>w</value>
</set>
</property>
<property name="wife"><null/></property>Null注入
<property name="info">Properties注入
<props>
<prop key="1">嘻嘻嘻</prop>
<prop key="2">笑笑笑</prop>
</props>
</property>
</bean>
<bean id="address" class="com.zr.pojo.Address">
<property name="address" value="山東日照"></property>
</bean>
</beans>
4.測試類
public class MyTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);
}
}
5.結果
Student{name=‘鄭’, address=Address{address=‘山東日照’}, books=[紅樓夢, 三國演義], hobbys=[睡覺, 吃飯], card={中國=l, 美國=2}, games=[q, w], wife=‘null’, info={1=嘻嘻嘻, 2=笑笑笑}}
6.3 命名空間注入
1、P命名空間注入 : 需要在頭文件中加入約束文件,只支持常量注入和Bean注入
導入約束 : xmlns:p="http://www.springframework.org/schema/p"
<!--P(屬性: properties)命名空間-->
<bean id="user" class="com.zr.pojo.User" p:name="鄭" p:age="18"/>
2、c命名空間注入 : 需要在頭文件中加入約束文件,只支持常量注入和Bean注入
導入約束 : xmlns:c="http://www.springframework.org/schema/c"
<!--C(構造: Constructor)命名空間-->
<bean id="user" class="com.zr.pojo.User" c:name="鄭" c:age="18"/>
6.4 bean的作用域
Scope | Description |
---|---|
singleton | (Default) Scopes a single bean definition to a single object instance for each Spring IoC container. |
prototype | Scopes a single bean definition to any number of object instances. |
request | Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext . |
session | Scopes a single bean definition to the lifecycle of an HTTP Session . Only valid in the context of a web-aware Spring ApplicationContext . |
application | Scopes a single bean definition to the lifecycle of a ServletContext . Only valid in the context of a web-aware Spring ApplicationContext . |
websocket | Scopes a single bean definition to the lifecycle of a WebSocket . Only valid in the context of a web-aware Spring ApplicationContext . |
1.單例模式(Spring默認機制)
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
2.原型模式:每次從容器中get的時候,都會產生一個新對象
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
3.其餘的request,session,application這些只能在web開發中用到
7.Bean的自動裝配
- 自動裝配是使用spring滿足bean依賴的一種方法
- spring會在應用上下文中爲某個bean尋找其依賴的bean
Spring中bean有三種裝配機制,分別是:
- 在xml中顯式配置
- 在java中顯式配置
- 隱式的bean發現機制和自動裝配
這裏我們主要講第三種:自動化的裝配bean
7.1使用XML實現自動裝配
測試環境搭建
1、新建一個項目
2、新建兩個實體類,Cat Dog 都有一個shout的方法
public class Cat {
public void shout() {
System.out.println("miao~");
}
}
public class Dog {
public void shout() {
System.out.println("wang~");
}
}
3、新建一個用戶類 User
public class User {
private Cat cat;
private Dog dog;
private String name;
}
4、編寫Spring配置文件
<?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="dog" class="com.zr.pojo.Dog"/>
<bean id="cat" class="com.zr.pojo.Cat"/>
<bean id="user" class="com.zr.pojo.User">
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
<property name="name" value="zheng"/>
</bean>
</beans>
5、測試
public class MyTest {
@Test
public void testMethodAutowire() {
ApplicationContext context = newClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
user.getCat().shout();
user.getDog().shout();
}
}
結果正常輸出,環境OK
byName
autowire byName (按名稱自動裝配)
由於在手動配置xml過程中,常常發生字母缺漏和大小寫等錯誤,而無法對其進行檢查,使得開發效率降低
採用自動裝配將避免這些錯誤,並且使配置簡單化
測試:
1、修改bean配置,增加一個屬性 autowire=“byName”
<bean id="user" class="com.zr.pojo.User" autowire="byName">
<property name="name" value="zheng"/>
</bean>
2、再次測試,結果依舊成功輸出
3、將 cat 的bean id修改爲 catXXX
4、再次測試, 執行時報空指針java.lang.NullPointerException。因爲按byName規則找不對應set方法,setCat方法就沒執行,對象就沒有初始化,所以調用時就會報空指針錯誤
小結:
當一個bean節點帶有 autowire byName的屬性時
- 將查找其類中所有的set方法名,例如setCat,獲得將set去掉並且首字母小寫的字符串,即cat
- 去spring容器中尋找是否有此字符串名稱id的對象。
- 如果有,就取出注入,如果沒有,就不注入
byType
autowire byType (按類型自動裝配)
使用autowire byType首先需要保證:同一類型的對象,在spring容器中唯一,如果不唯一,會報不唯一的異常
NoUniqueBeanDefinitionException
測試:
1、將user的bean配置修改一下 : autowire=“byType”
2、測試,正常輸出
3、再註冊一個cat 的bean對象
<bean id="dog" class="com.zr.pojo.Dog"/>
<bean id="cat" class="com.zr.pojo.Cat"/>
<bean id="cat2" class="com.zr.pojo.Cat"/>
<bean id="user" class="com.zr.pojo.User" autowire="byType">
<property name="str" value="zr"/>
</bean>
4、測試,報錯:NoUniqueBeanDefinitionException
5、刪掉cat2,將cat的bean名稱改掉,測試,因爲是按類型裝配,所以並不會報異常,也不影響最後的結果,甚至將id屬性去掉,也不影響結果
這就是按照類型自動裝配
小結:
Spring的自動裝配需要從兩個角度來實現:
- 組件掃描(component scanning):spring會自動發現應用上下文中所創建的bean
- 自動裝配(autowiring):spring自動滿足bean之間的依賴,也就是我們說的IoC/DI
組件掃描和自動裝配組合發揮巨大威力,使得顯示的配置降低到最少
**推薦不使用自動裝配xml配置 , 而使用註解 **
7.2 使用註解實現自動裝配
註釋在配置Spring方面比XML更好嗎?
基於註釋的配置的引入提出了一個問題,即這種方法是否比XML“更好”。簡短的答案是“取決於情況”。長話短說,每種方法都有其優缺點,通常,由開發人員決定哪種策略更適合他們。由於定義方式的不同,註釋在聲明中提供了很多context,從而使配置更短,更簡潔。但是,XML擅長連接組件而不接觸其源代碼或重新編譯它們。一些開發人員更喜歡將裝配放置在靠近源的位置,而另一些開發人員則認爲帶註釋的類不再是POJO,而且,該配置變得分散且難以控制。
使用註解須知:
1.導入約束 : context約束
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
2.配置註解的支持 : < 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/>
</beans>
3.在spring4之後,想要使用註解形式,必須得要引入aop的包
@Autowired
默認byType,Type分辨不了,再通過byName,都不行,則報錯
直接在屬性上使用即可,使用Autowired可以不用編寫set方法了,通過反射注入
前提是這個自動裝配的屬性在IOC容器中存在
如果使用在set方法上,則通過set方法注入
測試:
1、將User類中的set方法去掉,使用@Autowired註解
public class User {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String str;
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
public String getStr() {
return str;
}
}
2、此時配置文件內容
<context:annotation-config/>
<bean id="dog" class="com.zr.pojo.Dog"/>
<bean id="cat" class="com.zr.pojo.Cat"/>
<bean id="user" class="com.zr.pojo.User"/>
3、測試,成功輸出結果
@Autowired(required=false) 說明:
false,對象可以爲null(xml裏可以沒有Cat bean)
true,對象必須注入對象,不能爲null
//如果允許對象爲null,設置required = false,默認爲true
@Autowired(required = false)
private Cat cat;
@Qualifier
如果@Autowired自動裝配的環境比較複雜,比如說xml裏配置了多個同種類型的Cat bean,且id名也不符合
自動裝配無法通過一個註解[@Autowired]完成的時候,我們可以使用@Qualifier(value = “bean的id”)去配置@Autowired的使用,指定一個唯一的bean對象注入
<bean id="cat" class="com.zr.pojo.Cat"></bean>
<bean id="cat1" class="com.zr.pojo.Cat"></bean>
public class People {
private String name;
@Autowired
@Qualifier(value = "cat")
private Cat cat;
@Resource
- @Resource如有指定的name屬性,先按該屬性進行byName方式查找裝配
- 其次再進行默認的byName方式進行裝配
- 如果以上都不成功,則按byType的方式自動裝配
- 都不成功,則報異常
實體類:
public class User {
@Resource(name = "cat2")
private Cat cat;
@Resource
private Dog dog;
private String str;
}
beans.xml
<bean id="dog" class="com.zr.pojo.Dog"/>
<bean id="cat1" class="com.zr.pojo.Cat"/>
<bean id="cat2" class="com.zr.pojo.Cat"/>
<bean id="user" class="com.zr.pojo.User"/>
測試:結果OK
配置文件2:beans.xml , 刪掉cat2
<bean id="dog" class="com.zr.pojo.Dog"/>
<bean id="cat1" class="com.zr.pojo.Cat"/>
實體類上只保留註解
@Resource
private Cat cat;
@Resource
private Dog dog;
結果:OK
結論:先進行byName查找,失敗;再進行byType查找,成功
7.3小結
@Autowired與@Resource異同:
1、@Autowired與@Resource都可以用來裝配bean
2、@Autowired默認按類型裝配(屬於spring規範),默認情況下必須要求依賴對象必須存在,如果要允許null 值,可以設置它的required屬性爲false,如:@Autowired(required=false) ,如果我們想使用名稱裝配可以結合@Qualifier註解進行使用
3、@Resource(屬於J2EE復返),默認按照名稱進行裝配,名稱可以通過name屬性進行指定。如果沒有指定name屬性,當註解寫在字段上時,默認取字段名進行按照名稱查找,如果註解寫在setter方法上默認取屬性名進行裝配。當找不到與名稱匹配的bean時才按照類型進行裝配。但是需要注意的是,如果name屬性一旦指定,就只會按照名稱進行裝配。
它們的作用相同都是用註解方式注入對象,但執行順序不同。@Autowired先byType,@Resource先byName
8.使用註解開發
8.1 Bean的實現
之前都是使用 bean 的標籤進行bean注入,但是實際開發中,一般都會使用註解
1、配置掃描哪些包下的註解
<!--指定註解掃描包-->
<context:component-scan base-package="com.zr"/>
2、在指定包下編寫類,增加註解
@Component("user")//不加("user"),id默認爲當前類的類名首字母小寫
// 相當於配置文件中 <bean id="user" class="當前註解的類"/>
public class User {
public String name = "zr";
}
3、測試
@Test
public void test(){
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user.name);
}
8.2 屬性注入
1、可以不用提供set方法,直接在直接名上添加@value(“值”)
@Component("user")
// 相當於配置文件中 <bean id="user" class="當前註解的類"/>
public class User {
@Value("zr")
// 相當於配置文件中 <property name="name" value="秦疆"/>
public String name;
}
2、如果提供了set方法,在set方法上添加@value(“值”);
@Component("user")
public class User {
public String name;
@Value("zr")
public void setName(String name) {
this.name = name;
}
}
8.3 衍生註解
@Component有三個衍生註解,在web開發中,會按照mvc三層架構分層
- @Controller:web層
- @Service:service層
- @Repository:dao層
這四個註解功能都是一樣的,都是代表將某個類註冊到Spring中,裝配bean
8.4 自動裝配註解
在Bean的自動裝配已經講過,可以回顧
8.5 作用域
@scope
- singleton:默認的,Spring會採用單例模式創建這個對象。關閉工廠 ,所有的對象都會銷燬
- prototype:多例模式 , 關閉工廠 ,所有的對象不會銷燬。內部的垃圾回收機制會回收
@Controller("user")
@Scope("prototype")
public class User {
@Value("zr")
public String name;
}
8.6 小結
XML與註解比較
- XML可以適用任何場景 ,結構清晰,維護方便
- 註解不是自己提供的類使用不了,開發簡單方便
xml與註解整合開發 :推薦最佳實踐
- xml管理Bean
- 註解完成屬性注入
9.annotation-config和component-scan區別
< context:annotation-config > 和 < context:component-scan >的區別:
-
< context:annotation-config > 是用於激活那些已經在spring容器裏註冊過的bean
(無論是通過xml的方式還是通過package sanning的方式)上面的註解
-
< context:component-scan >除了具有< context:annotation-config >的功能之外,
< context:component-scan >還可以在指定的package下掃描以及註冊javabean
有三個class A,B,C,並且B,C的對象被注入到A中
package com.zr.pojo;
public class B {
public B() {
System.out.println("creating bean B: " + this);
}
}
package com.zr.pojo;
public class C {
public C() {
System.out.println("creating bean C: " + this);
}
}
package com.zr.pojo1;
import com.zr.pojo.B;
import com.zr.pojo.C;
public class A {
private B bbb;
private C ccc;
public A() {
System.out.println("creating bean A: " + this);
}
public void setBbb(B bbb) {
System.out.println("setting A.bbb with " + bbb);
this.bbb = bbb;
}
public void setCcc(C ccc) {
System.out.println("setting A.ccc with " + ccc);
this.ccc = ccc;
}
}
在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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bBean" class="com.zr.pojo.B"/>
<bean id="cBean" class="com.zr.pojo.C"/>
<bean id="aBean" class="com.zr.pojo1.A">
<property name="bbb" ref="bBean"/>
<property name="ccc" ref="cBean"/>
</bean>
</beans>
加載applicationContext.xml配置文件,將得到下面的結果:
creating bean B: com.zr.pojo.B@6483f5ae
creating bean C: com.zr.pojo.C@282003e1
creating bean A: com.zr.pojo1.A@7fad8c79
setting A.bbb with com.zr.pojo.B@6483f5ae
setting A.ccc with com.zr.pojo.C@282003e1
完全通過xml的方式,太過時了
下面通過註解的方式來簡化我們的xml配置文件
首先,我們使用@Autowired的方式將對象bbb和ccc注入到A中
package com.zr.pojo1;
import com.zr.pojo.B;
import com.zr.pojo.C;
import org.springframework.beans.factory.annotation.Autowired;
public class A {
private B bbb;
private C ccc;
public A() {
System.out.println("creating bean A: " + this);
}
@Autowired
public void setBbb(B bbb) {
System.out.println("setting A.bbb with " + bbb);
this.bbb = bbb;
}
@Autowired
public void setCcc(C ccc) {
System.out.println("setting A.ccc with " + ccc);
this.ccc = ccc;
}
}
然後applicationContext.xml配置文件去除屬性< property >
<?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="bBean" class="com.zr.pojo.B"/>
<bean id="cBean" class="com.zr.pojo.C"/>
<bean id="aBean" class="com.zr.pojo1.A"/>
</beans>
加載applicationContext.xml配置文件之後,得到下面的結果
creating bean B: com.zr.pojo.B@768b970c
creating bean C: com.zr.pojo.C@290dbf45
creating bean A: com.zr.pojo.A@12028586
ClassA中顯然沒有注入屬性,結果是錯誤的的,究竟是因爲什麼呢?爲什麼屬性沒有被注入進去呢?
是因爲註解本身並不能夠做任何事情,它們只是最基本的組成部分
我們需要能夠處理這些註解的處理工具來處理這些註解
這就是< context:annotation-config > 所做的事情,用於激活那些已經在spring容器裏註冊過的bean的註解
將applicationContext.xml配置文件作如下修改
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean id="bBean" class="com.zr.pojo.B"/>
<bean id="cBean" class="com.zr.pojo.C"/>
<bean id="aBean" class="com.zr.pojo1.A"/>
</beans>
加載applicationContext.xml配置文件之後,將得到下面的結果:
creating bean B: com.zr.pojo.B@36c88a32
creating bean C: com.zr.pojo.C@1d119efb
creating bean A: com.zr.pojo.A@35047d03
setting A.ccc with com.zr.pojo.C@1d119efb
setting A.bbb with com.zr.pojo.B@36c88a32
OK, 結果正確了
但是如果我們將代碼作如下修改
package com.zr.pojo;
import org.springframework.stereotype.Component;
@Component
public class B {
public B() {
System.out.println("creating bean B: " + this);
}
}
package com.zr.pojo;
import org.springframework.stereotype.Component;
@Component
public class C {
public C() {
System.out.println("creating bean C: " + this);
}
}
package com.zr.pojo1;
import com.zr.pojo.B;
import com.zr.pojo.C;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class A {
private B bbb;
private C ccc;
public A() {
System.out.println("creating bean A: " + this);
}
@Autowired
public void setBbb(B bbb) {
System.out.println("setting A.bbb with " + bbb);
this.bbb = bbb;
}
@Autowired
public void setCcc(C ccc) {
System.out.println("setting A.ccc with " + ccc);
this.ccc = ccc;
}
}
applicationContext.xml配置文件修改爲:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
當我們加載applicationContext.xml配置文件之後,卻沒有任何輸出,這是爲什麼呢?
那是因爲<context:annotation-config />僅能夠在已經在已經註冊過的bean上面起作用
對於沒有在spring容器中註冊的bean,它並不能執行任何操作
但是不用擔心,< context:component-scan >除了具有<context:annotation-config />的功能之外,還具有自動將帶有@component,@service,@Repository等註解的對象註冊到spring容器中的功能
我們將applicationContext.xml配置文件作如下修改:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.zr.pojo"/>
<context:component-scan base-package="com.zr.pojo1"/>
</beans>
加載applicationContext.xml就會得到下面的結果:
creating bean B: com.zr.pojo.B@6b81ce95
creating bean C: com.zr.pojo.C@16293aa2
creating bean A: com.zr.pojo1.A@2d7275fc
setting A.bbb with com.zr.pojo.B@6b81ce95
setting A.ccc with com.zr.pojo.C@16293aa2
結果正確
回頭看下我們的applicationContext.xml文件,已經簡化爲兩行context:component-scan了,是不是很簡單?
那如果在applicationContext.xml手動加上下面的配置,也就是說
既在applicationContext.xml中手動的註冊了A的實例對象,
同時,通過component-scan去掃描並註冊B,C的對象,如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.zr.pojo"/>
<bean id="aBean" class="com.zr.pojo1.A"/>
</beans>
結果正確:
creating bean B: com.zr.pojo.B@6b81ce95
creating bean C: com.zr.pojo.C@16293aa2
creating bean A: com.zr.pojo1.A@2d7275fc
setting A.ccc with com.zr.pojo.C@16293aa2
setting A.bbb with com.zr.pojo.B@6b81ce95
雖然class A並不是通過掃描的方式註冊到容器中的 ,
但是< context:component-scan > 所產生的的處理那些註解的處理器工具,會處理所有綁定到容器上面的bean,不管是通過xml手動註冊的還是通過scanning掃描註冊的。
那麼,如果我們通過下面的方式呢?我們既配置了<context:annotation-config />,又配置了<context:component-scan base-package=“com.xxx” />,它們都具有處理在容器中註冊的bean裏面的註解的功能。會不會出現重複注入的情況呢?
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config />
<context:component-scan base-package="com.zr.pojo"/>
<bean id="aBean" class="com.zr.pojo1.A"/>
</beans>
結果如下,沒有出現:
creating bean B: com.zr.pojo.B@37afeb11
creating bean C: com.zr.pojo.C@2d7275fc
creating bean A: com.zr.pojo1.A@79e2c065
setting A.bbb with com.zr.pojo.B@37afeb11
setting A.ccc with com.zr.pojo.C@2d7275fc
因爲<context:annotation-config />和 < context:component-scan >同時存在的時候,前者會被忽略
也就是那些@autowire,@resource等注入註解只會被注入一次
哪怕是你手動的註冊了多個處理器,spring仍然只會處理一次:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config />
<context:component-scan base-package="com.zr.pojo"/>
<bean id="aBean" class="com.zr.pojo1.A"/>
<bean id="bla" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="bla1" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="bla2" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="bla3" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
</beans>
結果仍是正確的:
creating bean B: com.zr.pojo.B@515aebb0
creating bean C: com.zr.pojo.C@399f45b1
creating bean A: com.zr.pojo1.A@3a93b025
setting A.ccc with com.zr.pojo.C@399f45b1
setting A.bbb with com.zr.pojo.B@515aebb0
10.使用java的方式配置Spring
現在完全不使用Spring的xml配置了,全部交給java來做
JavaConfig 原來是 Spring 的一個子項目,它通過 Java 類的方式提供 Bean 的定義信息,在 Spring4 之後的版本, JavaConfig 已正式成爲 Spring4 的核心功能
實體類
@Component("user")//表示這個類被Spring接管了,註冊到容器中
public class User {
@Value("zr")//屬性注入
private String name;
public void setName(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
配置類
//@Configuration本身也是一個@Component
//@Configuration代表這是一個配置類,和applicationContext.xml一樣
@Configuration
@ComponentScan("com.zr.pojo")
@Import(XXX.class)//和xml裏的Import標籤作用一樣
public class MyConfig {
//註冊一個bean,相當於之前的bean標籤
//這個方法的名字,相當於bean標籤的id屬性
//這個方法的返回值,相當於bean標籤的class屬性
@Bean
public User user(){
return new User();//返回要注入到bean的對象
}
}
測試類
public class Test {
public static void main(String[] args) {
//如果完全使用了配置類去做,我們就只能通過AnnotationConfig 上下文來獲取容器,通過配置類的class對象加載
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
User user = context.getBean("user", User.class);
System.out.println(user.getName());
}
}
11.代理模式
爲什麼學習代理模式?因爲這就是SpringAOP的底層
代理模式的分類:
- 靜態代理
- 動態代理
11.1 靜態代理
靜態代理角色分析
- 抽象角色 : 一般使用接口或者抽象類來實現
- 真實角色 : 被代理的角色
- 代理角色 : 代理真實角色 , 代理真實角色後 , 一般會做一些附屬的操作
- 客戶 : 使用代理角色來進行一些操作
代碼步驟
1.抽象角色
//出租房子的接口
public interface Rent {
void rentHouse();
}
2.真實角色
//房東
public class Landlord implements Rent {
@Override
public void rentHouse() {
System.out.println("鄭房東要出租房子");
}
}
3.代理角色
public class Proxy implements Rent {
private Landlord landlord;//組合大於繼承
public Proxy(Landlord landlord){
this.landlord = landlord;
}
@Override
public void rentHouse() {
lookHouse();
landlord.rentHouse();
agencyFees();
signContract();
}
public void lookHouse(){
System.out.println("帶你看房子");
}
public void agencyFees(){
System.out.println("收取中介費");
}
public void signContract(){
System.out.println("籤合同");
}
}
4.客戶訪問代理角色
public class Client {
public static void main(String[] args) {
Proxy proxy = new Proxy(new Landlord());
proxy.rentHouse();
}
}
在這個過程中,你直接接觸的就是中介,就如同現實生活中的樣子,
你看不到房東,但是你依舊通過代理租到了房東的房子,這就是所謂的代理模式
11.2 靜態代理再理解
練習步驟:
1、創建一個抽象角色
//抽象角色:增刪改查業務
public interface UserService {
void add();
void delete();
void update();
void query();
}
2、需要一個真實對象來完成這些增刪改查操作
//真實對象,完成增刪改查操作的人
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("增加了一個用戶");
}
public void delete() {
System.out.println("刪除了一個用戶");java
}
public void update() {
System.out.println("更新了一個用戶");
}
public void query() {
System.out.println("查詢了一個用戶");
}
}
3、需求來了,現在需要增加一個日誌功能,怎麼實現
- 思路1 :在實現類上增加代碼 【麻煩】
- 思路2:使用代理來做 , 能夠不改變原來的業務情況下,實現此功能
4、設置一個代理類來處理日誌,代理角色
//代理角色,在這裏面增加日誌的實現
public class UserServiceProxy implements UserService {
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
public void add() {
log("add");
userService.add();
}
public void delete() {
log("delete");
userService.delete();
}
public void update() {
log("update");
userService.update();
}
public void query() {
log("query");
userService.query();
}
public void log(String msg){
System.out.println("執行了"+msg+"方法");
}
}
5、測試訪問類
public class Client {
public static void main(String[] args) {
//真實業務
UserServiceImpl userService = new UserServiceImpl();
//代理類
UserServiceProxy proxy = new UserServiceProxy();
//使用代理類實現日誌功能
proxy.setUserService(userService);
proxy.add();
}
}
我們在不改變原來的代碼的情況下,實現了對原有功能的增強,這是AOP中最核心的思想
靜態代理的好處:
- 可以使得我們的真實角色更加純粹 . 不再去關注一些公共的事情
- 公共的業務由代理來完成 . 實現了業務的分工
- 公共業務發生擴展時變得更加集中和方便
靜態代理的缺點:
- 一個被代理對象就會產生一個代理角色,代碼量繁瑣,開發效率降低
我們想要靜態代理的好處,又不想要靜態代理的缺點,所以 , 就有了動態代理
11.3 動態代理
-
動態代理的角色和靜態代理的一樣
-
動態代理的代理類是動態生成的,靜態代理的代理類是我們提前寫好的
-
動態代理分爲兩類 : 一類是基於接口動態代理 , 一類是基於類的動態代理
-
- 基於接口的動態代理 : JDK動態代理
- 基於類的動態代理 : cglib
- java字節碼實現 : javasist
JDK的動態代理需要了解兩個類
InvocationHandler 和 Proxy
- 創建一個計算器接口 Cal,定義4個方法
package com.zr.pojo;
public interface Cal {
public int add(int num1,int num2);
public int sub(int num1,int num2);
public int mul(int num1,int num2);
public int div(int num1,int num2);
}
- 創建接口的實現類 CalImpl
package com.zr.pojo;
public class CalImpl implements Cal {
public int add(int num1, int num2) {
System.out.println("add方法的參數是["+num1+","+num2+"]");
int result = num1+num2;
System.out.println("add方法的結果是"+result);
return result;
}
public int sub(int num1, int num2) {
System.out.println("sub方法的參數是["+num1+","+num2+"]");
int result = num1-num2;
System.out.println("sub方法的結果是"+result);
return result;
}
public int mul(int num1, int num2) {
System.out.println("mul方法的參數是["+num1+","+num2+"]");
int result = num1*num2;
System.out.println("mul方法的結果是"+result);
return result;
}
public int div(int num1, int num2) {
System.out.println("div方法的參數是["+num1+","+num2+"]");
int result = num1/num2;
System.out.println("div方法的結果是"+result);
return result;
}
}
上述代碼中,日誌信息和業務邏輯的耦合性很高,不利於系統的維護,使用 AOP 可以進行優化,如何來實現 AOP?使用動態代理的方式來實現
給業務代碼找一個代理,打印日誌信息的工作交個代理來做,這樣的話業務代碼就只需要關注自身的業務即可
package com.zr.pojo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class MyInvocationHandler implements InvocationHandler {
//接收委託對象
private Object object = null;
//返回代理對象
public Object getProxy(Object object){
this.object = object;
return Proxy.newProxyInstance(object.getClass().getClassLoader(),
object.getClass().getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
System.out.println(method.getName()+"方法的參數是:"+ Arrays.toString(args));
Object result = method.invoke(this.object,args);
System.out.println(method.getName()+"的結果是"+result);
return result;
}
}
動態代理的好處
- 可以使得我們的真實角色更加純粹 . 不再去關注一些公共的事情
- 公共的業務由代理來完成 . 實現了業務的分工
- 公共業務發生擴展時變得更加集中和方便
- 一個動態代理 , 一般代理某一類業務
- 一個動態代理可以代理多個類,代理的是接口
以上是通過動態代理實現 AOP 的過程,比較複雜,不好理解,Spring 框架對 AOP 進行了封裝,使用 Spring 框架可以用面向對象的思想來實現 AOP
Spring 框架中不需要創建 InvocationHandler,只需要創建一個切面對象,將所有的非業務代碼在切面對象中完成即可,Spring 框架底層會自動根據切面類以及目標類生成一個代理對象
12.AOP
12.1 什麼是AOP
AOP(Aspect Oriented Programming)意爲:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率
Aop在Spring中的作用
提供聲明式事務,允許用戶自定義切面
以下名詞需要了解下:
- 橫切關注點:跨越應用程序多個模塊的方法或功能。即是,與我們業務邏輯無關的,但是我們需要關注的部分,就是橫切關注點。如日誌 , 安全 , 緩存 , 事務等等 …
- 切面(ASPECT):橫切關注點 被模塊化 的特殊對象。即,它是一個類
- 通知(Advice):切面必須要完成的工作。即,它是類中的一個方法
- 目標(Target):被通知對象
- 代理(Proxy):向目標對象應用通知之後創建的對象
- 切入點(PointCut):切面通知 執行的 “地點”的定義
- 連接點(JointPoint):與切入點匹配的執行點
使用Spring實現Aop
【重點】使用AOP織入,需要導入一個依賴包
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
12.2AspectJ的切入點表達式
AspectJ定義了專門的表達式用於指定切入點,表達式原型是:
execution( [modifiers-pattern] 訪問權限類型
ret-type-pattern 返回值類型
[declaring-type-pattern] 全限定性類名
name-pattern(param-pattern) 方法名(參數類型和參數個數)
[throws-pattern] 拋出異常類型
)
表達式中加[]的部分表示可省略部分,各部分間用空格分開
public void com.zr.pojo.Landlord.rentHouse(String name,int id){}
execution(public * *(…)) 指定切入點爲:任意公共方法
execution(* set*(…)) 指定切入點爲:任何一個以"set"開始的方法
execution(* com.zr.pojo.* . *(…)) 指定切入點爲:定義在pojo包裏的任意類的任意方法
execution(* com.zr.pojo…* . *(…)) 指定切入點爲:定義在pojo包或者子包裏的任意類的任意方法
execution(* *…pojo. *. * (…)) 指定切入點爲:所有包下的pojo子包下所有類中的所有方法
12.3 通過 Spring API 接口實現
第一種方式:
1編寫業務接口和實現類
package com.zr.service;
public interface UserService {
void add();
void delete();
void update();
void select();
}
package com.zr.service.imp;
import com.zr.service.UserService;
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("增加用戶");
}
@Override
public void delete() {
System.out.println("刪除用戶");
}
@Override
public void update() {
System.out.println("更新用戶");
}
@Override
public void select() {
System.out.println("查詢用戶");
}
}
2寫增強類 ,一個前置增強 ,一個後置增強
package com.zr.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class BeforeLog implements MethodBeforeAdvice {
//method:要執行的目標對象的方法
//args:被調用的方法的參數
//target:目標對象
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"被執行了");
}
}
package com.zr.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
@Override
//returnValue:返回值
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("執行了" + method.getName() + "方法,返回結果爲" + returnValue);
}
}
3spring的文件中註冊 , 並實現aop切入實現 , 注意導入aop約束
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 註冊bean-->
<bean id="userService" class="com.zr.service.imp.UserServiceImpl"></bean>
<bean id="before" class="com.zr.log.BeforeLog"></bean>
<bean id="after" class="com.zr.log.AfterLog"></bean>
<!-- 方式一:使用原生SpringAOP接口-->
<!-- 配置AOP,需要導入AOP的約束-->
<aop:config>
<!-- 切入點 expression表達式 execution(要執行的位置)-->
<aop:pointcut id="pointcut" expression="execution(* com.zr.service.imp.UserServiceImpl.*(..))"/>
<!-- 執行環繞增加 advice-ref執行方法 pointcut-ref切入點-->
<aop:advisor advice-ref="before" pointcut-ref="pointcut"></aop:advisor>
<aop:advisor advice-ref="after" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
</beans>
4測試
import com.zr.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext.xml");
//動態代理代理的是一個接口
UserService userService = context.getBean("userService",UserService.class);
userService.delete();
}
}
12.4 自定義類來實現AOP
第二種方式:
目標業務類不變依舊是userServiceImpl
1寫自己的一個切入類
@Component
public class Diy {
public void before(){
System.out.println("開始");
}
public void after(){
System.out.println("結束");
}
}
2去spring中配置
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.test"></context:component-scan>
<aop:config>
<aop:aspect ref="diy">
<aop:pointcut id="1" expression="execution(* com.test.service.imp.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="1"></aop:before>
<aop:after-returning method="before" pointcut-ref="1"></aop:after-returning>
</aop:aspect>
</aop:config>
3測試
public class MyTest {
public static void main(String[] args) {
ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext.xml");
//動態代理代理的是一個接口
UserService userService = context.getBean("userService",UserService.class);
userService.delete();
}
}
12.4 使用註解實現
第三種方式:
1編寫一個註解實現的增強類
package com.zr.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AnnotationPointcut {
@Before("execution(* com.zr.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("---------方法執行前---------");
}
@After("execution(* com.zr.serviczre.UserServiceImpl.*(..))")
public void after(){
System.out.println("---------方法執行後---------");
}
@Around("execution(* com.zr.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("環繞前");
System.out.println("簽名:"+jp.getSignature());
//執行目標方法proceed
Object proceed = jp.proceed();
System.out.println("環繞後");
System.out.println(proceed);
}
}
2在Spring配置文件中,註冊bean,並增加支持註解的配置
<!--第三種方式:註解實現-->
<bean id="annotationPointcut" class="com.zr.config.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>
aop:aspectj-autoproxy:說明
通過aop命名空間的<aop:aspectj-autoproxy />聲明自動爲spring容器中那些配置@aspectJ切面的bean創建代理,織入切面。當然,spring 在內部依舊採用AnnotationAwareAspectJAutoProxyCreator進行自動代理的創建工作,但具體實現的細節已經被<aop:aspectj-autoproxy />隱藏起來了
<aop:aspectj-autoproxy />有一個proxy-target-class屬性,默認爲false,表示使用jdk動態代理織入增強,當配爲<aop:aspectj-autoproxy poxy-target-class=“true”/>時,表示使用CGLib動態代理技術織入增強。不過即使proxy-target-class設置爲false,如果目標類沒有聲明接口,則spring將自動使用CGLib動態代理
13.整合mybatis
步驟
1、導入相關jar包
<dependencies>
<!-- junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!-- mysql-connector-java-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!-- mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!-- spring相關-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!-- aspectJ AOP 織入器-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<!-- mybatis-spring整合包 【重點】-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
<!-- 快速生成setget...-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
</dependencies>
2、編寫配置文件
spring-dao.xml
整合mybatis配置文件
<?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="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/zr/dao/UserMapper.xml"/>
<!-- 如果想使用通配符,classpath後面需要加上*-->
<!-- <property name="mapperLocations" value="classpath*:com/zr/dao/*.xml"/>-->
</bean>
<!--配置數據源:數據源有非常多,可以使用第三方的,也可使用Spring-jdbc的數據源-->
<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="Aa766267"/>
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!-- 只能使用構造器注入sqlSessionFactory,因爲它沒有set方法-->
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
</beans>
applicationContext.xml
管理所有的bean
<?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">
<import resource="spring-dao.xml"/>
<!-- 第一種方法-->
<bean id="userMapper" class="com.zr.dao.imp.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"></property>
</bean>
<!-- 第二種方法-->
<bean id="userMapper2" class="com.zr.dao.imp.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
</beans>
mybatis-config.xml
可以保留用來管理settings設置和typeAliases別名
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="com.zr.pojo"/>
</typeAliases>
</configuration>
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zr.dao.UserMapper">
<select id="getAllUser" resultType="user">
select * from user
</select>
</mapper>
3、代碼實現
package com.zr.pojo;
import lombok.Data;
@Data
public class User {
private int id;
private String name;
private String password;
}
package com.zr.dao;
import com.zr.pojo.User;
import java.util.List;
public interface UserMapper {
List<User> getAllUser();
}
第一種:
package com.zr.dao.imp;
import com.zr.dao.UserMapper;
import com.zr.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;
public class UserMapperImpl implements UserMapper {
//sqlSession不用我們自己創建了,Spring來管理
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<User> getAllUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.getAllUser();
}
}
第二種:
package com.zr.dao.imp;
import com.zr.dao.UserMapper;
import com.zr.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
@Override
public List<User> getAllUser() {
return getSqlSession().getMapper(UserMapper.class).getAllUser();
}
}
4.測試
package com.zr.dao;
import com.zr.dao.imp.UserMapperImpl;
import com.zr.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
import java.util.List;
public class MyTest {
public static void main(String[] args) throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapperImpl userMapper = context.getBean("userMapper", UserMapperImpl.class);
List<User> allUser = userMapper.getAllUser();
for (User user : allUser) {
System.out.println(allUser);
}
}
}
14.聲明式事務
回顧事務
- 事務在項目開發過程非常重要,涉及到數據的一致性的問題,不容馬虎
- 事務管理是企業級應用程序開發中必備技術,用來確保數據的完整性和一致性
- 把一組業務當初一個業務來做,要麼都成功,要麼都失敗
事務ACID
-
原子性(atomicity)
事務是原子性操作,由一系列動作組成,事務的原子性確保動作要麼全部完成,要麼完全不起作用
-
一致性(consistency)
一旦所有事務動作完成,事務就要被提交。數據和資源處於一種滿足業務規則的一致性狀態中
-
隔離性(isolation)
可能多個事務會同時處理相同的數據,因此每個事務都應該與其他事務隔離開來,防止數據損壞
-
持久性(durability)
事務一旦完成,無論系統發生什麼錯誤,結果都不會受到影響 , 通常情況下,事務的結果被寫到持久化存儲器中
測試
將上面的代碼拷貝到一個新項目中
在之前的案例中,我們給userDao接口新增兩個方法,刪除和增加用戶
//添加一個用戶
int addUser(User user);
//根據id刪除用戶
int deleteUser(int id);
mapper文件,我們故意把 deletes 寫錯,測試
<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
<delete id="deleteUser" parameterType="int">
deletes from user where id = #{id}
</delete>
編寫接口的實現類
public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {
//增加一些操作
public List<User> selectUser() {
User user = new User(4,"小明","123456");
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
mapper.addUser(user);
mapper.deleteUser(4);
return mapper.selectUser();
}
//新增
public int addUser(User user) {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.addUser(user);
}
//刪除
public int deleteUser(int id) {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.deleteUser(id);
}
}
測試
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserMapper mapper = (UserMapper) context.getBean("userDao");
List<User> user = mapper.selectUser();
System.out.println(user);
}
報錯:sql異常,delete寫錯了
結果 :插入成功
沒有進行事務的管理 , 想讓它們都成功才成功,有一個失敗,就都失敗,我們就應該需要事務
以前我們都需要自己手動管理事務,十分麻煩 , 配置即可
Spring中的事務管理
Spring在不同的事務管理API之上定義了一個抽象層,使得開發人員不必瞭解底層的事務管理API就可以使用Spring的事務管理機制,Spring支持編程式事務管理和聲明式的事務管理
編程式事務管理
- 將事務管理代碼嵌到業務方法中來控制事務的提交和回滾
- 缺點:必須在每個事務操作業務邏輯中包含額外的事務管理代碼
聲明式事務管理
- 一般情況下比編程式事務好用
- 將事務管理代碼從業務方法中分離出來,以聲明的方式來實現事務管理
- 將事務管理作爲橫切關注點,通過aop方法模塊化 , Spring中通過Spring AOP框架支持聲明式事務管理
使用Spring管理事務,注意頭文件的約束導入 : tx
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
事務管理器
- 無論使用Spring的哪種事務管理策略(編程式或者聲明式)事務管理器都是必須的
- 就是 Spring的核心事務管理抽象,管理封裝了一組獨立於技術的方法
JDBC事務
<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
配置好事務管理器後我們需要去配置事務的通知
<!--配置事務通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--配置哪些方法使用什麼樣的事務,配置事務的傳播特性-->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="search*" propagation="REQUIRED"/>
<tx:method name="get" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
spring事務傳播特性:
事務傳播行爲就是多個事務方法相互調用時,事務如何在這些方法間傳播。spring支持7種事務傳播行爲:
- propagation_requierd:如果當前沒有事務,就新建一個事務,如果已存在一個事務中,加入到這個事務中,這是最常見的選擇
- propagation_supports:支持當前事務,如果沒有當前事務,就以非事務方法執行
- propagation_mandatory:使用當前事務,如果沒有當前事務,就拋出異常
- propagation_required_new:新建事務,如果當前存在事務,把當前事務掛起
- propagation_not_supported:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起
- propagation_never:以非事務方式執行操作,如果當前事務存在則拋出異常
- propagation_nested:如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與propagation_required類似的操作
Spring 默認的事務傳播行爲是 PROPAGATION_REQUIRED,它適合於絕大多數的情況
假設 ServiveX#methodX() 都工作在事務環境下(即都被 Spring 事務增強了),假設程序中存在如下的調用鏈:Service1#method1()->Service2#method2()->Service3#method3(),那麼這 3 個服務類的 3 個方法通過 Spring 的事務傳播機制都工作在同一個事務中
就好比,我們剛纔的幾個方法存在調用,所以會被放在一組事務當中
配置AOP
導入aop的頭文件
<!--配置aop織入事務-->
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.zr.dao.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
進行測試
刪掉剛纔插入的數據,再次測試
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserMapper mapper = (UserMapper) context.getBean("userDao");
List<User> user = mapper.selectUser();
System.out.println(user);
}
思考問題?
爲什麼需要配置事務?
- 如果不配置,就需要我們手動提交控制事務
- 事務在項目開發過程非常重要,涉及到數據的一致性的問題,不容馬虎