本篇文章從Spring框架的概念入手,完整介紹Spring框架的特性和一些Spring一些底層的實現原理,詳細介紹Spring的特性IOC特性和Spring的三種不聽的注入方式,並分析其特點.最後總結Spring IOC的特點和Spring工廠的特性
文章目錄
引言
- 傳統web開發存在硬編碼所造成的過度程序耦合(例如:Service中作爲屬性Dao對象)
- 邠Java EE API較爲複雜,使用效率低(例如:JDBC開發步驟)
- 侵入性強,移植性差(例如:DAO實現的更換,從Connection到SqlSession).
Spring框架
概念
- Spring是一個項目管理框架,同時也是一套Java EE解決方案
- Spring是衆多優秀設計模式的組合(工廠,單例,代理,適配器,裝飾設計模式,觀察者,模板,策略)
- Spring並未代替現有框架產品,而是將衆多框架進行有機整合,簡化企業級開發,俗稱"膠水框架"
Spring架構組成
Spring架構組成 |
---|
搭建一個自定義的工廠
自己搭建一個自定義的工廠,瞭解一下工廠的運行原理是怎麼樣的
分析:
- 爲了測試我們的Spring的自動創建對象,所以需要創建一個實體類
- 需要配置文件來管理和指明我們要管理的實體類都有哪些
- 需要創建一個工廠提供一個方法來爲我們創建實體類的對象,而不是我們手工的去創建
最終的項目結構 |
---|
實體類代碼
Userdao接口代碼
package per.leiyu.dao;
/**
* @author 雷雨
* @date 2020/6/19 15:03
*/
public interface UserDao {
void deleteUser(Integer id);
}
UserDaoImpl代碼
package per.leiyu.dao.daoImpl;
import per.leiyu.dao.UserDao;
/**
* @author 雷雨
* @date 2020/6/19 15:10
*/
public class UserDaoImpl implements UserDao {
@Override
public void deleteUser(Integer id) {
System.out.println("User的刪除方法");
}
}
service的代碼基本上和UserDao相同,因爲我們只是爲了簡單實現spring的自動創建實體類對象功能,這裏只列舉一個測試對象就可以了
配置文件bean.properties
userDao=per.leiyu.dao.daoImpl.UserDaoImpl
userService=per.leiyu.service.serviceImpl.UserServiceImpl
測試類代碼
package per.leiyu.factoryTest;
import per.leiyu.dao.UserDao;
import per.leiyu.factory.Myfactory;
import java.io.IOException;
/**
* @author 雷雨
* @date 2020/6/19 15:30
*/
public class MyfactoryTest {
public static void main(String[] args) throws IOException, IllegalAccessException, InstantiationException, ClassNotFoundException {
//1.創建一個工廠對象
Myfactory myfactory = new Myfactory("/bean.properties");
//2.從工廠中獲取對象
UserDao userDao = (UserDao)myfactory.getBean("userDao");
userDao.deleteUser(1);
}
}
自定義工廠獲取實體類對象的測試結果 |
---|
思路分析:
- 配置文件提供了實體類和給定的一個
name
值,告訴我們用name值可以找到對應實體類的路徑- 工廠先加載配置文件,然後提供一個方法來獲取配置文件中name值來用
反射機制
創建對應的實體類
在maven項目中搭建spring環境
先創建好一個maven項目
- 第一步需要導入spring的依賴
- 創建Spring的配置文件
- 創建實體類(創建實體類後要在配置文件中對要創建對象的實體類"註冊")
- 測試
spring的依賴
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
創建Spring的配置文件
命名無限制,約定俗稱的命名有:spring-context.xml applicationContext.xml 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-3.0.xsd">
<!--要工廠生產的對象
使用的是xml格式的配置方式
-->
<bean id="userDao" class="per.leiyu.dao.daoImpl.UserDaoImpl"></bean>
</beans>
id
:標識class
:要生產的類的路徑
仍然使用上面自定義工廠的實體類就可以
測試
package per.leiyu.factoryTest;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import per.leiyu.dao.UserDao;
/**
* @author 雷雨
* @date 2020/6/19 16:01
*/
public class SpringFactoryTest {
@Test
public void testSpringFactory(){
//啓動工廠
//加載了配置文件(用的實現類) 返回值用接口接收
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
//獲取對象
UserDao userDao = (UserDao) context.getBean("userDao");
userDao.deleteUser(1);
}
}
Spring簡單實現自動創建實體類對象測試結果 |
---|
- 我們能調到那個實體類的方法並執行,就說明在底層Spring已經自動的幫我們創建了實體類的對象
Spring工廠細節
- 我們在maven項目的依賴中只導入了一個依賴,真的是這樣嗎?
- schema
Spring依賴管理
雖然我們只在maven項目中導入了spring的一個依賴,但是
jar包之間是彼此依賴
的,當導入一個"上層"依賴時,maven發現"下層"的依賴沒有導入就會自動幫我們導入
看這個圖就會發現maven實際上並不是導入一個依賴 |
---|
Spring常用功能的jar包依賴關係 |
---|
Schema:規範
配置文件中的頂級標籤中包含了語義化標籤的相關信息
- xmlns: 語義化標籤所在的命名空間
- xmlns:xsi: XMLSchema-instance標籤遵循Schema標籤標準
- xsi:schemaLocation: xsd文件位置,用以描述標籤語義,屬性,取值範圍等
簡單的說就是描述了
- xml文件中可以出現什麼樣的標籤
- 這些標籤代表了什麼語義
- 這些標籤的層級是怎麼樣的(誰是誰的父標籤,誰是子標籤)
IOC
IOC簡單介紹
IOC
:Inversion of Control控制反轉反轉了依賴關係的滿足方式,由之前的自己創建依賴對象,變爲了由工廠推送.(變主動爲被動,即反轉)
解決了具有依賴關係的組件之間的強耦合,使得項目形態更加穩健
要了解控制反轉,那就首先得了解什麼叫依賴關係,這裏做一個簡單的解釋
沒使用Spring項目管理之前強耦合和其缺點
在有Spring IOC之前 |
---|
我們service的實現需要使用到UserDaoImpl的功能,那麼我們在不使用Spring的情況下,我們只能將該對象new出來.--------這就導致了強耦合
當項目需要發生改變:我們需要使用到另一個UserDaoImpl時,我們不僅需要重新寫一個UserDaoImpl,還需要改動service的相關代碼
,這種強耦合的關係,在修改一個類的時候還需要改動另一個類的代碼,這給代碼的維護和開發都帶來很大的困難,這就導致了多少程序員同行的加班熬夜和程序員們可憐的髮際線逐漸難以保護
使用Spring IOC
- 不再在需要用到其他類時new對象,而是叫給Spring管理,
只需要提供給其接口信息,和get set方法
- 在spring的配置文件中指定
package per.leiyu.service.serviceImpl;
import per.leiyu.dao.UserDao;
import per.leiyu.dao.daoImpl.UserDaoImpl;
import per.leiyu.service.UserService;
/**
* @author 雷雨
* @date 2020/6/19 15:13
*/
public class UserServiceImpl implements UserService {
// UserDao userDao = new UserDaoImpl() ;
private UserDao userDao;
@Override
public void deleteUser(Integer id) {
System.out.println("UserService的實現方法");
userDao.deleteUser(1);
}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
<bean id="userDao" class="per.leiyu.dao.daoImpl.UserDaoImpl"></bean>
<bean id="userService" class="per.leiyu.service.serviceImpl.UserServiceImpl">
<!-- userDao屬性賦值 值爲id爲userDao的一個bean -->
<property name="userDao" ref="userDao"/>
</bean>
- 這裏的
標籤中的name屬性
填寫實體類(UserServiceImpl)中的userDao屬性
UserServiceImpl實體類中的屬性 |
---|
ref
對應的是在spring配置文件中一個bean的一個id值
ref屬性對應id值 |
---|
使用Spring執行結果 |
---|
- 成功執行了自己的方法和UserDao接口的實現類的方法
使用Spring IOC的優勢
- 不引用任何一個具體的組件(實現類),在需要其他組件的位置預留存取值入口(set/get)
當UserServiceImpl中的使用的Userdao的實現類發生改動時
不需要改動UserServiceImpl中相關代碼
只需要在Spring的相關配置文件中重新"註冊"實體類的bean,修改UserServiceImpl對應的ref映射的bean的id值即可.
消除了UserServiceImpl和UserDao的強耦合
這就達到了
如果一個類需要修改,那麼一定是這個類本身的問題,而不會是因爲別的類發生修改而這個類也發生修改
DI
DI
:Dependency Injection 依賴注入
概念
在Spring創建對象的同時,爲其屬性賦值,稱之爲依賴注入
可分爲三類注入方式:
- set注入
- 構造注入
- 自動注入
set注入(常用)
創建對象時,Spring工廠會通過Set方法爲對象的屬性賦值
定義目標Bean類型並提供get和set方法
package per.leiyu.entiry;
import java.util.*;
/**
* @author 雷雨
* @date 2020/6/19 17:45
*/
public class User {
private Integer id;
private String password;
private String sex;
private Date bornDate;
private String[] hobbys;
private Set<String> phones;
private List<String> names;
private Map<String,String> countries;
private Properties properties;
//篇幅原因這裏不展示get和set
在Spring配置文件中的映射
<bean id="user" class="per.leiyu.entiry.User">
<!-- 基本類型 String -->
<property name="id" value="1"/>
<property name="password" value="123"/>
<property name="sex" value="男"/>
<property name="bornDate" value="2020/6/18 12:20:03"/>
<!-- 數組 -->
<property name="hobbys">
<array>
<value>bastetball</value>
<value>football</value>
</array>
</property>
<!-- set-->
<property name="phones">
<set>
<value>12321</value>
<value>214214</value>
</set>
</property>
<property name="names">
<list>
<value>張三</value>
<value>李四</value>
</list>
</property>
<property name="countries">
<map>
<entry key="zh" value="中國"></entry>
<entry key="en" value="英國"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="url">jdbc:mysql:3306</prop>
<prop key="username">root</prop>
</props>
</property>
</bean>
構造注入(瞭解)
創建對象時,Spring工廠會通過構造方法爲對象的屬性賦值
定義目標Bean類型並提供有參的構造函數
package per.leiyu.entiry;
/**
* @author 雷雨
* @date 2020/6/19 18:17
*/
public class Student {
private Integer id;
private String name;
private String sex;
private Integer age;
public Student(Integer id, String name, String sex, Integer age) {
//這裏在構造裏面加了一句話是爲了後面容易驗證
System.out.println("這個方法執行了");
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
}
public Student() {
super();
}
}
Spring配置文件配置Bean
<bean id="student" class="per.leiyu.entiry.Student">
<constructor-arg name="id" value="1"/>
<constructor-arg name="name" value="雷雨想當工程師"/>
<constructor-arg name="age" value="18"/>
<constructor-arg name="sex" value="男"/>
bean>
測試類
package per.leiyu.factoryTest;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import per.leiyu.entiry.Student;
/**
* @author 雷雨
* @date 2020/6/19 18:20
*/
public class SpringStudentTest {
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
Student student = (Student) context.getBean("student");
System.out.println(student);
}
}
結果 |
---|
自動注入(瞭解)
自動注入方式-byName |
---|
自動注入方式-byType |
---|
- 需要注意的是:在配置文件中使用自動注入,
符合注入條件的必須只是唯一的一個Bean
,如果是多個Bean,那麼會報異常
Bean細節
控制簡單對象的單例.多例模式
配置
<!--
singleton(默認):每次調用工廠都是得到的同一個對象
prototype:每次調用工廠,都會創建新的對象
-->
<bean id="mc" class="per.leiyu.entiry.Mycalass" scope="singleton"></bean>
- 注意:需要根據場景決定對象的單例和多例模式
- 可以共用:Service.Dao.SqlSessionFactory(或者是所有的工廠)
- 不可共用:Connection.SqlSession.ShoppingCart
FactoryBean創建複雜對象
作用:讓Spring可以創建複雜對象.或者無法直接通過反射創建的對象
Spring創建對象的過程:就是需要用反射來調用實體類的構造方法來創建對象,但是有些對象不能通過簡單的new(也就是構造方法的方式)來創建對象
複雜對象:簡單的說就是不能通過new創建的對象,比如Connection對象.SqlSessionFactory對象
創建複雜對象需要藉助FactoryBean,這裏演示一個創建Connection對象的工廠Bean方法
package per.leiyu;
import org.springframework.beans.factory.FactoryBean;
import java.sql.Connection;
import java.sql.DriverManager;
/**
* @author 雷雨
* @date 2020/6/19 20:21
*/
public class myFactoryBean implements FactoryBean {
/**
* 創建複雜對象的具體方法
* @return
* @throws Exception
*/
@Override
public Object getObject() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection("jdbc:mysql:localhost:3306:/mybatis","root","123456");
}
@Override
public Class<?> getObjectType() {
return Connection.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
在Spring配置文件中進行註冊
<!-- 複雜對象的創建 演示Connection的創建
注意Spring中創建複雜對象(使用FactoryBean)返回的是getObject方法的返回值
-->
<bean id="myFactoryconn" class="per.leiyu.myFactoryBean"></bean>
測試類
package per.leiyu.factoryTest;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.sql.Connection;
/**
* @author 雷雨
* @date 2020/6/19 20:32
*/
public class TestFactoryConnection {
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
Connection conn = (Connection)context.getBean("conn");
System.out.println(conn);
}
}
成功創建了複雜對象 |
---|
Spring工廠特性
餓漢式創建優勢
工廠創建之後,會將Spring配置文件中的所有對象都創建完成(餓漢式)
提高程序運行效率.比年多次IO,減少對象創建時間(概念接近連接池,一次性創建好,使用時直接獲取)
聲明週期方法
- 自定義初始化方法:添加"inint-method"屬性,Spring則會在創建對象之後,調用此方法
- 自定義銷燬方法:添加"destory-method"屬性,Spring則會在銷燬對象之前,調用此方法
- 銷燬:工廠的close()方法被調用之後,Spring會銷燬所有已創建的單例對象
- 分類:Singleton對象由Spring容器銷燬,prototype對象由JVM銷燬
Spring生命週期過程:(不完全過程)
調用構造方法-----調用set方法-----調用初始化方法-----調用銷燬方法銷燬
- 如果不是構造注入,那麼就調用空參構造,如果是構造注入就直接調用有參的構造方法
生命週期階段
單例Bean:singleton
隨工廠啓動
創建
=構造方法
>set方法
(注入值)>init
(初始化)>構建完成===隨工廠關閉銷燬
多例Bean:prototype
被使用時
創建
=構造方法
>set方法
(注入值)>init
(初始化)>構建完成===JVM垃圾回收銷燬
我是雷雨,一個
普本科
的學生,主要專注於Java後端和大數據開發
如果這篇文章有幫助到你,希望你給我一個
大大的贊
如果有什麼問題,希望你能留言
和我一起研究
,學習靠自覺,分享靠自願