文章目錄
- 圖書管理系統
- 用戶登錄
- 需求分析
- 接口定義
- 前端頁面代碼
- 服務器代碼
- 圖書列表展示
- 需求分析
- 接口定義
- 前端頁面部分代碼
- 服務器代碼
- Controller層
- service層
- Dao層
- modle層
- Spring IoC
- 定義
- 傳統程序開發
- 解決方案
- IoC優勢
- Spring DI
- IoC &DI使用
- 主要注解
- Spring IoC詳解
- bean的存儲
- 五大注解
- @Controller(控制器存儲)
- getBean()方法
- 面試:ApplicationContext VS BeanFactory
- 為什么要這么多類注解
- 五大注解是否可以混用
- 程序被Spring管理的條件
- 方法注解--@Bean
- 掃描路徑
- Spring DI詳解
- 屬性注入
- 構造方法注入
- Setter注入
- 優缺點
- @Autowired存在的問題
- @Primary
- @Qualifier
- @Resource
- 面試--@Autowird 與 @Resource的區別
圖書管理系統
用戶登錄
需求分析
賬號密碼校驗接口:根據輸入用戶名和密碼校驗登錄是否通過。
接口定義
- url:/user/login
- type:post
- 請求參數:name=admin&password=admin
- 返回:true //賬號密碼驗證成功,false//賬號密碼驗證失敗
前端頁面代碼
<body><div class="container-login"><div class="container-pic"><img src="pic/computer.png" width="350px"></div><div class="login-dialog"><h3>登陸</h3><div class="row"><span>用戶名</span><input type="text" name="userName" id="userName" class="form-control"></div><div class="row"><span>密碼</span><input type="password" name="password" id="password" class="form-control"></div><div class="row"><button type="button" class="btn btn-info btn-lg" onclick="login()">登錄</button></div></div></div><script src="js/jquery.min.js"></script><script>function login() {$.ajax({url: "/user/login",type: "post",data: {userName: $("#userName").val(),password: $("#password").val()},success: function (result) {if (result == "") {location.href = "book_list.html";} else {alert(result)}}})}</script>
</body>
服務器代碼
@RequestMapping("/user")
@RestController
public class UserController {@RequestMapping("/login")public String login(String userName, String password, HttpSession session) {//1.校驗參數//2.驗證密碼是否正確//3.返回結果if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {return "用戶名或密碼為空";}//理論上應該從數據庫中讀取,暫時先不用//一般正確情況下,會有后續的操作,因此建議將后續操作比較多的情況下放在括號外面if (!"admin".equals(userName) || !"admin".equals(password)) {return "密碼錯誤";}session.setAttribute("userName", userName);return "";}
}
圖書列表展示
需求分析
圖書列表:提供圖書列表信息
接口定義
- url:/user/login
- type:post
- 請求參數:無
- 返回:
[{“id”: 1,
“bookName”:“book1”,
“author” :“author1”,
“count”:270,
“price”: 20”,
publish":“publish1”,
“status”: 1,
“statusCN”:“可借閱”}…]
前端頁面部分代碼
<script>//這里需要直接訪問到后端的圖書列表信息,直接需要訪問到后端,所以直接開始就使用ajax,但是為了代碼的可讀性,將其封裝到一個方法里面先getBookList();function getBookList() {$.ajax({url:"/book/getBookList",type:"get",success:function(books){var finalHtml = "";//下面使用單引號的原因是因為html中也有雙引號,可能會造成出錯for(var book of books){finalHtml += '<tr>';finalHtml += '<td><input type="checkbox" name="selectBook" value="'+book.id+'" id="selectBook" class="book-select"></td>';finalHtml += '<td>'+book.id+'</td>';finalHtml += '<td>'+book.bookName+'</td>';finalHtml += '<td>'+book.author+'</td>';finalHtml += '<td>'+book.num+'</td>';finalHtml += '<td>'+book.price+'</td>';finalHtml += '<td>'+book.publishName+'</td>';finalHtml += '<td>'+book.statusCN+'</td>';finalHtml += '<td>';finalHtml += ' <div class="op">';finalHtml += '<a href="book_update.html?bookId='+book.id+'">修改</a>';finalHtml += '<a href="javascript:void(0)" onclick="deleteBook('+book.id+')">刪除</a>';finalHtml += '</div></td></tr>';}$("tbody").html(finalHtml);}})}
服務器代碼
Controller層
@RequestMapping("/book")
@RestController
public class BookController {@Autowiredprivate BookService bookService;@RequestMapping("/getBookList")public List<BookInfo> getBookList() {
// BookService bookService = new BookService();return bookService.getBookList();}
}
service層
@Component
public class BookService {@Autowiredprivate BookDao bookDao;public List<BookInfo> getBookList(){
// BookDao bookDao = new BookDao();List<BookInfo> bookInfos = bookDao.mockData();for (BookInfo bookInfo : bookInfos) {if (bookInfo.getStatus() == 2){bookInfo.setStatusCN("不可借閱");}else {bookInfo.setStatusCN("可借閱");}}return bookInfos;}
}
Dao層
@Component
public class BookDao {//理論上該方法應該從數據庫中獲取,當前采用mock方式public List<BookInfo> mockData(){List<BookInfo> bookInfos = new ArrayList<BookInfo>();//mock數據,也就是測試時候所有的模擬數據for (int i = 1; i <= 15; i++) {BookInfo bookInfo = new BookInfo();bookInfo.setId(i);bookInfo.setAuthor("作者" + i);bookInfo.setBookName("圖書" + i);bookInfo.setNum(i * 2 + 1);bookInfo.setPrice(new BigDecimal(i*3));bookInfo.setPublishName("出版社" + i);if (i % 5 == 0){bookInfo.setStatus(2);
// bookInfo.setStatusCN("不可借閱");}else {bookInfo.setStatus(1);
// bookInfo.setStatusCN("可借閱");}bookInfos.add(bookInfo);}return bookInfos;}
}
modle層
@Data
public class BookInfo {private Integer id;private String bookName;private String author;private Integer num;private BigDecimal price;private String publishName;private Integer status;//1-可借閱 0-不可借閱 這里的數據都是要存放到數據庫中的,盡量減少往數據庫中存放文字信息private String statusCN;//這個字段不用網數據庫中存儲,僅是為了與status進行文字與數字的轉換
}
Spring IoC
Spring的抽象概念:Spring是包含了眾多?具?法的IoC容器。
定義
IoC 是Spring的核心思想,之前項目在類上添加 @Restcontroller 和@Controller 注解,就是把這個對象交給Spring管理,Spring框架啟動時就會加載該類,把對象交給Spring管理,就是loC思想。
loC:Inversion of Control(控制反轉),也就是說 Spring是一個"控制反轉"的容器。
什么是控制反轉呢?也就是控制權反轉,什么的控制權發生了反轉?
獲得依賴對象的過程被反轉了也就是說,當需要某個對象時,傳統開發模式中需要自己通過 new 創建對象,現在不需要再進行創建,把創建對象的任務交給容器,程序中只需要依賴注入(DependencyInjection, Dl)就可以了這個容器稱為:loC容器,Spring是一個loC容器,所以有時Spring 也稱為Spring 容器。
傳統程序開發
比如開發一個汽車,傳統的設計思路為:
先設計輪子(Tire),然后根據輪子的大小設計底盤(Bottom),接著根據底盤設計車身(Framework),后根據車身設計好整個汽車(Car)。
這里就出現了一個"依賴"關系:汽車依賴車身,車身依賴底盤,底盤依賴輪子。
也就是Car類依賴Framework類,Framework類依賴Bottom類,Bottom類依賴Tire類。
這樣的設計看起來沒問題,但是可維護性卻很低,接下來需求有了變更:隨著對的車的需求量越來越大,個性化需求也會越來越多,我們需要加工多種尺寸的輪胎。
此時,開發一個汽車需要依賴再依賴,直至依賴到輪胎的尺寸,也就是下面的改法。
public static void main(String[] args) {public static void main(String[] args) {Car car = new Car(20);car.run();}}public class Car {private Framework framework;public Car(int size) {framework = new Framework(size);System.out.println("Car init....");}public void run() {System.out.println("Car run...");}
}public class Framework {private Bottom bottom;public Framework(int size) {bottom = new Bottom(size);System.out.println("Framework init...");}
}public class Bottom {private Tire tire;public Bottom(int size) {this.tire = new Tire(size);System.out.println("Bottom init...");}
}public class Tire {private int size;public Tire(int size) {this.size = size;System.out.println("輪胎尺?: " + size);}
}
以上代碼可以看出,以上程序的問題是:當最底層代碼改動之后,整個調用鏈上的所有代碼都需要修改,程序的耦合度非常高(修改一處代碼,影響其他處的代碼修改)。
解決方案
我們嘗試換一種思路,先設計汽車的大概樣子,然后根據汽車的樣子來設計車身,根據車身來設計底盤,最后根據底盤來設計輪子。這時候,依賴關系就倒置過來了:輪子依賴底盤,底盤依賴車身,車身依賴汽車。
可以嘗試不在每個類中創建下級類,如果創建下級類就會出現當下級類發生改變操作,自己也要跟著修改。
此時,只需要將原來由自己創建的下級類,改為傳遞的方式(也就是注入的方式),因為我們不需要在當前類中創建下級類了,所以下級類即使發生變化(創建或減少參數),當前類本身也無需修改任何代碼,這樣就完成了程序的解耦。
public class Main {public static void main(String[] args) {Tire tire = new Tire(20);Bottom bottom = new Bottom(tire);Framework framework = new Framework(bottom);Car car = new Car(framework);car.run();}
}public class Car {private Framework framework;public Car(Framework framework) {this.framework = framework;System.out.println("Car init....");}public void run() {System.out.println("Car run...");}
}public class Car {private Framework framework;public Car(Framework framework) {this.framework = framework;System.out.println("Car init....");}public void run() {System.out.println("Car run...");}
}public class Framework {private Bottom bottom;public Framework(Bottom bottom) {this.bottom = bottom;System.out.println("Framework init...");}
}public class Bottom {private Tire tire;public Bottom(Tire tire) {this.tire = tire;System.out.println("Bottom init...");}
}public class Tire {private int size;public Tire(int size) {this.size = size;System.out.println("輪胎尺?: " + size);}
}
代碼經過以上調整,無論底層類如何變化,整個調用鏈是不用做任何改變的,這樣就完成了代碼之間的解耦,從而實現了更加靈活、通用的程序設計了。
IoC優勢
在傳統的代碼中對象創建順序是:Car>Framework->Bottom->Tire
改進之后解耦的代碼的對象創建順序是:Tire->Bottom->Framework ->Car
改進之后的控制權發生的反轉,不再是使用方對象創建并控制依賴對象了,而是把依賴對象注入將當前對象中,依賴對象的控制權不再由當前類控制了。
這樣的話,即使依賴類發生任何改變,當前類都是不受影響的,這就是典型的控制反轉,也就是IoC的實現思想。
loC容器具備以下優點
- 資源集中管理:loC容器會幫助管理一些資源(對象等),需要使用時,只需要從loC容器中去取就可以了。
- 解耦合:在創建實例的時候不需要了解其中的細節,降低了使用資源雙方的依賴程度,也就是耦合度。
Spring 就是一種loC容器,幫助我們來做了這些資源管理。
Spring DI
Dl:DependencyInjection(依賴注入),容器在運行期間,動態的為應用程序提供運行時所依賴的資源,稱之為依賴注入。
程序運行時需要某個資源,此時容器就為其提供這個資源。
從這點來看,依賴注入(DI)和控制反轉(l0C)是從不同的角度的描述的同一件事情,就是指通過引入loC容器,利用依賴關系注入的方式,實現對象之間的解耦。
上述代碼通過構造函數的方式,把依賴對象注入到需要使用的對象中。
IoC是一種思想,也是"目標",而思想只是一種指導原則,最終還是要有可行的落地方案,而 DI 就屬于具體的實現。
所以也可以說,DI是loC的一種實現。
IoC &DI使用
既然 Spring 是一個 loC(控制反轉)容器,作為容器,那么它就具備兩個最基礎的功能:存、取。
Spring 容器管理的主要是對象,這些對象,我們稱之為"Bean"。把這些對象交由Spring管理,由Spring來負責對象的創建和銷毀,程序只需要告訴Spring,哪些需要存,以及如何從Spring中取出對象。
主要注解
@Component:交給Spring管理
@Autowired:注入運行時依賴的對象
@Component
public class BookService {@Autowiredprivate BookDao bookDao;public List<BookInfo> getBookList(){
// BookDao bookDao = new BookDao();List<BookInfo> bookInfos = bookDao.mockData();for (BookInfo bookInfo : bookInfos) {if (bookInfo.getStatus() == 2){bookInfo.setStatusCN("不可借閱");}else {bookInfo.setStatusCN("可借閱");}}return bookInfos;}
}
Spring IoC詳解
前?提到IoC控制反轉,就是將對象的控制權交給Spring的IOC容器,由IOC容器創建及管理對象,也就是bean的存儲。
bean的存儲
五大注解
- 五大類注解:@Controller、@Service、@Repository、@Component、@Configuration 。
- bean對象:在spring容器中存放的對象。
- ApplicationContext: 翻譯為Spring上下文,指的就是當前的運行環境,也可以看作是?個容器。故ApplicationContext的對象中存放了所有與當前的運行環境有關的內容,比如 spring容器中存放的bean對象。
@Controller(控制器存儲)
這里僅展示@Controller,其他四個注解與@Controller類似。
將UserControllerTest類用@Controller注解存放到IoC容器中。
@Controller
public class UserControllerTest {public void say(){System.out.println("hello, UserControllerTest");}
}
getBean()方法
這里分別使用三種getBean()方法來獲取UserController對象,進行打印測試
@SpringBootApplication
public class LibraryApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(LibraryApplication.class, args);UserControllerTest bean = context.getBean(UserControllerTest.class);bean.say();System.out.println(bean);UserControllerTest userControllerTest = (UserControllerTest) context.getBean("userControllerTest");userControllerTest.say();System.out.println(userControllerTest);UserControllerTest userControllerTest1 = context.getBean("userControllerTest", UserControllerTest.class);userControllerTest1.say();System.out.println(userControllerTest1);}
}
結果成功輸出也就是獲取到了UserControllerTest對象,并且地址一樣,說明是一個對象。
獲取對象的功能是Application的父類BeanFactory的功能。
面試:ApplicationContext VS BeanFactory
繼承關系和功能方面來說:Spring 容器有兩個頂級的接口BeanFactory和ApplicationContext。
其中 BeanFactory 提供了基礎的訪問容器的能力,而ApplicationContext 屬于 BeanFactony 的子類,它除了繼承了 BeanFactory 的所有功能之外,它還擁有獨特的特性,還添加了環境管理支持、資源訪問支持、以及事件傳播等方面的支持。
從性能方面來說:ApplicationContext 是一次性加載并初始化所有的 Bean 對象,而BeanFactory 是需要那個才去加載那個,因此更加輕量(空間換時間)。
為什么要這么多類注解
與應用分層呼應,讓程序員看到類注解之后,就能直接了解當前類的用途。
- @Controller:控制層,接收請求,對請求進行處理,并進行響應
- @Servie:業務邏輯層,處理具體的業務邏輯
- @Repository:數據訪問層,也稱為持久層,負責數據訪問操作
- @Configuration:配置層,處理項目中的一些配置信息
- @Component:是一個元注解,也就是說可以注解其他類注解@Controller,@Service,@Repository,@Confiquraion,這些注解被稱為@Component 的行生注解,因為這些注解源代碼里面都有一個注解@Component。
五大注解是否可以混用
功能上:@Service @Repository @Configuration @Component 可以完全混用,@Controller有自己的特殊性。
規范上:不可以混用。因為我們想要與應用分層呼應。
程序被Spring管理的條件
- 程序要被spring掃描到(默認路徑是啟動類所在的目錄以及子目錄),手動設置:@ComponentScan(basePackages = “~”)
- 程序需要配置五大注解和@Bean
方法注解–@Bean
@Bean要搭配類注解使用
類注解是添加到某個類上的,但是存在兩個問題:
- 使用外部包里的類,沒辦法添加類注解
- 一個類,需要多個對象,比如多個數據源
示例1,@Bean要搭配類注解使用
@Configuration
public class UserConfig {public void say(){System.out.println("hi,UserConfig");}@Beanpublic User user(){return new User("張三");}
}
示例2:定義多個對象,使用類的類型掃描
@Service
public class UserService {public void say(){System.out.println("hello, UserService");}@Beanpublic BookInfo user(){return new BookInfo();}@Beanpublic BookInfo user1(){return new BookInfo();}
}@SpringBootApplication
public class LibraryApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(LibraryApplication.class, args);BookInfo bean = context.getBean(BookInfo.class);System.out.println(bean);}
}
通過類的類型掃描,這里出現了報錯,通過類的類型掃描,此時容器中有兩個User對象,根據類型獲取對象,此時Spring不知道要獲取哪個對象,所以報錯了。
解決辦法:用類的名字掃描。
@SpringBootApplication
public class LibraryApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(LibraryApplication.class, args);BookInfo bean1 = (BookInfo) context.getBean("user");System.out.println(bean1);BookInfo bean2 = (BookInfo) context.getBean("user1");System.out.println(bean2);}
}
掃描路徑
把啟動類放到其他的目錄下面,再次啟動程序,會出錯。
就是因為沒有找到對應的bean對象,使用五大注解聲明的bean,要想生效,還需要配置掃描路徑,讓Spring掃描到這些注解也就是通過 @ComponentScan 來配置掃描路徑。
@ComponentScan({"com.example.library"})
@SpringBootApplication
public class LibraryApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(LibraryApplication.class, args);}
}
Spring DI詳解
DI(依賴注入):依賴注入是一個過程,是指loC容器在創建Bean時,去提供運行時所依賴的資源,而資源指的就是對象在上面程序案例中,我們使用了 @Autowired 這個注解,完成了依賴注入的操作簡單來說,就是把對象取出來放到某個類的屬性中。
在一些文章中,依賴注入也被稱之為"對象注入”、"屬性裝配”,具體含義需要結合文章的上下文來理解。
關于依賴注入, Spring也給我們提供了三種方式:
屬性注入(Field Injection)
構造?法注入(Constructor Injection)
Setter 注入(Setter Injection)
屬性注入
@Controller
public class UserControllerTest {@Autowiredprivate UserService userService;//屬性注入public void say(){System.out.println("hello, UserControllerTest");}
}
構造方法注入
只有一個構造方法的時候即使不加@Autowired也可以獲取數據,但是要是加一個空的構造方法,會報出空指針異常。
因為程序啟動的時候會首先調用無參數的構造方法,如果沒有會調用我們寫的,但是兩個都有的話就會調用無參數的,此時并沒有真正new對象,去調用say()方法就會出現空指針異常。
解決辦法:就是在想要注入的構造方法中添加@Autowired注解
@Controller
public class UserControllerTest {private UserService userService;@Autowiredpublic UserService(UserService service){this.userService = service;}public void say(){System.out.println("hello, UserControllerTest");}
}
Setter注入
@Controller
public class UserControllerTest {private UserService userService;@Autowiredpublic void setUserService(UserService service){this.userService = service;}public void say(){System.out.println("hello, UserControllerTest");}
}
優缺點
- 屬性注入
優點:簡潔,使用方便
缺點:只能用于 loC 容器,如果是非 loC 容器不可用,并且只有在使用的時候才會出現 NPE(空指針異常);不能注入一個Final修飾的屬性。 - 構造函數注入(Spring4.x推薦)
優點:可以注入final修飾的屬性;注入的對象不會被修改依賴對象;在使用前一定會被完全初始化,因為依賴是在類的構造方法中執行的,而構造方法是在類加載階段就會執行的方法;通用性好,構造方法是JDK支持的,所以更換任何框架都是適用的。
缺點:注入多個對象時,代碼會比較繁瑣。 - Setter注入
優點:方便在類實例之后,重新對該對象進行配置或者注入。
缺點:不能注入一個Final修飾的屬性;注入對象可能會被改變,因為setter方法可能會被多次調用,就有被修改的風險。
@Autowired存在的問題
Student 實體類
@Data
public class Student {private String name;private Integer id;public Student() {}public Student(String name) {this.name = name;}public Student(String name, Integer id) {this.name = name;this.id = id;}
BeanConfig類
@Configuration
public class BeanConfig {@Beanpublic Student StudentInfo() {return new Student("wh",01);}@Beanpublic Student StudentInfo2() {return new Student("Bob",02);}
}
DomeController類
@Controller
public class DomeController {@Autowiredprivate Student student;public void say(){System.out.println(student);}
}
啟動類
@SpringBootApplication
public class SpringIocApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);DomeController bean = context.getBean(DomeController.class);bean.say();}
}
運行
報錯的原因是,非唯一的 Bean 對象
解釋:@Autowired是先按照類型去注入,匹配到多個對象時,再按照名稱去注入。
如果明確注入對象的名稱,則可以正確打印該學生信息。
@Controller
public class DomeController {@Autowiredprivate Student StudentInfo2;public void say(){System.out.println(StudentInfo2);}
}
那當沒有明確注入對象的名稱,又想得到正確結果我們可以怎么做?
有以下幾種解決方案:
@Primary
@Qualifier
@Resource
@Primary
使用@Primary注解:當存在多個相同類型的Bean注入時,加上@Primary注解,來確定默認的實現。
直接加到Bean注入的方法上。
@Primary
@Bean
public Student StudentInfo() {return new Student("wh", 01);
}
@Qualifier
使?@Qualifier注解:指定當前要注?的bean對象。 在@Qualifier的value屬性中,指定注?的bean 的名稱。
@Qualifier注解不能單獨使?,必須配合@Autowired使用。
@Controller
public class DomeController {@Qualifier("StudentInfo2")@Autowiredprivate Student student;public void say(){System.out.println(student);}
}
@Resource
本身就是依賴注入注解,是按照bean的名稱進行注入。
@Controller
public class DomeController {@Resource(name = "StudentInfo")private Student student;public void say(){System.out.println(student);}
}
面試–@Autowird 與 @Resource的區別
- @Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解。
- @Autowired 默認是按照類型注入,而@Resource是按照名稱注入,相比于 @Autowired 來說, @Resource ?持更多的參數設置,例如 name 設置,根據名稱獲取 Bean。