Spring 是分層的 Java SE/EE 應用 full-stack 輕量級開源框架,以 IoC(Inverse Of Control: 反轉控制)AOP(Aspect Oriented Programming:面向切面編程)爲內核,提供了展示層 Spring MVC 和持久層 Spring JDBC 以及業務層事務管理等衆多的企業級應用技術,還能整合開源世界衆多 著名的第三方框架和類庫,逐漸成爲使用最多的 Java EE 企業應用開源框架。html
經過 Spring 提供的 IoC 容器,能夠將對象間的依賴關係交由 Spring 進行控制,避免硬編碼所造 成的過分程序耦合。用戶也沒必要再爲單例模式類、屬性文件解析等這些很底層的需求編寫代碼,能夠更專一於上層的應用。java
經過 Spring 的 AOP 功能,方便進行面向切面的編程,許多不容易用傳統 OOP 實現的功能能夠經過 AOP 輕鬆應付。mysql
能夠將咱們從單調煩悶的事務管理代碼中解脫出來,經過聲明式方式靈活的進行事務的管理,提升開發效率和質量。spring
能夠用非容器依賴的編程方式進行幾乎全部的測試工做,測試再也不是昂貴的操做,而是隨手可作的事情。sql
Spring 能夠下降各類框架的使用難度,提供了對各類優秀框架(Struts、Hibernate、Hessian、Quartz 等)的直接支持。數據庫
Spring 對 JavaEE API(如 JDBC、JavaMail、遠程調用等)進行了薄薄的封裝層,使這些 API 的使用難度大爲下降。編程
耦合性(Coupling),也叫耦合度,是對模塊間關聯程度的度量。耦合的強弱取決於模塊間接口的複雜性、調用模塊的方式以及經過界面傳送數據的多少。模塊間的耦合度是指模塊之間的依賴關係,包括控制關係、調用關係、數據傳遞關係。模塊間聯繫越多,其耦合性越強,同時代表其獨立性越差( 下降耦合性,能夠提升其獨立 性)。耦合性存在於各個領域,而非軟件設計中獨有的,可是咱們只討論軟件工程中的耦合。服務器
在軟件工程中,耦合指的就是就是對象之間的依賴性。對象之間的耦合越高,維護成本越高。所以對象的設計應使類和構件之間的耦合最小。軟件設計中一般用耦合度和內聚度做爲衡量模塊獨立程度的標準。劃分模塊的一個準則就是高內聚低耦合。架構
下面這段代碼就演示了程序的耦合:框架
public class JdbcDemo1 { public static void main(String[] args) throws SQLException { //1.註冊驅動 DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver()); //2.獲取鏈接 Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring", "root","12345678"); //3.獲取操做數據庫的預處理對象 PreparedStatement preparedStatement = connection.prepareStatement("select * from account"); //4.執行SQL語句,獲得結果 ResultSet resultSet = preparedStatement.executeQuery(); //5.遍歷結果集 while (resultSet.next()){ System.out.println(resultSet.getString("name")); } //6.釋放資源 resultSet.close(); preparedStatement.close(); connection.close(); } }
建立數據庫的SQL語句以下:
create table account( id int primary key auto_increment, name varchar(40), money float )character set utf8 collate utf8_general_ci; insert into account(name,money) values('aaa',1000); insert into account(name,money) values('bbb',1000); insert into account(name,money) values('ccc',1000);
運行結果以下:
在上述代碼中,咱們採用DriverManager.registerDriver方法來註冊驅動,這會致使一個編譯期依賴,若是咱們的項目中沒有導入對應的jar包或者更換了數據庫品牌(好比 Oracle),項目在編譯時就會出錯,須要修改源碼來從新數據庫驅動。這顯然不是咱們想要的。所以,咱們一般採用Class.forName("com.mysql.jdbc.Driver")的方式來註冊驅動。
所謂解耦合,咱們能夠簡單理解爲下降程序間的依賴。在實際開發中,應該作到儘可能避免編譯器依賴,而儘量是運行時依賴。上述案例,當採用Class.forName方式以後,咱們就可使用反射類建立對象,而避免使用new關鍵字。此時,咱們的類中再也不依賴具體的驅動類,此時就算刪除 mysql 的驅動jar包,依然能夠編譯(運行仍是會報錯,沒有驅動不可能運行成功的)。可是,這也產生了一個新的問題,mysql 驅動的全限定類名字符串是在 java 類中寫死的,一旦要改仍是要修改源碼。咱們能夠經過讀取配置文件的方式來獲取要建立對象的權限定類名來解決這個問題。
新建一個IDEA工程用於模擬傳統的三層架構,在src/mian/java/目錄下建立以下內容:
具體代碼以下:
//帳戶的持久層接口IAccountDao public interface IAccountDao { /** * 模擬和數據庫進行交互 */ void saveAccounts(); } //帳戶的持久層接口的實現類AccountDaoImpl public class AccountDaoImpl implements IAccountDao { /** * 模擬和數據庫進行交互 */ public void saveAccounts() { System.out.println("向數據庫寫入帳戶數據!!!"); } }
//帳戶的業務層接口IAccountService public interface IAccountService { /** * 模擬保存帳戶操做 */ void saveAccounts(); } //帳戶的業務層接口的實現類AccountServiceImpl public class AccountServiceImpl implements IAccountService { //持久層接口對象的引用 private IAccountDao accountDao = new AccountDaoImpl(); /** * 模擬保存帳戶操做 */ public void saveAccounts() { System.out.println("執行保存帳戶操做"); //調用持久層接口函數 accountDao.saveAccounts(); } }
//模擬表示層Client,用於調用業務層 public class Client { public static void main(String[] args) { IAccountService accountService = new AccountServiceImpl(); accountService.saveAccounts(); } }
運行結果以下:
在上述案例中,業務層調用持久層,而且此時業務層在依賴持久層的接口和實現類。如 果此時沒有持久層實現類, 編譯將不能經過。這種編譯期依賴關係,應該在咱們開發中杜絕。咱們須要優化代碼解決。
在實際開發中,咱們能夠用工廠模式來解決這個問題。具體來講就是,將三層架構的全限定類名都使用配置文件配置起來,當啓動服務器應用加載的時候,讓一個類中的方法經過讀取配置文件,運用反射把對象建立出來並存起來。在接下來的使用的時候,直接拿過來用就行了。那麼,這個讀取配置文件,建立和獲取三層對象的類就是工廠類。或者,咱們能夠簡單地理解爲工廠是用來建立Bean對象的。配置文件的內容應該包含兩個部分:惟一標識符和全限定類名(key-value的結構)。配置文件能夠是XML文件或者properties文件。
a.在src/java/main目錄下,新建包factory,在factory包下新建類BeanFactory:
//建立Bean對象的工廠類 public class BeanFactory { //Properties對象,用於讀取配置文件 private static Properties properties; //使用靜態代碼爲Properties對象賦值 static { try { //實例化對象 properties = new Properties(); //獲取properties文件的流對象 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); //加載配置文件 properties.load(in); } catch (IOException e) { throw new ExceptionInInitializerError("初始化properties對象失敗"); } } /** * 根據Bean的名稱獲取Bean對象 * @param beanName Bean對象的名稱 * @return */ public static Object getBean(String beanName) { Object bean = null; //獲取Bean對象的全限定類名 String beanPath = properties.getProperty(beanName); //從Properties對象中獲取Bean對象 try { bean = Class.forName(beanPath).getDeclaredConstructor().newInstance(); } catch (Exception e) { e.printStackTrace(); } return bean; } }
b.在src/resources目錄下,新建bean.properties文件,內容以下:
#帳戶的業務層接口的實現類 accountService = service.impl.AccountServiceImpl #帳戶的數據訪問層接口的實現類 accountDao = dao.impl.AccountDaoImpl
c.改造數據訪問層和業務邏輯層
更改ui/Client類中main方法的第一行代碼爲:
IAccountService accountService = (IAccountService) BeanFactory.getBean("accountService");
更改service/impl/AccountServiceImpl類中實例化持久層接口對象的代碼爲:
private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
d.運行結果
當進行上述改造以後,就再也不經過new關鍵字建立對象,而是經過對象名來找到配置文件中對應的全限定類名,而後經過反射建立對象。這就下降了程序之間的依賴,運行結果與以前徹底一致。
a.多例模式
更改ui/Client類中main方法爲:
public static void main(String[] args) { for(int i = 0; i < 5; i++){ //採用工廠模式建立對象 IAccountService accountService = (IAccountService) BeanFactory.getBean("accountService"); System.out.println(accountService); } }
能夠看到運行結果爲:
很明顯這是5個不一樣的對象,這是由於咱們在BeanFactory.getBean方法中建立Bean對象的方式爲:Class.forName(beanPath).getDeclaredConstructor().newInstance(),這樣獲取Bean對象時,都會調用默認的構造函數建立一個新的對象。多例模式因爲對象被建立屢次,所以執行效率沒有單例模式高。
b.單例模式
更改factory/BeanFactory類的代碼以下:
public class BeanFactory { //Properties對象,用於讀取配置文件 private static Properties properties; //定義一個Map,用於存放要建立Bean對象,也就是一個容器 private static Map<String, Object> beansMap = null; //單例模式 static { try { //實例化對象 properties = new Properties(); //獲取properties文件的流對象 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); //加載配置文件 properties.load(in); //實例化容器 beansMap = new HashMap<String, Object>(); //取出配置文件中全部的key Enumeration<Object> beanKeys = properties.keys(); //遍歷keys while(beanKeys.hasMoreElements()){ //取出每一個key,也就是對象名 String beanName = beanKeys.nextElement().toString(); //根據key獲取value,也就是全限定類名 String beanPath = properties.getProperty(beanName); //反射建立對象 Object beanObject = Class.forName(beanPath).getDeclaredConstructor().newInstance(); //存入容器 beansMap.put(beanName,beanObject); } } catch (Exception e) { e.printStackTrace(); throw new ExceptionInInitializerError("初始化建立Bean對象失敗!"); } } //單例模式 public static Object getBean(String beanName) { return beansMap.get(beanName); } }
運行結果爲:
能夠看到這時5個對象都是同一個對象,當BeanFacyory類加載時,就會建立Bean對象並存入Map結構中。當須要對象時,就會從這個Map結構中去取,所以都是同一個對象。