1、Spring概述
Spring 是分層的 Java SE/EE 應用 full-stack 輕量級開源框架,以 IoC(Inverse Of Control: 反轉控制)和 AOP(Aspect Oriented Programming:面向切面編程)為內核,提供了展現層 Spring MVC 和持久層。Spring JDBC 以及業務層事務管理等眾多的企業級應用技術,還能整合開源世界眾多 著名的第三方框架和類庫,逐漸成為使用最多的 Java EE 企業應用開源框架
2、spring發展歷程
1997 年 IBM 提出了 EJB 的思想
1998 年,SUN 制定開發標準規范 EJB1.0
1999 年,EJB1.1 發布
2001 年,EJB2.0 發布
2003 年,EJB2.1 發布
2006 年,EJB3.0 發布
Rod Johnson(spring之父) ,Expert One-to-One J2EE Design and Development(2002) 闡述了 J2EE 使用 EJB 開發設計的優點及解決方案。Expert One-to-One J2EE Development without EJB(2004) 。闡述了 J2EE 開發不使用 EJB 的解決方式(Spring 雛形) ,2017年9月份發布了spring的最新版本spring 5.0通用版(GA)
3、spring的優勢
(1)、方便解耦,簡化開發:IOC通過 Spring 提供的 IoC容器,可以將對象間的依賴關系交由 Spring進行控制,避免硬編碼所造 成的過度程序耦合。用戶也不必再為單例模式類、屬性文件解析等這 些很底層的需求編寫代碼,可以更專注于上層的應用。(2)、AOP編程的支持通過 Spring 的 AOP 功能,方便進行面向切面的編程,許多不容易用傳統 OOP實現的功能可以。通過 AOP輕松應付。(3)、聲明式事務的支持可以將我們從單調煩悶的事務管理代碼中解脫出來,通過聲明式方式靈活的進行事務的管理, 提高開發效率和質量。(4)、方便程序的測試可以用非容器依賴的編程方式進行幾乎所有的測試工作,測試不再是昂貴的操作,而是隨手可 做的事情。(5)、方便集成各種優秀框架Spring 可以降低各種框架的使用難度,提供了對各種優秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持(6)、降低 JavaEE API 的使用難度Spring 對 JavaEE API(如 JDBC、JavaMail、遠程調用等)進行了薄薄的封裝層,使這些 API 的 使用難度大為降低。(7)、Java 源碼是經典學習范例Spring 的源代碼設計精妙、結構清晰、匠心獨用,處處體現著大師對 Java設計模式靈活運用以 及對 Java技術的高深造詣。它的源代碼無疑是 Java技術的最佳實踐的范例。
4、spring體系結構
二、Spring入門案例のHelloWorld
和大多數框架一樣,使用第三方的框架,基本步驟:
1.引入框架使用需要的依賴******
2.配置文件(格式大多數xml格式或yaml、properties)
3.spring提供的API完成對象的獲取和實例化
案例代碼
-
pom.xml導入spring依賴
<dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>5.3.28</version> </dependency> <dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>5.3.28</version> </dependency> <dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.28</version> </dependency>
-
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:p="http://www.springframework.org/schema/p"xmlns:c="http://www.springframework.org/schema/c"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttps://www.springframework.org/schema/tx/spring-tx.xsd"><!-- spring配置文件中配置對象的基本信息 --><bean id="myUserDaoImpl1" class="com.woniu.dao.impl.UserDaoImpl" scope="prototype"></bean> </beans>
-
創建Person類型,利用spring獲取Person對象
通過案例代碼的運行,我們會發現,不用寫new UserDaoImpl()也能得到UserDaoImpl對象。解決了傳統自己new對象的“硬編碼”問題。
思考1:Spring中獲取的對象是單例的嗎?
案例分析
-
UserDaoImpl.java
-
public class UserDaoImpl implements UserDao {public UserDaoImpl() {System.out.println("UserDaoImpl構造器被調用");}@Overridepublic int insert() {System.out.println("鏈接mysql數據庫,完成用戶信息");return 0;}
}
-
測試類
-
@Test public void mt01(){ApplicationContext factory=new ClassPathXmlApplicationContext("ApplicationContext.xml");//獲取特定的對象 spring容器裝的對象,默認:單例的!!!Object obj1 = factory.getBean("myUserDaoImpl1");Object obj2 = factory.getBean("myUserDaoImpl1");System.out.println("obj1內存地址:"+obj1);System.out.println("obj2內存地址:"+obj2);System.out.println(obj1==obj2);
}
-
測試結果
觀察測試代碼運行結果,可以得出結論:默認情況下,所有由Spring構建的對象都是單例對象。
思考2:如何從Spring容器得到一個多例對象呢?
我們可以通過設置bean標簽的scope屬性來改變對象在Spring容器中的創建方式:
<bean id="userDao" class="cn.woniu.dao.UserDao" scope="prototype"></bean>
- 作用:
用于注冊對象信息,讓 spring 來創建。
默認情況下 它調用的是類中的無參構造函數。如果沒有無參構造函數則不能創建成功。 - 屬性:
id:給對象在容器中提供一個唯一標識。用于獲取對象。
class:指定類的全限定類名。用于反射創建對象。默認情況下調用無參構造函數。
scope:指定對象的作用范圍。
1、singleton :默認值,單例的.
2、prototype :多例的.
3、request :WEB 項目中,Spring 創建一個 Bean 的對象,將對象存入到 request 域中
4、session :WEB 項目中,Spring 創建一個 Bean 的對象,將對象存入到 session 域中.
5、global session :WEB 項目中,應用在 Portlet 環境 .如果沒有 Portlet 環境那么 globalSession 相當于 session.
三、解讀Helloworld案例代碼
1、ApplicationContext是什么?
我們可以把ApplicationContext理解成是一個裝載對象的“大容器”,其本質就是一個對象工廠。借助IDEA快捷鍵Alt+Ctrl+U可以看到ApplicationContext實現過的所有父接口:
2、BeanFactory和ApplicationContext的區別【常見面試題】
ApplicationContext底層就是對象工廠(即BeanFactory)。Spring提供兩種創建對象工廠的方式(BeanFactory和ApplicationContext兩個接口)。這種方式創建對象工廠時有什么區別呢?
案例演示ApplicationContext
-
在UserDaoImpl類中添加無參構造方法:代碼略
-
測試方法
//1.創建spring ioc容器 ApplicationContext可以理解成是Spring容器,本質其實就是BeanFactory,職責就是負責獲取對象
//ApplicationContext:工廠建好,立馬將需要工廠管理對象也一起建好
ApplicationContext cnt=new ClassPathXmlApplicationContext(“spring-config.xml”); -
結果如下:
UserDaoImpl對象的構造器被調用,意味著UserDaoImpl對象在創建ApplicationContext時就被創建
案例演示BeanFactory
-
修改測試方法
//1.創建spring ioc容器 ApplicationContext可以理解成是Spring容器,本質其實就是BeanFactory,職責就是負責獲取對象
//BeanFactory延遲加載(也稱為懶加載)創建工廠對象時,不會幫我們把工廠內部對象創建
Resource resource=new ClassPathResource(“spring-config.xml”);
BeanFactory cnt=new XmlBeanFactory(resource); -
結果如下:
控制臺什么也沒輸出,也就是UserDaoImpl對象的構造器沒有調用,意味著UserDaoImpl對象在創建BeanFactory時沒有被創建
通過以上案例對比,我們可以得出以下結論:
BeanFactory:SpringIoc容器基本實現,是Spring內部的使用接口,不提供開發人員進行使用。
BeanFactory對象工廠實現的特點是:
構建核心容器時,創建對象采取的策略是延遲加載的方式,什么時候調用getBean根據id獲取對象了,什么時候才真正創建對象。適用于多例模式
ApplicationContext:ApplicationContext是BeanFactory接口的子接口,提供更多更強大的功能,一般由開發人員進行使用。
ApplicationContext實現的特點是:
在構建核心容器時,創建對象采取的策略是立即加載的方式,只要一讀取完配置文件就馬上創建配置文件中的配置對象。適用于單例模式
3、ApplicationContext的三個實現類【理解】
在入門Helloworld案例中,我們實例化spring的工廠對象時,使用的是ClassPathXmlApplicationContext對象,其實在ApplicationContext的繼承體系中,除了ClassPathXmlApplicationContext這個實現類以外,還有另外兩個子類也可以完成ApplicationContext對象工廠的實例化。
提示:idea中通過選中類,通過ctrl+H可以查看向下派生的繼承體現,通過ctrl+alt+u可以查看該類向上的繼承體系結構。
在ApplicationContext對象上按ctrl+h查看該類的結構
ClassPathXmlApplicationContext:加載類路徑下的配置文件,要求配置文件必須在類路徑下(常用)*****
FileSystemXmlApplicationContext:加載磁盤任意路徑下的配置文件(必須有訪問權限)
AnnotationConfigApplicationContext:讀取注解配置容器
4、bean 的作用范圍和生命周期 【面試題】
1、單例對象:scope="singleton"一個應用只有一個對象的實例。它的作用范圍就是整個應用。生命周期:對象出生:當應用加載,創建容器時,對象就被創建了。對象活著:只要容器在,對象一直活著。 對象死亡:當應用卸載,銷毀容器時,對象就被銷毀了。2、多例對象:scope="prototype"每次訪問對象時,都會重新創建對象實例。生命周期:對象出生:當使用對象時,創建新的對象實例。不使用就不創建對象活著:只要對象在使用中,就一直活著。對象死亡:當對象長時間不用時,被 java 的垃圾回收器回收了。
案例演示:bean的生命 周期
創建StudentService類,在該類中添加:一個構造方法、一個init方法、一個destory方法public class StudentService {//構造方法public StudentService(){System.out.println("創建對象實例");}//初始化方法public void init(){System.out.println("對象被創建了");}//銷毀public void destory(){System.out.println("對象被銷毀了");}
}
配置spring-config.xml
<!-- 實例化StudentService --><bean id = "studentService"class="cn.woniu.service.StudentService"scope="singleton"init-method="init"destroy-method="destory"></bean>
創建測試方法
我們發現,在===========上面時,對象就已經被創建了,這是單例對象的特點,立即加載,而且執行了studnetService類的init方法,但是我們并沒有看到執行destory方法。原因很簡單:當測試方法執行完時,線程結束,此時容器銷毀,容器銷毀意味著創建的單例對象也要銷毀,只是此時沒來得及打印。要想看到效果,我們需要手動讓容器銷毀,調用容器的close方法,此時對象就會銷毀,即執行destory方法
如果我們把配置文件的對象改成多例模式呢?其它的代碼不變
<!-- 實例化StudentService --><bean id = "studentService"class="cn.woniu.service.StudentService"scope="prototype"init-method="init"destroy-method="destory"></bean>
改為多例模式后發現對象是懶加載的方式,即在用到的時候才會創建,而且我們手動關閉容器的時候也不會調用destory方法,原因很簡單,多例對象的死亡是由java垃圾回收器回收的,不受容器管理。
四 IOC
IOC容器負責bean管理。什么是bean管理?從兩個方面來理解:
- Spring容器負責為java項目創建對象(即IOC)
- Spring容器負責為創建的對象注入屬性值(即DI)
Bean管理操作的方式有:
- 基于xml配置文件方式實現
- 基于注解方式實現
- 通過JavaConfig配置bean
IOC概念
IOC: Inversion Of Control 控制反轉。
以前:java程序使用對象:開發人員 new 對象 正向控制
spring:java程序使用對象:找Spring的ApplicationContext對象拿對象
翻譯:new對象稱為開發人員對對象的控制,將以前程序員自己new對象“權利” 交個Spring的spring容器統一管理的現象就是控制的權利反轉了。所以,在spring中IOC的主要作用就是利用spring容器完成不同類對象的創建【實體類不會通過spring的ioc完成創建】進而解決程序中new對象時硬編碼的問題。
IOC只解決程序間的依賴關系,除此之外沒有任何功能
如何使用ioc完成對象創建呢?通常步驟有:
1.配置文件利用<bean></bean>配置你要spring容器管理的對象
2.獲取spring工廠對象【或spring容器】
3.調用getBean根據id獲取對象即可。
五 DI
DI:Dependency Injection 依賴注入
翻譯:IOC只管對象創建,DI負責對象屬性賦值
DI常用方式
- setter注入方式
- 構造器注入方式
setter注入方式
Set注入就是在類中提供需要注入成員的 setter方法,通過調用setter完成屬性賦值
案例
Person.java代碼
@ToString
public class Person {private Integer id;//1private String name;//張三豐public Person() {System.out.println("Person的構造器被調用了");}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
構造器注入
注入屬性值的對象提供了帶參構造器,而且還保持類有無參構造器
案例
Person.java添加構造器
public class Person {private Integer id;//1private String name;//張三豐public Person(Integer id, String name) {this.id = id;this.name = name;}public Person() {}
}
使用c命名空間和p命名空間簡化構造器賦值和setter賦值【了解】
使用步驟:
- xml文件根標簽中引入p和c命名空間
- 使用c:和p:作為前綴實現賦值
不同數據類型的屬性注入
Spring不管entity類型,主要對三層架構對象進行ioc和di管理
8種基本類型及對應的包裝類及String類型
賦值方式都是通過value屬性賦值
使用setter賦值,完整語法
<property name="屬性名" value="屬性值"/>
使用構造器賦值,完整語法
<constructor-arg name/index/type="形參名" value="屬性值"/>
自定義對象類型
使用setter賦值,完整語法
<property name="屬性名" ref="引用的對象ID"/>
使用構造器賦值,完整語法
<constructor-arg name/index/type="形參名" ref="引用的對象ID"/>
集合和數組類型【了解】
常見類型:數組、List集合、Set集合、Map集合、Properties集合
利用c命名空間和p命名空間簡化屬性賦值【了解】
c命名空間簡化構造器注入方式,舉例:
p命名空間簡化setter注入方式
使用步驟:
- xml文件根標簽中引入p和c命名空間
- 使用c:和p:作為前綴實現賦值
六 DI自動裝配機制【理解】
自動裝配是根據自動的裝配規則(byName屬性名稱或byType屬性類型),Spring自動將匹配的屬性值進行注入的方式。自動裝配是spring DI的一種方式。
在Spring中有三種自動裝配的方式:
- 在xml中顯式配置【理解】
- 在javal類中顯示配置【Spring5+的新特性】【重點】
- 隱式的自動裝配【注解方式,重點】
利用XML配置完成自動裝配【理解】
自動裝配常見異常
使用byType進行裝配時,如果一個類型可以找到多個Bean對象,就會出現以下異常:
解決方案:利用byName進行裝配
七 spring提供注解完成IOC和DI功能【實際開發都是注解完成ioc和di 重點】
IOC注解
@Component,@Repository,@Service,@Controller標記在類上,實現這個類由spring容器負責對象管理
作用類似xml配置
@Component:標注三層架構以外的類,比如全局異常、工具類、redis或minio工具類
spring為@Component衍生了三個注解,三個注解作用跟@Component一模一樣,但是從詞意來看,閱讀性比@Component更好
@Repository:一般用在持久層
@Service:一般用在業務層
@Controller:一般用在控制器層
DI注解
@Value
完成8種基本類型和String類型的屬性值注入。@Value的作用等價于以下代碼:
<property name="" value='屬性值'/>
或
<constructor-arg name/index/type="" value="屬性值"/>
@Autowired
完成自定義類型屬性注入。@Autowired的作用等價于以下代碼:
<bean autowired="byName/byType"/>
@Autowired默認先根據byType,如果byType裝配失敗,退而其次,使用byName再裝配,如果byName也失敗,直接拋出異常
跟@Autowired注解一樣,也可以為引用類型的對象屬性注入值的的注解還有以下兩個:
@Resource
@Resource注解與@Autowired注解的作用是一樣的,都是用來為bean對象注入值的,唯一的區別是@Resource注解要根據實例化的bean名稱為bean對象注入值。@Resource是javax包下注解,不是spring官方注解。
@Resource(value=“byName屬性名”)
@Quanifier
byName注入,是spring提供的byName注解,它在給字段注入時不能獨立使用,必須和@Autowire 一起使用,表示在自動按照類型注入的基礎之上,再按照 Bean 的id 注入;@Quanifier也給方法形參注入,注入時可以獨立使用。
語法:
@Quanifier(value=“byName的名稱”)
小結注解開發
spring提供的注解,依據注解作用和使用位置不同,劃分為三類:
1.注解在類上,作用IOC創建對象的作用:@Component @Repository @Service @Controller2.注解在屬性上,作用DI屬性注入值的作用:@Resource:byName@Quanifier:默認byName,但是不能獨立使用在屬性上,必須@Autowired結合使用@AutoWired:默認先byType,byType失敗,再byName@Value:注入八種基本類型和String類型3.注解在類上,設置對象單例模式還是多例模式,設置對象使用范圍:[了解]@Scope(設置對象使用范圍:propotype singleton session request)
使用案例
-
UserDaoImpl.java
//這個類是否會被別的層的類使用,如果會,這個類交給spring容器管理
@Repository
public class UserDaoImpl implements UserDao {
@Override
public int insert() {
System.out.println(“連接mysql數據庫,執行insert操作…”);
return 0;
}
} -
UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
//DI注入對象,注入方式
@Autowired
private UserDao userDao;
@Override
public void add() {
userDao.insert();
}
} -
UserController.java
/**
- 注解開發里面,如果使用setter注入方式,類中不需要提供setter都可以注入成功
*/
@Controller
public class UserController {
@Autowired
private UserService userService;
public void service(){
userService.add();
}
}
- 注解開發里面,如果使用setter注入方式,類中不需要提供setter都可以注入成功
-
單元測試
@Test
public void service() {
ApplicationContext cnt=new ClassPathXmlApplicationContext(“spring-config.xml”);
UserController controller = cnt.getBean(“userController”, UserController.class);
controller.service();}
當我們再為UserDao接口提供一個新的實現類,代碼如下所示:
-
UserDaoImpl2.java
@Repository
public class UserDaoImpl2 implements UserDao {
public UserDaoImpl2() {
System.out.println(“UserDaoImpl2”);
}@Overridepublic int insert() {System.out.println("連接oracle數據庫,執行insert操作...");return 0;}
}
重新執行單元測試,會發現控制臺爆出異常:
如何解決呢?可以借助@Resource或@Qualifier指定注入對象的名稱,代碼如下所示:
-
UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Resource(name = “userDaoImpl2”)//name屬性設置注入對象的屬性名,如果屬性名和name注入的名稱一致的,name可以不賦值
private UserDao userDao;
@Override
public void add() {
userDao.insert();
}
}
或
@Service
public class UserServiceImpl implements UserService {@Autowired@Qualifier("userDaoImpl") //默認byName,但是注入屬性時不能獨立使用,必須和@Autowired一起結合使用private UserDao userDao;@Overridepublic void add() {userDao.insert();}
}
如果,我們希望UserDaoImpl2是一個多例對象,也可以在類上通過@Scope注解注解對象的使用范圍:
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Repository
public class UserDaoImpl2 implements UserDao {public UserDaoImpl2() {System.out.println("UserDaoImpl2");}@Overridepublic int insert() {System.out.println("連接oracle數據庫,執行insert操作...");return 0;}
}
測試多例模式
@Testpublic void test02() {ApplicationContext cnt=new ClassPathXmlApplicationContext("spring-config.xml");Object o1 = cnt.getBean("userDaoImpl2");Object o2 = cnt.getBean("userDaoImpl2");Object o3 = cnt.getBean("userDaoImpl2");}
控制臺輸出結果如下圖所示:
擴展了解:程序的耦合和解耦
1、藕合和解藕的思路
藕合就是程序間的依賴關系。解藕就是降低程序間的依賴關系。在開發過程中應做到編譯期不依賴,運行時再依賴解藕思路:使用反射來創建對象,而不使用new關鍵字通過讀取配置文件來獲取要創建的對象全限定名
2、曾經代碼的問題
創建java控制臺程序
創建Dao類
創建service并調用Dao
創建測試類并調用service
層與層之間的關系通過new來實現調用,并且new對象時需要導入該對象所在的正確的包名,否則報錯。這種關系稱為藕合關系。
3、工廠類和配置文件解決藕合問題
a、創建properties屬性文件
b、創建工廠類
package cn.woniu.utils;import java.io.InputStream;
import java.util.Properties;/*** 對象工廠*/
public class BeanFactory {//定義一個Properties對象private static Properties properties;//使用靜態代碼塊為Properties對象賦值static {try {properties = new Properties();//獲取Properties文件流對象InputStream input = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");properties.load(input);} catch (Exception e) {throw new ExceptionInInitializerError("初始化Properties屬性出錯"+e.getMessage());}}/*** 根據Bean名稱獲取Bean對象** @param beanName*/public static Object getBean(String beanName) {Object bean = null;try {String beanPath = properties.getProperty(beanName);bean = Class.forName(beanPath).newInstance();} catch (Exception e) {e.printStackTrace();}return bean;}
}
c、使用工廠類調用各層
service調用dao
測試調用service