1. 什么是框架
框架在項(xiàng)目中的表現(xiàn)就是一系列的jar包,例如Thymeleaf就是一個(gè)框架。
每種框架都會(huì)解決某種特定的問題,可能是開發(fā)效率的問題,或運(yùn)行效率的問題,或代碼管理維護(hù)的問題等等。
項(xiàng)目中使用框架就相當(dāng)于得到了一個(gè)“毛坯房”,使用了框架之后,開發(fā)人員只需要關(guān)心后續(xù)的“裝修”即可,。
絕大部分的框架都有特定的使用方式,在使用時(shí),必須遵循框架的使用規(guī)則!
每個(gè)框架都可能是若干個(gè)開發(fā)人員甚至開發(fā)團(tuán)隊(duì)多年的工作積累的作品,對于初學(xué)者來說,不要過于鉆牛角尖,嘗試?yán)斫饪蚣艿牡讓訉?shí)現(xiàn)原理!
簡單的說:使用框架,可以讓編程變得更加簡單!在學(xué)習(xí)框架時(shí),主要學(xué)習(xí)框架的正確使用方式!
2. 依賴關(guān)系
假設(shè)在項(xiàng)目中需要開發(fā)一個(gè)用戶注冊的功能!在項(xiàng)目中可能存在:
public class UserRegServlet {
private UserDao userDao = new UserDao ( ) ;
public void doPost ( ) {
userDao. reg ( ) ; // 調(diào)用userDao對象實(shí)現(xiàn)存儲(chǔ)用戶數(shù)據(jù)
}
}
public class UserDao {
public void reg ( ) {
// 通過JDBC技術(shù)將用戶數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)庫中
}
}
在以上代碼中,UserRegServlet
就是依賴 于UserDao
的!
3. 耦合度
如果某個(gè)類過于依賴于另外一個(gè)類,通常稱之為了“耦合度較高”,是不利于代碼的管理和維護(hù)的,簡單的說,如果UserRegServlet
依賴于UserDao
,在未來的某一天,UserDao
已經(jīng)不能滿足項(xiàng)目的需求了(可能是因?yàn)榇a有Bug,或者使用的技術(shù)比較落后等),如果需要把UserDao
替換掉,替換難度大就會(huì)影響項(xiàng)目的管理和維護(hù),為了解決這樣的問題采取的解決方案就稱之為“解耦”,使得依賴關(guān)系不那么明確,甚至就是不明確!
就以上UserRegServlet
依賴UserDao
的問題,如果要解耦,可以先創(chuàng)建一個(gè)接口:
public interface IUserDao {
void reg ( ) ;
}
然后,使得UserDao
是實(shí)現(xiàn)了以上接口的:
public class UserDao implements IUserDao {
public void reg ( ) {
// 具體的實(shí)現(xiàn)了reg()方法應(yīng)該實(shí)現(xiàn)的功能
}
}
經(jīng)過以上調(diào)整以后,如果在UserRegServlet
中需要使用到UserDao
,以前的代碼是這樣的:
private UserDao userDao = new UserDao ( ) ;
現(xiàn)在就可以改為:
private IUserDao userDao = new UserDao ( ) ;
以上代碼就相當(dāng)于:
private List< String> strings = new ArrayList < > ( ) ;
改成這樣以后,在同一個(gè)項(xiàng)目中,無論多少個(gè)Servlet
組件需要使用到UserDao
,都可以使用以上“聲明為接口,創(chuàng)建實(shí)現(xiàn)類的對象”的語法風(fēng)格,如果以后UserDao
需要被替換掉,也只需要替換“賦值”的代碼,聲明部分是不需要替換的!例如需要把UserDao
替換為UserMybatisDao
時(shí),原來的代碼是:
private IUserDao userDao = new UserDao ( ) ;
新的代碼就可以是:
public class UserMybatisDao implements IUserDao {
public void reg ( ) {
// 使用更好的方式實(shí)現(xiàn)reg()應(yīng)該實(shí)現(xiàn)的功能
}
}
在后續(xù)的使用中,就可以是:
private IUserDao userDao = new UserMybatisDao ( ) ;
也就是說,在UserDao
換成了UserMybatisDao
時(shí),在各個(gè)Servlet
中,都只需要調(diào)整等于號(hào)右側(cè)的內(nèi)容,而不再需要修改等于號(hào)左側(cè)的部分!
當(dāng)然,關(guān)于以上代碼的右側(cè)部分,還可以使用“工廠設(shè)計(jì)模式 ”作進(jìn)一步的處理:
public class UserDaoFactory {
// 返回接口類型的對象
public static IUserDao newInstance ( ) {
return new UserDao ( ) ; // 也可以返回UserMybatisDao的對象
}
}
當(dāng)有了工廠后,此前的代碼就可以進(jìn)一步調(diào)整為:
private IUserDao userDao = UserDaoFactory. newInstance ( ) ;
可以發(fā)現(xiàn),以上代碼中不再出現(xiàn)任何一個(gè)實(shí)現(xiàn)類的名字了,無論是哪個(gè)Servlet
組件需要訪問數(shù)據(jù)庫,都聲明為以上代碼即可,以后,如果實(shí)現(xiàn)類需要被替換,也只需要替換工廠方法的返回值即可!
在實(shí)際項(xiàng)目開發(fā)時(shí),項(xiàng)目中的組件的依賴更加復(fù)雜,為每個(gè)組件都創(chuàng)建對應(yīng)的接口及工廠是非常麻煩的,而Spring框架就很好的解決了這個(gè)問題,可以簡單的將Spring理解為一個(gè)“萬能工廠”,當(dāng)使用了Spring框架后,就不必自行開發(fā)工廠了!
4. Spring框架簡介
Spring框架的主要作用:解決了創(chuàng)建對象和管理對象的問題,。
5. 通過Spring創(chuàng)建對象
創(chuàng)建Maven Project ,在創(chuàng)建過程中,勾選Create a simple project ,Group Id 填為cn.tedu
,Artifact Id 填為spring01
,其它項(xiàng)保持默認(rèn)即可,。
使用Spring框架時(shí),必須在項(xiàng)目的pom.xml 中添加spring-context
的依賴:
<!-- https:///artifact/org.springframework/spring-context -->
< dependency>
< groupId> org.springframework</ groupId>
< artifactId> spring-context</ artifactId>
< version> 5.2.5.RELEASE</ version>
</ dependency>
首先,在項(xiàng)目中,創(chuàng)建cn.tedu.spring
包,并在這個(gè)包下創(chuàng)建BeanFactory
類:
package cn. tedu. spring;
public class BeanFactory {
}
當(dāng)前,代碼放在哪個(gè)包中并不重要,應(yīng)該養(yǎng)成習(xí)慣,每個(gè)類都應(yīng)該放在某個(gè)包中,不要讓任何類不放在任何包中!
以上類的名稱也不重要,是自定義的!
如果希望由Spring來創(chuàng)建并管理某個(gè)類的對象,必須在以上類中添加方法,關(guān)于這個(gè)方法:
應(yīng)該使用public
權(quán)限; 返回值類型就是需要Spring創(chuàng)建并管理的類的對象的類型; 方法名稱可以自定義; 參數(shù)列表暫時(shí)為空; 在方法體中,自行編寫創(chuàng)建返回值對象的代碼,。
假設(shè)需要Spring來創(chuàng)建Date
類型的對象,則在類中添加方法:
public Date aaa ( ) {
// 規(guī)范,規(guī)則
}
Spring框架要求:創(chuàng)建對象的方法必須添加@Bean
注解,并且,這樣的方法必須在配置類 中!任何一個(gè)類添加了@Configuration
注解都可以作為配置類!
package cn. tedu. spring;
@Configuration
public class BeanFactory {
@Bean
public Date aaa ( ) {
return new Date ( ) ;
}
}
完成后,應(yīng)該使用一個(gè)可以運(yùn)行的類,或通過單元測試來檢驗(yàn)“是否可以通過Spring容器獲取對象”,。本次先創(chuàng)建一個(gè)可以運(yùn)行的類:
package cn. tedu. spring;
public class SpringTests {
public static void main ( String[ ] args) {
// 1. 加載配置類,得到Spring容器
AnnotationConfigApplicationContext ac
= new AnnotationConfigApplicationContext ( BeanFactory. class ) ;
// 2. 從Spring容器中獲取所需要的對象
Date date = ( Date) ac. getBean ( "aaa" ) ; // getBean()方法的參數(shù)就是創(chuàng)建對象的方法的名稱
// 3. 測試獲取到的對象
System. out. println ( date) ;
// 4. 關(guān)閉
ac. close ( ) ;
}
}
6. 關(guān)于@Bean注解
當(dāng)方法的聲明之前添加了@Bean
注解,就表示這個(gè)方法是需要由Spring框架所調(diào)用,并且,由Spring框架管理該方法返回的對象的!默認(rèn)情況下,該方法的名稱就是后續(xù)獲取對象時(shí),調(diào)用getBean()
方法的參數(shù)!
由于添加了@Bean
注解的方法是被Spring框架調(diào)用的,不需要自行編寫代碼來調(diào)用這個(gè)方法,所以,Spring的建議是“使用合理的屬性名稱作為方法名,并不需要使用動(dòng)詞或動(dòng)詞為前綴的方法名”,簡單的說,如果方法是為了獲取Date
類型的對象的,該方法的名稱應(yīng)該是date
,而不是getDate()
,則后續(xù)調(diào)用getBean()
時(shí),參數(shù)就是date
這個(gè)名稱!
當(dāng)然,如果不遵循Spring的建議,還可以在@Bean
注解中配置注解參數(shù)來指定Bean的名稱,例如:
@Bean ( "date" )
public Date getDate ( ) {
return new Date ( ) ;
}
則后續(xù)就根據(jù)注解參數(shù)來獲取對象:
Date date = ( Date) ac. getBean ( "date" ) ;
其關(guān)系如下圖:
其實(shí),在開發(fā)項(xiàng)目時(shí),真的不必關(guān)心這些問題,也就是說,例如是一個(gè)獲取Date
對象的方法,其名稱到底是date
還是getDate
都是正確的!畢竟這個(gè)方法最終就是由Spring框架來調(diào)用,開發(fā)人員不會(huì)自行調(diào)用該方法!
7. Spring管理對象的作用域
由Spring管理的對象,默認(rèn)情況下,是單例 的!所以,其作用域就非常久!
在Spring管理對象的情況下,討論對象的作用域,其本質(zhì)就是討論其是否單例!
在創(chuàng)建對象的方法之前,添加@Scope
注解,并配置注解參數(shù)為prototype
,就可以使得該對象不是單例 的:
@Scope ( "prototype" )
@Bean
public User user ( ) {
return new User ( ) ;
}
由Spring管理的對象,如果是單例模式的,默認(rèn)情況下,是餓漢式 的!在創(chuàng)建對象的方法之前,添加@Lazy
注解,就可以調(diào)整為懶漢式 的:
@Bean
@Lazy
public User user ( ) {
return new User ( ) ;
}
一般,在開發(fā)項(xiàng)目時(shí),極少調(diào)整對象的作用域!
8. 當(dāng)天小結(jié):
Spring的主要作用:創(chuàng)建對象,管理對象; 如果某個(gè)方法是用于給Spring框架創(chuàng)建對象的,這個(gè)方法就必須添加@Bean
注解; 所有添加了@Bean
注解的方法,其所在的類應(yīng)該添加@Configuration
注解,凡添加了@Configuration
注解的類稱之為配置類 ; 默認(rèn)情況下,由Spring管理的對象是單例的,使用@Scope
注解可以將Spring管理的對象調(diào)整為“非單例”的; 默認(rèn)情況下,由Spring管理的單例的對象是是“餓漢式”的,使用@Lazy
可以將它們改為“懶漢式”的,。
附1:設(shè)計(jì)模式之單例模式
單例模式的特點(diǎn):在同一時(shí)期,某個(gè)類的對象一定最多只有1個(gè)!也許會(huì)嘗試多次的獲取對象,但是,獲取到的一定是同一個(gè)對象!
假設(shè)項(xiàng)目中有King
類:
public class King {
}
很顯然,目前它并不是單例的,因?yàn)?#xff0c;可以:
King k1 = new King ( ) ;
King k2 = new King ( ) ;
King k3 = new King ( ) ;
以上代碼就創(chuàng)建了3個(gè)King
類型的對象!如果要實(shí)現(xiàn)單例,首先,就必須限制構(gòu)造方法的訪問,例如:
public class King {
private King ( ) {
}
}
每個(gè)類中都可以有若干個(gè)構(gòu)造方法,如果某個(gè)類沒有顯式的聲明任何構(gòu)造方法,編譯器就會(huì)自動(dòng)添加1個(gè)公有的,、無參數(shù)的構(gòu)造方法!如果類中已經(jīng)聲明任何構(gòu)造方法,則編譯器不會(huì)自動(dòng)添加構(gòu)造方法!
由于將構(gòu)造方法聲明為私有的,則原有的King k1 = new King();
這類代碼就不能用于創(chuàng)建對象了!
限制構(gòu)造方法的訪問,其目的是“不允許隨意創(chuàng)建對象”,并不是“不允許創(chuàng)建對象”,在King
類的內(nèi)部,還是可以創(chuàng)建對象的,可以添加方法,返回內(nèi)部創(chuàng)建的對象:
public class King {
private King king = new King ( ) ;
private King ( ) {
}
public King getInstance ( ) {
return king;
}
}
所以,當(dāng)需要King
類型的對象時(shí),可以通過getInstance()
方法來獲取!
但是,以上代碼是不可行的!因?yàn)?#xff0c;如果要調(diào)用getInstance()
方法,必須先獲取King
的對象,而獲取King
對象的唯一方式就是調(diào)用getInstance()
方法!為了解決這個(gè)問題,必須在getInstance()
方法的聲明之前添加static
修飾符,最終,就可以通過類名.方法名()
的語法格式來調(diào)用方法了!同時(shí),由于“被static
修飾的成員,不可以訪問其它未被static
修飾的成員”,所以,全局屬性king
也必須被static
修飾:
public class King {
private static King king = new King ( ) ;
private King ( ) {
}
public static King getInstance ( ) {
return king;
}
}
至此,基本的單例模式的代碼就設(shè)計(jì)完成了!
以上代碼是“餓漢式 ”的單例模式,另外,還有“懶漢式 ”的單例模式!
基本的懶漢式單例模式的代碼是:
public class King {
private static King king = null;
private King ( ) {
}
public static King getInstance ( ) {
if ( king == null) {
king = new King ( ) ;
}
return king;
}
}
注意:以上代碼是多線程不安全的!
在開發(fā)領(lǐng)域中,只要數(shù)據(jù)的產(chǎn)生、變化不是開發(fā)人員預(yù)期的,就稱之為“不安全”,也就是“數(shù)據(jù)安全問題”,。
為了保障線程安全,應(yīng)該為以上創(chuàng)建對象的代碼片斷“加鎖”,例如:
public class King {
private static King king = null;
private King ( ) {
}
public static King getInstance ( ) {
synchronized ( "hello" ) {
if ( king == null) {
king = new King ( ) ;
}
}
return king;
}
}
當(dāng)然,無論是哪個(gè)線程在什么時(shí)候執(zhí)行以上代碼,都必須先“鎖住”代碼片斷后才能開始執(zhí)行,是沒有必要的,“鎖”的性能消耗是浪費(fèi)的,所以,可以進(jìn)一步調(diào)整為:
public class King {
private static King king = null;
private King ( ) {
}
public static King getInstance ( ) {
if ( king == null) { // 判斷有沒有必要鎖定接下來的代碼
synchronized ( "java" ) {
if ( king == null) { // 判斷有沒有必要?jiǎng)?chuàng)建對象
king = new King ( ) ;
}
}
}
return king;
}
}
至此,懶漢式的單例模式就完成了!