目錄
1 loC控制反轉思想
2 DI依賴注入
3 loC詳解
3.1 存儲Bean
(1)@Controller
(2)@Service
(3)@Repository
(4)@Component
(5)@Configuration
(6)@Bean
(7)Bean對象命名規范
(8)5大類注解之間的關系
3.2 獲取Bean
(1)ApplicationContext
(2)三種常用的獲取方式
(3)獲取多個@Bean注解的Bean
(4)Bean重命名
3.3 Spring掃描路徑
(1)默認路徑
(2)@ComponentScan
3.4 ApplicationContext和BeanFactory區別
4 DI詳解
4.1 屬性注入
4.2 構造方法注入
4.3 Setter方法注入
4.4 三種方式優缺點
(1)屬性注入
(2)構造方法注入
(3)Setter方法注入
4.5 @Autowired原理
(1)@Autowired原理與問題
(2)解決方法
(3)@Autowired與@Resource區別
????????Spring家族包含:Spring Framework、SpringMVC、SpringBoot、Spring Cloud等等框架,而我們常說的Spring框架就是指Spring Framework。
????????Spring框架(Spring Framework)的推出是為了簡化Java程序的開發(快速、簡單、安全)。它是包含眾多工具方法的loC(控制反轉)容器。即Spring是一個容器,其內部裝了很多的對象,這個容器蘊含了loC思想。
1 loC控制反轉思想
????????建房子首先需要用各種建材蓋出毛坯房,然后再對毛坯房進行裝修,得到成品房,成品房再交付到客戶手中。用面向對象的程序設計思想就需要三個類:Ston類表示用建材蓋毛坯房、Decoration類表示對毛坯房進行裝修、House類表示把成品房交付給客戶。
// 建房子用的建材public class Stone {public Stone() {System.out.println("使用建材數量: " + 20 + " 建成毛坯房");}}// 毛坯房裝修public class Decoration {private Stone stone;public Decoration(){stone = new Stone();System.out.println("對毛坯房進行裝修");}}//成品房交付public class House {private Decoration decoration;public House(){decoration = new Decoration();System.out.println("成功交付房子");}}
????????在這個流程中,House類的實例需要Decoration類的實例,Decoration類的實例需要Stone類的實例。我們稱這種關系為:House實例依賴Decoration實例,Decoration實例依賴Stone實例。
????????如果現在想要修改Stone類,比如添加一些屬性、修改一些方法的參數時,情況就如下述:
public class Stone {private int num;public String name;public Stone(int num,String name) {this.num = num;this.name = name;System.out.println("使用建材數量: " + this.num + " 建成毛坯房: " + this.name);}}public class Decoration {private Stone stone;public String name;public Decoration(int num,String name){stone = new Stone(num,name);this.name = name;System.out.println("對毛坯房:" + this.name + "進行裝修");}}public class House {private Decoration decoration;public House(int num,String name){decoration = new Decoration(num,name);System.out.println("成功交付房子:" + decoration.name);}}
????????可以發現上述方案有一個很大的問題:調用鏈最底層的代碼需要修改,整個調用鏈的類都需要進行修改,程序耦合度很高。
????????如果改變實例創建順序,讓每個實例的創建都不由其他類控制,而通過依賴對象注入的方式,即把實例傳遞給每個類的構造方法,這樣每個類的修改就不需要修改其他類的代碼了,實現了對象的解耦。
public class Stone {private int num;public String name;public Stone(int num,String name) {this.num = num;this.name = name;System.out.println("使用建材數量: " + this.num + " 建成毛坯房: " + this.name);}}public class Decoration {private Stone stone;public String name;public Decoration(Stone stone){name = stone.name;System.out.println("對毛坯房:" + name + "進行裝修");}}public class House {private Decoration decoration;public House(Decoration decoration){System.out.println("成功交付房子:" + decoration.name);}}public class Test {public static void main(String[] args) {Stone stone = new Stone(20,"花園3單元301");Decoration decoration = new Decoration(stone);House house = new House(decoration);}}
????????原先對象的創建順序是:House對象=>Decoration對象=>Stone對象,對象的創建依賴其他對象。而采用依賴對象注入的方式后,對象的創建順序就變為:Stone對象=>Decoration對象=>House對象,對象的創建不由其他對象負責,而采用注入的方式,即每個對象的創建控制權發生了反轉。這就是loC控制反轉的思想。
????????而Spring是loC容器,loC容器體現在上述Test類的代碼,即由Spring負責創建對象并管理對象等資源。因此loC容器具有以下優點:
????????1.資源集中管理:loC容器幫助我們管理一些資源,包括對象等。需要使用這些資源時,直接從容器取。
????????2.耦合度降低:降低了資源之間的依賴程度,最典型的就是上述對象間的創建不再依賴其他對象的實現細節,某個對象修改時,由于是依賴對象注入的方式,不需要修改其他對象。
2 DI依賴注入
????????DI依賴注入:容器運行期間,動態地為應用程序提供依賴的資源的過程,就是依賴注入。
????????DI和loC是從兩個角度描述同一件事,loC是思想,DI是其具體的實現,loC強調要將對象等資源交給容器來管理,那么該如何管理呢?DI就是其實現管理的方式,即依賴注入,需要時把對象從容器中取出來注入到需要使用的地方。
????????Spring把對象稱為Bean,Bean存放在loC容器中,因此loC容器主要提供兩個功能:存和取。存就是loC容器體現的方面,取就是DI體現的方面。
3 loC詳解
????????如何把Bean存儲到loC容器中,這就牽扯到5大類注解:@Controller、@Service、@Repository、@Component、@Configuration,1個方法注解:@Bean。
3.1 存儲Bean
(1)@Controller
@Controllerpublic class UserController {public void hello(){System.out.println("hello userController");}}
????????直接在要交給Spring管理的對象的類前加上@Controller,其他的5大注解類似。Spring在啟動時就會掃描該路徑并創建對象放到loC容器中。
(2)@Service
@Servicepublic class UserService {public void hello(){System.out.println("hello userService");}}
(3)@Repository
@Data@Repositorypublic class UserInfo {private Integer id;private String name;private Integer age;public UserInfo() {}public UserInfo(Integer id, String name, Integer age) {this.id = id;this.name = name;this.age = age;}}
(4)@Component
@Componentpublic class UserCompeonent {public void hello(){System.out.println("hello userCompeonent");}}
(5)@Configuration
@Configurationpublic class UserConfiguration {public void hello(){System.out.println("hello userConfiguration");}}
(6)@Bean
@Componentpublic class UserCompeonent {public void hello(){System.out.println("hello userCompeonent");}@Beanpublic UserInfo getUser1(){UserInfo user1 = new UserInfo(100,"zhangsan",20);return user1;}@Beanpublic UserInfo getUser2(){UserInfo user2 = new UserInfo(101,"lisi",21);return user2;}}
????????@Bean注解必須搭配類注解使用,如果要定義多個對象(類是同一個,但對象不是同一個),就需要使用多次@Bean注解。
????????為什么要用到@Bean注解?上述類注解存在兩個問題:1.如果外部類的對象我們也想交給Spring來管理,那么沒有辦法在外部類加類注解(總不能改別人的源代碼吧)2.如果一個類需要注冊多個對象,多個對象的屬性的值不同,通過類注解沒有辦法實現(類注解使用了單例模式,對象全局只有一個,這點在后面獲取Bean會得到印證)。而@Bean注解就可以解決上述問題。
(7)Bean對象命名規范
????????在java.beans.Introspector類中的decapitalize定義了Bean的命名規范:
????public static String decapitalize(String name) {if (name == null || name.length() == 0) {return name;}if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&Character.isUpperCase(name.charAt(0))){return name;}char[] chars = name.toCharArray();chars[0] = Character.toLowerCase(chars[0]);return new String(chars);}
????????默認如果類的名稱前兩個字母是大寫的,則Bean對象名稱和類名一致;如果類的名稱不符合這種特殊情況,則首字母小寫(小駝峰)。而方法注解@Bean所創建的對象的名稱,就是方法名。
(8)5大類注解之間的關系
????????觀察@Controller、@Service、@Repository、@Configuration四個注解的源碼發現,都含有@Component注解,說明那4個注解是@Component注解的衍生類(子類),而@Component是元注解。
????????那有了@Component就可以把對象創建出來交給Spring管理,為什么還需要其他注解?這是開發模式的需要:常用的開發模式是后端分為Controller層、Service層和Dao層,Controller層用于處理請求和響應,Service層用于業務邏輯,Dao層用于和數據庫交互。對Controller層使用@Controller,對Service層使用@Service,對Dao層使用@Repository,便于項目結構的清晰,這已經成為了一種開發規范。
????????注意:@RestController也具有創建Bean對象并存儲到loC容器中的功能,這是由于@RestController=@Controller+@ResponseBody。但是如果只對Controller層使用@Component注解和@ResponseBody,就會導致請求url分級的一些問題。
3.2 獲取Bean
(1)ApplicationContext
????????因為Bean對象都交給Spring管理了,所以獲取Bean就需要用到Spring的上下文(它可以看做一個容器,存儲了Spring運行時需要用到的環境和對象)。
????????而在啟動類(@SpringBootApplication)中,SpringApplication.run()方法會返回一個對象,類型是ConfigurableApplicationContext,它的父類是ApplicationContext,我們獲取Bean就是通過該對象獲取的。
????????同時,也可以直接在在其他類中通過DI(依賴注入)注入ApplicationContext對象,使用@Autowired注解注入ApplicationContext對象。這里的注解在后面會講。
(2)三種常用的獲取方式
@SpringBootApplicationpublic class SpringmvcApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringmvcApplication.class, args);// 獲取Bean對象方式1:通過類型獲取UserController userController1 = context.getBean(UserController.class);userController1.hello();System.out.println(userController1);// 獲取Bean對象方式2:通過對象名稱獲取UserController userController2 = (UserController) context.getBean("userController");userController2.hello();System.out.println(userController2);// 獲取Bean對象方式3:通過類型+對象名稱獲取UserController userController3 = ?context.getBean("userController", UserController.class);userController3.hello();System.out.println(userController3);}}
????????觀察結果可以發現,我們使用三種方式獲得的Bean對象是同一個,這說明UserController的對象全局中只有一個,這是由于Spring對于Bean的創建遵循單例模式。
????????方式1不適合獲取多個Bean對象,而方式2和3都適合。
(3)獲取多個@Bean注解的Bean
@SpringBootApplicationpublic class SpringmvcApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringmvcApplication.class, args);UserInfo user1 = ?context.getBean("getUser1", UserInfo.class);UserInfo user2 = ?context.getBean("getUser2", UserInfo.class);System.out.println(user1);System.out.println(user2);}}
????????注意到,獲取@Bean注解的對象,需要使用帶對象名稱的方式(2或3),而對象的名稱就是方法名。
(4)Bean重命名
????????要重命名時,直接在注解中填入要重命名的名稱(類注解和方法注解都適用)。比如:
@Controller("UserController")public class UserController?{public void hello(){System.out.println("hello userController");}}@SpringBootApplicationpublic class SpringmvcApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringmvcApplication.class, args);// 默認名稱是userController(類的首字母小寫), 重命名為UserControllerUserController?userController1?= (UserController) context.getBean("UserController");userController1.hello();System.out.println(userController1);}}
????????注意:Java對類名的大小寫敏感,不允許通過首字母大寫和小寫區分不同的類,比如UserController和userController,不能表示為兩個不同的類,必須是同一個類,這也是命名規范。
3.3 Spring掃描路徑
(1)默認路徑
????????Spring默認的掃描路徑是啟動類所在的路徑,在該同級目錄下,Spring啟動時都可以掃描到其中的注解,然后依次創建對象,存儲在loC容器中。
(2)@ComponentScan
????????如果想要Spring也掃描啟動類所在路徑外的路徑,就需要使用@ComponentScan(“要掃描的路徑(包的全名)”),將該注解放在啟動類前(@SpringBootApplication所在的位置)。如果要額外掃描多個路徑,就用{}把所有的路徑都括起來。
@SpringBootApplication包含@ComponentScan,其默認掃描路徑就是啟動類所在的路徑。
3.4 ApplicationContext和BeanFactory區別
????????注意到,getBean()方法實際上是BeanFactory接口提供的方法,而ApplicationContext接口繼承了ListableBeanFactory和HierarchicalBeanFactory,這兩個接口的父類就是BeanFactory。因此:
????????1.從繼承角度來講,BeanFactory提供了基礎的訪問容器的能力,而ApplicationContext屬于BeanFactory的子類,它除了繼承了BeanFactory的所有功能之外,還擁有獨特的特性,還添加了對國際化支持、資源訪問支持、以及事件傳播等方面的支持。
????????2.從性能角度來講,ApplicationContext是一次性加載并初始化所有的Bean對象(空間換時間)。而BeanFactory是需要哪個才去加載哪個,更加輕量。
4 DI詳解
????????DI是依賴注入,體現在把loC容器中管理的對象動態的注入到需要的地方。有3種注入的方式:屬性注入、構造方法注入和Setter方法注入。
4.1 屬性注入
@Controllerpublic class UserController {@Autowiredpublic UserService userService;public void hello(){System.out.println("hello userController");userService.hello();}}@SpringBootApplicationpublic class SpringmvcApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringmvcApplication.class, args);// 依賴注入1UserController userController1 = context.getBean(UserController.class);userController1.hello();}}
????????屬性注入是通過在需要注入對象的屬性前加上@Autowired注解,運行結果可以看到,成功的把UserService的對象注入到UserController中。
4.2 構造方法注入
????????如果只有一個構造方法,只使用構造方法即可注入:
@Controllerpublic class UserController {public UserService userService;public UserController(UserService userService) {this.userService = userService;}public void hello(){System.out.println("hello userController");userService.hello();}}
????????如果有多個構造方法,就必須使用@Autowired注解+構造方法指明應該使用哪個構造方法來注入屬性:
@Controllerpublic class UserController {public UserService userService;public UserConfiguration userConfiguration;public UserController(UserService userService) {this.userService = userService;}@Autowiredpublic UserController(UserService userService, UserConfiguration userConfiguration) {this.userService = userService;this.userConfiguration = userConfiguration;}public void hello(){System.out.println("hello userController");userService.hello();userConfiguration.hello();}}
4.3 Setter方法注入
@Controllerpublic class UserController {public UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}public void hello(){System.out.println("hello userController");userService.hello();}}
????????Setter方法注入也需要搭配上@Autowired注解。
4.4 三種方式優缺點
(1)屬性注入
????????優點:使用簡單。
????????缺點:1.只能用于loC容器(Spring框架),不能用在非loC容器。2.并且只有使用時才能知道Bean是否為Null。3.不能注入final類型的屬性,因為final類型的屬性需要聲明時就初始化/聲明時不初始化但是需要在構造方法初始化(那還不如用構造方法注入)。
(2)構造方法注入
????????優點:1.可以注入final類型的屬性。2.一旦注入對象后不會被修改。3.依賴的對象在使用前就可以被初始化完成,因為依賴注入是在構造方法中完成的,而構造方法在類加載時期就會執行。4.通用性好,構造方法屬于JDK的,更換其他框架也適用。
????????缺點:注入多個對象,代碼會繁瑣,因為可能需要寫多種構造方法。
(3)Setter方法注入
????????優點:方便在類實例后,重新對對象進行配置或注入。
????????缺點:1.不能注入final類型的屬性。2.注入的對象有被修改的風險,因為Setter方法在運行期間可以被多次調用。
4.5 @Autowired原理
(1)@Autowired原理與問題
????????@Autowired的原理是先根據對象類型獲取對象,如果存在一個就注入。如果匹配到多個,再根據對象名稱獲取對象,此時如果獲取到對象就注入,否則就報錯有多個同一類型的類型。
????????因此,如果通過對象類型獲取對象的時候,同一個類型匹配到多個Bean,就會報錯。
@Componentpublic class UserCompeonent {public void hello(){System.out.println("hello userCompeonent");}@Beanpublic UserInfo getUser1(){UserInfo user1 = new UserInfo(100,"zhangsan",20);return user1;}@Beanpublic UserInfo getUser2(){UserInfo user2 = new UserInfo(101,"lisi",21);return user2;}}
(2)解決方法
????????修改屬性名為對象名稱:不建議使用,因為通常認為變量名與程序的業務邏輯無關,即修改變量名不影響程序的運行。但是這里如果修改變量名就會報錯。
????????@Primary:在多個Bean的某一個前加上@Primary,表示該Bean是優先被注入的對象。
@Componentpublic class UserCompeonent {public void hello(){System.out.println("hello userCompeonent");}@Primary@Beanpublic UserInfo getUser1(){UserInfo user1 = new UserInfo(100,"zhangsan",20);return user1;}@Beanpublic UserInfo getUser2(){UserInfo user2 = new UserInfo(101,"lisi",21);return user2;}}@Controllerpublic class UserController {@Autowiredpublic UserInfo user1;public void hello(){System.out.println("hello userController");System.out.println(user1);}}
????????@Qualifier:搭配@Autowired注解使用,在要注入的屬性前加上@Qualifier(“對象名稱”),指定要注入的對象。
@Controllerpublic class UserController {@Qualifier("getUser2")@Autowiredpublic UserInfo user;public void hello(){System.out.println("hello userController");System.out.println(user);}}
????????@Resource:不使用@Autowired注解進行注入了,直接使用@Resource(name=“對象名稱”)進行注入。
@Controllerpublic class UserController {@Resource(name = "getUser1")public UserInfo user;public void hello(){System.out.println("hello userController");System.out.println(user);}}
(3)@Autowired與@Resource區別
????????1.從通用性角度來講:@Autowired是Spring框架提供的注解,只適用Spring框架搭建的程序中。@Resource是JDK提供的注解,使用與Java的所有范圍。
????????2.從注入方式來講:@Autowired初始按對象類型注入,沒有找到對象就按對象名稱注入。@Resource是對象名稱注入,并且支持更多參數,最簡單的就是按name參數進行按對象名稱注入。
下篇文章:
Spring-AOPhttps://blog.csdn.net/sniper_fandc/article/details/148960179?fromshare=blogdetail&sharetype=blogdetail&sharerId=148960179&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link