精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

深入淺出Spring架構(gòu)設(shè)計(jì)

開發(fā) 前端
為什么需要Spring? 什么是Spring?對(duì)于這樣的問題,大部分人都是處于一種朦朦朧朧的狀態(tài),說的出來,但又不是完全說的出來,今天我們就以架構(gòu)設(shè)計(jì)的角度嘗試解開Spring的神秘面紗。

前言

為什么需要Spring? 什么是Spring?

對(duì)于這樣的問題,大部分人都是處于一種朦朦朧朧的狀態(tài),說的出來,但又不是完全說的出來,今天我們就以架構(gòu)設(shè)計(jì)的角度嘗試解開Spring的神秘面紗。

本篇文章以由淺入深的方式進(jìn)行介紹,大家不必驚慌,我可以保證,只要你會(huì)編程就能看懂。

本篇文章基于Spring 5.2.8,閱讀時(shí)長大概需要20分鐘

案例

我們先來看一個(gè)案例:有一個(gè)小伙,有一輛吉利車, 平常就開吉利車上班

代碼實(shí)現(xiàn):

  1. public class GeelyCar { 
  2.  
  3.     public void run(){ 
  4.         System.out.println("geely running"); 
  5.     } 
  1. public class Boy { 
  2.   // 依賴GeelyCar 
  3.     private final GeelyCar geelyCar = new GeelyCar(); 
  4.  
  5.     public void drive(){ 
  6.         geelyCar.run(); 
  7.     } 

有一天,小伙賺錢了,又買了輛紅旗,想開新車。

簡單,把依賴換成HongQiCar

代碼實(shí)現(xiàn):

  1. public class HongQiCar { 
  2.  
  3.     public void run(){ 
  4.         System.out.println("hongqi running"); 
  5.     } 
  1. public class Boy { 
  2.     // 修改依賴為HongQiCar 
  3.     private final HongQiCar hongQiCar = new HongQiCar(); 
  4.  
  5.     public void drive(){ 
  6.         hongQiCar.run(); 
  7.     } 

新車開膩了,又想換回老車,這時(shí)候,就會(huì)出現(xiàn)一個(gè)問題:這個(gè)代碼一直在改來改去

很顯然,這個(gè)案例違背了我們的依賴倒置原則(DIP):程序不應(yīng)依賴于實(shí)現(xiàn),而應(yīng)依賴于抽象

優(yōu)化后

現(xiàn)在我們對(duì)代碼進(jìn)行如下優(yōu)化:

Boy依賴于Car接口,而之前的GeelyCar與HongQiCar為Car接口實(shí)現(xiàn)

代碼實(shí)現(xiàn):

定義出Car接口

  1. public interface Car { 
  2.  
  3.     void run(); 

將之前的GeelyCar與HongQiCar改為Car的實(shí)現(xiàn)類

  1. public class GeelyCar implements Car { 
  2.  
  3.     @Override 
  4.     public void run(){ 
  5.         System.out.println("geely running"); 
  6.     } 

HongQiCar相同

Person此時(shí)依賴的為Car接口

  1. public class Boy { 
  2.   // 依賴于接口 
  3.     private final Car car; 
  4.    
  5.     public Person(Car car){ 
  6.         this.car = car; 
  7.     } 
  8.  
  9.     public void drive(){ 
  10.         car.run(); 
  11.     } 

此時(shí)小伙想換什么車開,就傳入什么參數(shù)即可,代碼不再發(fā)生變化。

局限性

以上案例改造后看起來確實(shí)沒有什么毛病了,但還是存在一定的局限性,如果此時(shí)增加新的場(chǎng)景:

有一天小伙喝酒了沒法開車,需要找個(gè)代駕。代駕并不關(guān)心他給哪個(gè)小伙開車,也不關(guān)心開的是什么車,小伙就突然成了個(gè)抽象,這時(shí)代碼又要進(jìn)行改動(dòng)了,代駕依賴小伙的代碼可能會(huì)長這個(gè)樣子:

  1. private final Boy boy = new YoungBoy(new HongQiCar()); 

隨著系統(tǒng)的復(fù)雜度增加,這樣的問題就會(huì)越來越多,越來越難以維護(hù),那么我們應(yīng)當(dāng)如何解決這個(gè)問題呢?

思考

首先,我們可以肯定:使用依賴倒置原則是沒有問題的,它在一定程度上解決了我們的問題。

我們覺得出問題的地方是在傳入?yún)?shù)的過程:程序需要什么我們就傳入什么,一但系統(tǒng)中出現(xiàn)多重依賴的類關(guān)系,這個(gè)傳入的參數(shù)就會(huì)變得極其復(fù)雜。

或許我們可以把思路反轉(zhuǎn)一下:我們有什么,程序就用什么!

當(dāng)我們只實(shí)現(xiàn)HongQiCar和YoungBoy時(shí),代駕就使用的是開著HongQiCar的YoungBoy!

當(dāng)我們只實(shí)現(xiàn)GeelyCar和OldBoy時(shí),代駕自然而然就改變成了開著GeelyCar的OldBoy!

而如何反轉(zhuǎn),就是Spring所解決的一大難題。

Spring介紹

Spring是一個(gè)一站式輕量級(jí)重量級(jí)的開發(fā)框架,目的是為了解決企業(yè)級(jí)應(yīng)用開發(fā)的復(fù)雜性,它為開發(fā)Java應(yīng)用程序提供全面的基礎(chǔ)架構(gòu)支持,讓Java開發(fā)者不再需要關(guān)心類與類之間的依賴關(guān)系,可以專注的開發(fā)應(yīng)用程序(crud)。

Spring為企業(yè)級(jí)開發(fā)提供給了豐富的功能,而這些功能的底層都依賴于它的兩個(gè)核心特性:依賴注入(DI)和面向切面編程(AOP)。

Spring的核心概念

IoC容器

IoC的全稱為Inversion of Control,意為控制反轉(zhuǎn),IoC也被稱為依賴性注入(DI),這是一個(gè)通過依賴注入對(duì)象的過程:對(duì)象僅通過構(gòu)造函數(shù)、工廠方法,或者在對(duì)象實(shí)例化在其上設(shè)置的屬性來定義其依賴關(guān)系(即與它們組合的其他對(duì)象),然后容器在創(chuàng)建bean時(shí)注入這些需要的依賴。這個(gè)過程從根本上說是Bean本身通過使用直接構(gòu)建類或諸如服務(wù)定位模式的機(jī)制,來控制其依賴關(guān)系的實(shí)例化或位置的逆過程(因此被稱為控制反轉(zhuǎn))。

依賴倒置原則是IoC的設(shè)計(jì)原理,依賴注入是IoC的實(shí)現(xiàn)方式。

容器

在Spring中,我們可以使用XML、Java注解或Java代碼的方式來編寫配置信息,而通過配置信息,獲取有關(guān)實(shí)例化、配置和組裝對(duì)象的說明,進(jìn)行實(shí)例化、配置和組裝應(yīng)用對(duì)象的稱為容器。

一般情況下,我們只需要添加幾個(gè)注解,這樣容器進(jìn)行創(chuàng)建和初始化后,我們就可以得到一個(gè)可配置的,可執(zhí)行的系統(tǒng)或應(yīng)用程序。

Bean

在Spring中,由Spring IOC容器進(jìn)行實(shí)例化—>組裝管理—>構(gòu)成程序骨架的對(duì)象稱為Bean。Bean就是應(yīng)用程序中眾多對(duì)象之一。

以上三點(diǎn)串起來就是:Spring內(nèi)部是一個(gè)放置Bean的IoC容器,通過依賴注入的方式處理Bean之間的依賴關(guān)系。

AOP

面向切面編程(Aspect-oriented Programming),是相對(duì)面向?qū)ο缶幊?OOP)的一種功能補(bǔ)充,OOP面向的主要對(duì)象是類,而AOP則是切面。在處理日志、安全管理、事務(wù)管理等方面有非常重要的作用。AOP是Spring框架重要的組件,雖然IOC容器沒有依賴AOP,但是AOP提供了非常強(qiáng)大的功能,用來對(duì)IOC做補(bǔ)充。

AOP可以讓我們?cè)诓恍薷脑写a的情況下,對(duì)我們的業(yè)務(wù)功能進(jìn)行增強(qiáng):將一段功能切入到我們指定的位置,如在方法的調(diào)用鏈之間打印日志。

Spring的優(yōu)點(diǎn)

1、Spring通過DI、AOP來簡化企業(yè)級(jí)Java開發(fā)

2、Spring的低侵入式設(shè)計(jì),讓代碼的污染極低

3、Spring的IoC容器降低了業(yè)務(wù)對(duì)象之間的復(fù)雜性,讓組件之間互相解耦

4、Spring的AOP支持允許將一些通用任務(wù)如安全、事務(wù)、日志等進(jìn)行集中式處理,從而提高了更好的復(fù)用性

5、Spring的高度開放性,并不強(qiáng)制應(yīng)用完全依賴于Spring,開發(fā)者可自由選用Spring框架的部分或全部

6、Spring的高度擴(kuò)展性,讓開發(fā)者可以輕易的讓自己的框架在Spring上進(jìn)行集成

7、Spring的生態(tài)極其完整,集成了各種優(yōu)秀的框架,讓開發(fā)者可以輕易的使用它們

我們可以沒有Java,但是不能沒有Spring~

用Spring改造案例

我們現(xiàn)在已經(jīng)認(rèn)識(shí)了什么是Spring,現(xiàn)在就嘗試使用Spring對(duì)案例進(jìn)行改造一下

原來的結(jié)構(gòu)沒有變化,只需在GeelyCar或HongQiCar上增加@Component注解,Boy在使用時(shí)加上@Autowired注解

代碼樣式:

  1. @Componentpublic class GeelyCar implements Car { @Override public void run() {  System.out.println("geely car running"); }} 

HongQiCar相同

在Spring中,當(dāng)類標(biāo)識(shí)了@Component注解后就表示這是一個(gè)Bean,可以被IoC容器所管理

  1. @Componentpublic class Boy {  // 使用Autowired注解表示car需要進(jìn)行依賴注入 @Autowired private Car car; public void driver(){  car.run(); }} 

我們之前所說的:我們實(shí)現(xiàn)什么,程序就使用什么,在這里就等同于我們?cè)谀膫€(gè)類上標(biāo)識(shí)了Component注解,哪個(gè)類就會(huì)是一個(gè)Bean,Spring就會(huì)使用它注入Boy的屬性Car中

所以當(dāng)我們給GeelyCar標(biāo)識(shí)Component注解時(shí),Boy開的車就是GeelyCar,當(dāng)我們給HongQiCar標(biāo)識(shí)Component注解時(shí),Boy開的車就是HongQiCar

當(dāng)然,我們不可以在GeelyCar和HongQiCar上同時(shí)標(biāo)識(shí)Component注解,因?yàn)檫@樣Spring就不知道用哪個(gè)Car進(jìn)行注入了——Spring也有選擇困難癥(or 一boy不能開倆車?)

使用Spring啟動(dòng)程序

  1. // 告訴Spring從哪個(gè)包下掃描Bean,不寫就是當(dāng)前包路徑@ComponentScan(basePackages = "com.my.spring.test.demo")public class Main { public static void main(String[] args) {  // 將Main(配置信息)傳入到ApplicationContext(IoC容器)中  ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);  // 從(IoC容器)中獲取到我們的boy  Boy boy = (Boy) context.getBean("boy");  // 開車  boy.driver(); }} 

這里就可以把我們剛剛介紹Spring的知識(shí)進(jìn)行解讀了

把具有ComponentScan注解(配置信息)的Main類,傳給AnnotationConfigApplicationContext(IoC容器)進(jìn)行初始化,就等于:IoC容器通過獲取配置信息進(jìn)行實(shí)例化、管理和組裝Bean。

而如何進(jìn)行依賴注入則是在IoC容器內(nèi)部完成的,這也是本文要討論的重點(diǎn)

思考

我們通過一個(gè)改造案例完整的認(rèn)識(shí)了Spring的基本功能,也對(duì)之前的概念有了一個(gè)具象化的體驗(yàn),而我們還并不知道Spring的依賴注入這一內(nèi)部動(dòng)作是如何完成的,所謂知其然更要知其所以然,結(jié)合我們的現(xiàn)有知識(shí),以及對(duì)Spring的理解,大膽猜想推測(cè)一下吧(這是很重要的能力哦)

其實(shí)猜測(cè)就是指:如果讓我們自己實(shí)現(xiàn),我們會(huì)如何實(shí)現(xiàn)這個(gè)過程?

首先,我們要清楚我們需要做的事情是什么:掃描指定包下面的類,進(jìn)行實(shí)例化,并根據(jù)依賴關(guān)系組合好。

步驟分解:

掃描指定包下面的類 -> 如果這個(gè)類標(biāo)識(shí)了Component注解(是個(gè)Bean) -> 把這個(gè)類的信息存起來

進(jìn)行實(shí)例化 -> 遍歷存好的類信息 -> 通過反射把這些類進(jìn)行實(shí)例化

根據(jù)依賴關(guān)系組合 -> 解析類信息 -> 判斷類中是否有需要進(jìn)行依賴注入的字段 -> 對(duì)字段進(jìn)行注入

方案實(shí)現(xiàn)

我們現(xiàn)在已經(jīng)有了一個(gè)看起來像是那么一回事的解決方案,現(xiàn)在就嘗試把這個(gè)方案實(shí)現(xiàn)出來

定義注解

首先我們需要定義出需要用到的注解:ComponentScan,Component,Autowired

  1. @Target(ElementType.TYPE) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. public @interface ComponentScan { 
  4.  
  5.     String basePackages() default ""
  1. @Target(ElementType.TYPE) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. public @interface Component { 
  4.  
  5.     String value() default ""
  1. @Target(ElementType.FIELD) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. public @interface Autowired { 

掃描指定包下面的類

掃描指定包下的所有類,聽起來好像一時(shí)摸不著頭腦,其實(shí)它等同于另一個(gè)問題:如何遍歷文件目錄?

那么存放類信息應(yīng)該用什么呢?我們看看上面例子中g(shù)etBean的方法,是不是像Map中的通過key獲取value?而Map中還有很多實(shí)現(xiàn),但線程安全的卻只有一個(gè),那就是ConcurrentHashMap(別跟我說HashTable)

定義存放類信息的map

  1. private final Map<String, Class<?>> classMap = new ConcurrentHashMap<>(16); 

具體流程,下面同樣附上代碼實(shí)現(xiàn):

代碼實(shí)現(xiàn),可以與流程圖結(jié)合觀看:

掃描類信息

  1. private void scan(Class<?> configClass) { 
  2.   // 解析配置類,獲取到掃描包路徑 
  3.   String basePackages = this.getBasePackages(configClass); 
  4.   // 使用掃描包路徑進(jìn)行文件遍歷操作 
  5.   this.doScan(basePackages); 
  1. private String getBasePackages(Class<?> configClass) { 
  2.   // 從ComponentScan注解中獲取掃描包路徑 
  3.   ComponentScan componentScan = configClass.getAnnotation(ComponentScan.class); 
  4.   return componentScan.basePackages(); 
  1. private void doScan(String basePackages) { 
  2.   // 獲取資源信息 
  3.   URI resource = this.getResource(basePackages); 
  4.  
  5.   File dir = new File(resource.getPath()); 
  6.   for (File file : dir.listFiles()) { 
  7.     if (file.isDirectory()) { 
  8.       // 遞歸掃描 
  9.       doScan(basePackages + "." + file.getName()); 
  10.     } 
  11.     else { 
  12.       // com.my.spring.example + . + Boy.class -> com.my.spring.example.Boy 
  13.       String className = basePackages + "." + file.getName().replace(".class"""); 
  14.       // 將class存放到classMap中 
  15.       this.registerClass(className); 
  16.     } 
  17.   } 
  1. private void registerClass(String className){ 
  2.   try { 
  3.     // 加載類信息 
  4.     Class<?> clazz = classLoader.loadClass(className); 
  5.     // 判斷是否標(biāo)識(shí)Component注解 
  6.     if(clazz.isAnnotationPresent(Component.class)){ 
  7.       // 生成beanName com.my.spring.example.Boy -> boy 
  8.       String beanName = this.generateBeanName(clazz); 
  9.       // car: com.my.spring.example.Car 
  10.       classMap.put(beanName, clazz); 
  11.     } 
  12.   } catch (ClassNotFoundException ignore) {} 

實(shí)例化

現(xiàn)在已經(jīng)把所有適合的類都解析好了,接下來就是實(shí)例化的過程了

定義存放Bean的Map

  1. private final Map<String, Object> beanMap = new ConcurrentHashMap<>(16); 

具體流程,下面同樣給出代碼實(shí)現(xiàn):

代碼實(shí)現(xiàn),可以與流程圖結(jié)合觀看:

遍歷classMap進(jìn)行實(shí)例化Bean

  1. public void instantiateBean() { 
  2.   for (String beanName : classMap.keySet()) { 
  3.     getBean(beanName); 
  4.   } 
  1. public Object getBean(String beanName){ 
  2.   // 先從緩存中獲取 
  3.   Object bean = beanMap.get(beanName); 
  4.   if(bean != null){ 
  5.     return bean; 
  6.   } 
  7.   return this.createBean(beanName); 
  1. private Object createBean(String beanName){ 
  2.   Class<?> clazz = classMap.get(beanName); 
  3.   try { 
  4.     // 創(chuàng)建bean 
  5.     Object bean = this.doCreateBean(clazz); 
  6.     // 將bean存到容器中 
  7.     beanMap.put(beanName, bean); 
  8.     return bean; 
  9.   } catch (IllegalAccessException e) { 
  10.     throw new RuntimeException(e); 
  11.   } 
  1. private Object doCreateBean(Class<?> clazz) throws IllegalAccessException {  // 實(shí)例化bean  Object bean = this.newInstance(clazz);  // 填充字段,將字段設(shè)值  this.populateBean(bean, clazz);  return bean;} 
  1. private Object newInstance(Class<?> clazz){  try {    // 這里只支持默認(rèn)構(gòu)造器    return clazz.getDeclaredConstructor().newInstance();  } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {    throw new RuntimeException(e);  }} 
  1. private void populateBean(Object bean, Class<?> clazz) throws IllegalAccessException {  // 解析class信息,判斷類中是否有需要進(jìn)行依賴注入的字段  final Field[] fields = clazz.getDeclaredFields();  for (Field field : fields) {    Autowired autowired = field.getAnnotation(Autowired.class);    if(autowired != null){      // 獲取bean      Object value = this.resolveBean(field.getType());      field.setAccessible(true);      field.set(bean, value);    }  }} 
  1. private Object resolveBean(Class<?> clazz){  // 先判斷clazz是否為一個(gè)接口,是則判斷classMap中是否存在子類  if(clazz.isInterface()){    // 暫時(shí)只支持classMap只有一個(gè)子類的情況    for (Map.Entry<String, Class<?>> entry : classMap.entrySet()) {      if (clazz.isAssignableFrom(entry.getValue())) {        return getBean(entry.getValue());      }    }    throw new RuntimeException("找不到可以進(jìn)行依賴注入的bean");  }else {    return getBean(clazz);  }} 
  1. public Object getBean(Class<?> clazz){ 
  2.   // 生成bean的名稱 
  3.   String beanName = this.generateBeanName(clazz); 
  4.   // 此處對(duì)應(yīng)最開始的getBean方法 
  5.   return this.getBean(beanName); 

組合

兩個(gè)核心方法已經(jīng)寫好了,接下把它們組合起來,我把它們實(shí)現(xiàn)在自定義的ApplicationContext類中,構(gòu)造方法如下:

  1. public ApplicationContext(Class<?> configClass) { 
  2.   // 1.掃描配置信息中指定包下的類 
  3.   this.scan(configClass); 
  4.   // 2.實(shí)例化掃描到的類 
  5.   this.instantiateBean(); 

UML類圖:

測(cè)試

代碼結(jié)構(gòu)與案例相同,這里展示一下我們自己的Spring是否可以正常運(yùn)行

運(yùn)行正常,中國人不騙中國人

源碼會(huì)在文末給出

回顧

現(xiàn)在,我們已經(jīng)根據(jù)設(shè)想的方案進(jìn)行了實(shí)現(xiàn),運(yùn)行的情況也達(dá)到了預(yù)期的效果。但如果仔細(xì)研究一下,再結(jié)合我們平常使用Spring的場(chǎng)景,就會(huì)發(fā)現(xiàn)這一份代碼的不少問題:

1、無法支持構(gòu)造器注入,當(dāng)然也沒有支持方法注入,這是屬于功能上的缺失。

2、加載類信息的問題,加載類時(shí)我們使用的是classLoader.loadClass的方式,雖然這避免了類的初始化(可千萬別用Class.forName的方式),但還是不可避免的把類元信息加載到了元空間中,當(dāng)我們掃描包下有不需要的類時(shí),這就浪費(fèi)了我們的內(nèi)存。

3、無法解決bean之間的循環(huán)依賴,比如有一個(gè)A對(duì)象依賴了B對(duì)象, B對(duì)象又依賴了A對(duì)象,這個(gè)時(shí)候我們?cè)賮砜纯创a邏輯,就會(huì)發(fā)現(xiàn)此時(shí)會(huì)陷入死循環(huán)。

4、擴(kuò)展性很差,我們把所有的功能都寫在一個(gè)類里,當(dāng)想要完善功能(比如以上3個(gè)問題)時(shí),就需要頻繁修改這個(gè)類,這個(gè)類也會(huì)變得越來越臃腫,別說迭代新功能,維護(hù)都會(huì)令人頭疼。

優(yōu)化方案

對(duì)于前三個(gè)問題都類似于功能上的問題,功能嘛,改一改就好了。

我們需要著重關(guān)注的是第四個(gè)問題,一款框架想要變得優(yōu)秀,那么它的迭代能力一定要好,這樣功能才能變得豐富,而迭代能力的影響因素有很多,其中之一就是它的擴(kuò)展性。

那么應(yīng)該如何提高我們的方案的擴(kuò)展性呢,六大設(shè)計(jì)原則給了我們很好的指導(dǎo)作用。

在方案中,ApplicationContext做了很多事情, 主要可以分為兩大塊

1、掃描指定包下的類

2、實(shí)例化Bean

借助單一職責(zé)原則的思想:一個(gè)類只做一種事,一個(gè)方法只做一件事。

我們把掃描指定包下的類這件事單獨(dú)使用一個(gè)處理器進(jìn)行處理,因?yàn)閽呙枧渲檬菑呐渲妙惗鴣恚俏覀兙徒兴渲妙愄幚砥鳎篊onfigurationCalssProcessor

實(shí)例化Bean這件事情也同樣如此,實(shí)例化Bean又分為了兩件事:實(shí)例化和依賴注入

實(shí)例化Bean就是相當(dāng)于一個(gè)生產(chǎn)Bean的過程,我們就把這件事使用一個(gè)工廠類進(jìn)行處理,它就叫做:BeanFactory,既然是在生產(chǎn)Bean,那就需要原料(Class),所以我們把classMap和beanMap都定義到這里

而依賴注入的過程,其實(shí)就是在處理Autowired注解,那它就叫做: AutowiredAnnotationBeanProcessor

我們還在知道,在Spring中,不僅僅只有這種使用方式,還有xml,mvc,SpringBoot的方式,所以我們將ApplicationContext進(jìn)行抽象,只實(shí)現(xiàn)主干流程,原來的注解方式交由AnnotationApplicationContext實(shí)現(xiàn)。

借助依賴倒置原則:程序應(yīng)當(dāng)依賴于抽象

在未來,類信息不僅僅可以從類信息來,也可以從配置文件而來,所以我們將ConfigurationCalssProcessor抽象

而依賴注入的方式不一定非得是用Autowried注解標(biāo)識(shí),也可以是別的注解標(biāo)識(shí),比如Resource,所以我們將AutowiredAnnotationBeanProcessor抽象

Bean的類型也可以有很多,可以是單例的,可以使多例的,也可以是個(gè)工廠Bean,所以我們將BeanFactory抽象

現(xiàn)在,我們借助兩大設(shè)計(jì)原則對(duì)我們的方案進(jìn)行了優(yōu)化,相比于之前可謂是”脫胎換骨“。

Spring的設(shè)計(jì)

在上一步,我們實(shí)現(xiàn)了自己的方案,并基于一些設(shè)想進(jìn)行了擴(kuò)展性優(yōu)化,現(xiàn)在,我們就來認(rèn)識(shí)一下實(shí)際上Spring的設(shè)計(jì)

那么,在Spring中又是由哪些"角色"構(gòu)成的呢?

1、Bean: Spring作為一個(gè)IoC容器,最重要的當(dāng)然是Bean咯

2、BeanFactory: 生產(chǎn)與管理Bean的工廠

3、BeanDefinition: Bean的定義,也就是我們方案中的Class,Spring對(duì)它進(jìn)行了封裝

4、BeanDefinitionRegistry: 類似于Bean與BeanFactory的關(guān)系,BeanDefinitionRegistry用于管理BeanDefinition

5、BeanDefinitionRegistryPostProcessor: 用于在解析配置類時(shí)的處理器,類似于我們方案中的ClassProcessor

6、BeanFactoryPostProcessor: BeanDefinitionRegistryPostProcessor父類,讓我們可以再解析配置類之后進(jìn)行后置處理

7、BeanPostProcessor: Bean的后置處理器,用于在生產(chǎn)Bean的過程中進(jìn)行一些處理,比如依賴注入,類似我們的AutowiredAnnotationBeanProcessor

8、ApplicationContext: 如果說以上的角色都是在工廠中生產(chǎn)Bean的工人,那么ApplicationContext就是我們Spring的門面,ApplicationContext與BeanFactory是一種組合的關(guān)系,所以它完全擴(kuò)展了BeanFactory的功能,并在其基礎(chǔ)上添加了更多特定于企業(yè)的功能,比如我們熟知的ApplicationListener(事件監(jiān)聽器)

以上說的類似其實(shí)有一些本末倒置了,因?yàn)閷?shí)際上應(yīng)該是我們方案中的實(shí)現(xiàn)類似于Spring中的實(shí)現(xiàn),這樣說只是為了讓大家更好的理解

我們?cè)诮?jīng)歷了自己方案的設(shè)計(jì)與優(yōu)化后,對(duì)這些角色其實(shí)是非常容易理解的

接下來,我們就一個(gè)一個(gè)的詳細(xì)了解一下

BeanFactory

BeanFactory是Spring中的一個(gè)頂級(jí)接口,它定義了獲取Bean的方式,Spring中還有另一個(gè)接口叫SingletonBeanRegistry,它定義的是操作單例Bean的方式,這里我將這兩個(gè)放在一起進(jìn)行介紹,因?yàn)樗鼈兇篌w相同,SingletonBeanRegistry的注釋上也寫了可以與BeanFactory接口一起實(shí)現(xiàn),方便統(tǒng)一管理。

BeanFactory

1、ListableBeanFactory:接口,定義了獲取Bean/BeanDefinition列表相關(guān)的方法,如getBeansOfType(Class type)

2、AutowireCapableBeanFactory:接口,定義了Bean生命周期相關(guān)的方法,如創(chuàng)建bean, 依賴注入,初始化

3、AbstractBeanFactory:抽象類,基本上實(shí)現(xiàn)了所有有關(guān)Bean操作的方法,定義了Bean生命周期相關(guān)的抽象方法

4、AbstractAutowireCapableBeanFactory:抽象類,繼承了AbstractBeanFactory,實(shí)現(xiàn)了Bean生命周期相關(guān)的內(nèi)容,雖然是個(gè)抽象類,但它沒有抽象方法

5、DefaultListableBeanFactory:繼承與實(shí)現(xiàn)以上所有類和接口,是為Spring中最底層的BeanFactory, 自身實(shí)現(xiàn)了ListableBeanFactory接口

6、ApplicationContext:也是一個(gè)接口,我們會(huì)在下面有專門對(duì)它的介紹

SingletonBeanRegistry

1、DefaultSingletonBeanRegistry: 定義了Bean的緩存池,類似于我們的BeanMap,實(shí)現(xiàn)了有關(guān)單例的操作,比如getSingleton(面試常問的三級(jí)緩存就在這里)

2、FactoryBeanRegistrySupport:提供了對(duì)FactoryBean的支持,比如從FactoryBean中獲取Bean

BeanDefinition

BeanDefinition其實(shí)也是個(gè)接口(想不到吧),這里定義了許多和類信息相關(guān)的操作方法,方便在生產(chǎn)Bean的時(shí)候直接使用,比如getBeanClassName

它的大概結(jié)構(gòu)如下(這里舉例RootBeanDefinition子類):

里面的各種屬性想必大家也絕不陌生

同樣的,它也有許多實(shí)現(xiàn)類:

1、AnnotatedGenericBeanDefinition:解析配置類與解析Import注解帶入的類時(shí),就會(huì)使用它進(jìn)行封裝

2、ScannedGenericBeanDefinition:封裝通過@ComponentScan掃描包所得到的類信息

3、ConfigurationClassBeanDefinition:封裝通過@Bean注解所得到的類信息

4、RootBeanDefinition:ConfigurationClassBeanDefinition父類,一般在Spring內(nèi)部使用,將其他的BeanDefition轉(zhuǎn)化成該類

BeanDefinitionRegistry

定義了與BeanDefiniton相關(guān)的操作,如registerBeanDefinition,getBeanDefinition,在BeanFactory中,實(shí)現(xiàn)類就是DefaultListableBeanFactory

BeanDefinitionRegistryPostProcessor

插話:講到這里,有沒有發(fā)現(xiàn)Spring的命名極其規(guī)范,Spring團(tuán)隊(duì)曾言Spring中的類名都是反復(fù)推敲才確認(rèn)的,真是名副其實(shí)呀,所以看Spring源碼真的是一件很舒服的事情,看看類名方法名就能猜出它們的功能了。

該接口只定義了一個(gè)功能:處理BeanDefinitonRegistry,也就是解析配置類中的Import、Component、ComponentScan等注解進(jìn)行相應(yīng)的處理,處理完畢后將這些類注冊(cè)成對(duì)應(yīng)的BeanDefinition

在Spring內(nèi)部中,只有一個(gè)實(shí)現(xiàn):ConfigurationClassPostProcessor

BeanFactoryPostProcessor

所謂BeanFactory的后置處理器,它定義了在解析完配置類后可以調(diào)用的處理邏輯,類似于一個(gè)插槽,如果我們想在配置類解析完后做點(diǎn)什么,就可以實(shí)現(xiàn)該接口。

在Spring內(nèi)部中,同樣只有ConfigurationClassPostProcessor實(shí)現(xiàn)了它:用于專門處理加了Configuration注解的類

這里串場(chǎng)一個(gè)小問題,如知以下代碼:

  1. @Configuraiton 
  2. public class MyConfiguration{ 
  3.   @Bean 
  4.   public Car car(){ 
  5.       return new Car(wheel()); 
  6.   } 
  7.   @Bean 
  8.   public Wheel wheel(){ 
  9.       return new Wheel(); 
  10.   } 

問:Wheel對(duì)象在Spring啟動(dòng)時(shí),被new了幾次?為什么?

BeanPostProcessor

江湖翻譯:Bean的后置處理器

該后置處理器貫穿了Bean的生命周期整個(gè)過程,在Bean的創(chuàng)建過程中,一共被調(diào)用了9次,至于哪9次我們下次再來探究,以下介紹它的實(shí)現(xiàn)類以及作用

1、AutowiredAnnotationBeanPostProcessor:用于推斷構(gòu)造器進(jìn)行實(shí)例化,以及處理Autowired和Value注解

2、CommonAnnotationBeanPostProcessor:處理Java規(guī)范中的注解,如Resource、PostConstruct

3、ApplicationListenerDetector: 在Bean的初始化后使用,將實(shí)現(xiàn)了ApplicationListener接口的bean添加到事件監(jiān)聽器列表中

4、ApplicationContextAwareProcessor:用于回調(diào)實(shí)現(xiàn)了Aware接口的Bean

5、ImportAwareBeanPostProcessor: 用于回調(diào)實(shí)現(xiàn)了ImportAware接口的Bean

ApplicationContext

ApplicationContext作為Spring的核心,以門面模式隔離了BeanFactory,以模板方法模式定義了Spring啟動(dòng)流程的骨架,又以策略模式調(diào)用了各式各樣的Processor......實(shí)在是錯(cuò)綜復(fù)雜又精妙絕倫!

它的實(shí)現(xiàn)類如下:

1、ConfigurableApplicationContext:接口,定義了配置與生命周期相關(guān)操作,如refresh

2、AbstractApplicationContext: 抽象類,實(shí)現(xiàn)了refresh方法,refresh方法作為Spring核心中的核心,可以說整個(gè)Spring皆在refresh之中,所有子類都通過refresh方法啟動(dòng),在調(diào)用該方法之后,將實(shí)例化所有單例

3、AnnotationConfigApplicationContext: 在啟動(dòng)時(shí)使用相關(guān)的注解讀取器與掃描器,往Spring容器中注冊(cè)需要用的處理器,而后在refresh方法在被主流程調(diào)用即可

4、AnnotationConfigWebApplicationContext:實(shí)現(xiàn)loadBeanDefinitions方法,以期在refresh流程中被調(diào)用,從而加載BeanDefintion

5、ClassPathXmlApplicationContext: 同上

從子類的情況可以看出,子類的不同之處在于如何加載BeanDefiniton, AnnotationConfigApplicationContext是通過配置類處理器(ConfigurationClassPostProcessor)加載的,而AnnotationConfigWebApplicationContext與ClassPathXmlApplicationContext則是通過自己實(shí)現(xiàn)loadBeanDefinitions方法,其他流程則完全一致

Spring的流程

以上,我們已經(jīng)清楚了Spring中的主要角色以及作用,現(xiàn)在我們嘗試把它們組合起來,構(gòu)建一個(gè)Spring的啟動(dòng)流程

同樣以我們常用的AnnotationConfigApplicationContext為例

圖中只畫出了Spring中的部分大概流程,詳細(xì)內(nèi)容我們會(huì)在后面的章節(jié)展開

小結(jié)

所謂萬事開頭難,本文初衷就是能讓大家以由淺入深的方式認(rèn)識(shí)Spring,初步建立Spring的認(rèn)知體系,明白Spring的內(nèi)部架構(gòu),對(duì)Spring的認(rèn)知不再浮于表面。

現(xiàn)在頭已經(jīng)開了,相信后面內(nèi)容的學(xué)習(xí)也將水到渠來。

本篇文章既講是Spring的架構(gòu)設(shè)計(jì),也希望能成為我們以后復(fù)習(xí)Spring整體內(nèi)容時(shí)使用的手冊(cè)。

最后,看完文章之后,相信對(duì)以下面試常問的問題回答起來也是輕而易舉

1、什么是BeanDefinition?

2、BeanFactory與ApplicationContext的關(guān)系?

3、后置處理器的分類與作用?

4、Spring的主要流程是怎么樣的?

如果小伙伴覺得沒辦法很好回答上來的話就再看看文章,或者在評(píng)論區(qū)留下自己的見解吧

好啦,我是敖丙,你知道的越多,你不知道的越多,我們下期見。

 

責(zé)任編輯:姜華 來源: 三太子敖丙
相關(guān)推薦

2022-01-13 09:38:25

Android架構(gòu)設(shè)計(jì)

2025-03-27 09:38:35

2021-03-16 08:54:35

AQSAbstractQueJava

2011-07-04 10:39:57

Web

2021-07-20 15:20:02

FlatBuffers阿里云Java

2017-07-02 18:04:53

塊加密算法AES算法

2019-01-07 15:29:07

HadoopYarn架構(gòu)調(diào)度器

2012-05-21 10:06:26

FrameworkCocoa

2020-05-27 20:25:47

SpringSpringBoot數(shù)據(jù)

2022-09-26 09:01:15

語言數(shù)據(jù)JavaScript

2017-06-06 15:24:13

springElasticSear架構(gòu)

2017-07-17 11:52:54

jQuery源碼分析前端框架類庫

2022-09-29 09:19:04

線程池并發(fā)線程

2011-01-27 10:11:46

J2EEjavaspring

2019-11-11 14:51:19

Java數(shù)據(jù)結(jié)構(gòu)Properties

2009-11-30 16:46:29

學(xué)習(xí)Linux

2018-11-09 16:24:25

物聯(lián)網(wǎng)云計(jì)算云系統(tǒng)

2021-04-27 08:54:43

ConcurrentH數(shù)據(jù)結(jié)構(gòu)JDK8

2022-11-09 08:06:15

GreatSQLMGR模式

2012-02-21 13:55:45

JavaScript
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

精品视频在线视频| 99在线热播精品免费| 日韩亚洲成人av在线| 国产色视频在线播放| 国产不卡在线| 成人免费毛片a| 国产成人avxxxxx在线看| 91成人精品一区二区| 日韩08精品| 欧美日韩中文字幕日韩欧美| 亚洲高清视频一区| 高清毛片aaaaaaaaa片| 鲁大师成人一区二区三区| 在线视频欧美日韩| 岛国精品一区二区三区| 亚洲欧美在线成人| 一区二区三区在线免费观看| 免费国产在线精品一区二区三区| ,一级淫片a看免费| 在线亚洲国产精品网站| 色香阁99久久精品久久久| 免费黄视频在线观看| 老司机成人影院| 亚洲另类在线制服丝袜| 日本不卡一区二区三区视频| 国产91视频在线| 日韩中文字幕一区二区三区| 色在人av网站天堂精品| 久久久久亚洲av无码a片| 成人高潮a毛片免费观看网站| 91极品美女在线| 免费网站永久免费观看| 男人的天堂在线视频免费观看| 91伊人久久大香线蕉| 成人久久精品视频| www.国产一区二区| 在线观看不卡| 欧美xxxx18性欧美| 999久久久国产| 亚洲瘦老头同性70tv| 日韩女优视频免费观看| 182午夜在线观看| 蜜桃视频成人m3u8| 欧美天天综合色影久久精品| 少妇久久久久久被弄到高潮| 天堂地址在线www| 久久久精品欧美丰满| 国产精品一区二区免费| www.日韩在线观看| 国产在线看一区| 国产精品久久久一区| 性无码专区无码| 亚洲精品视频啊美女在线直播| 色中色综合影院手机版在线观看| 亚洲天堂网av在线| 91蜜臀精品国产自偷在线| 国产亚洲精品成人av久久ww| 五月婷婷综合在线观看| 欧美91在线| 日韩av中文字幕在线免费观看| 女性生殖扒开酷刑vk| 中文字幕日韩高清在线| 精品日韩99亚洲| 熟女人妻一区二区三区免费看| 久久av网站| 欧美mv日韩mv| 尤物网站在线观看| 日韩av黄色在线| 日韩电影视频免费| 中文字幕在线观看网址| 亚洲小说图片| 国产一区二区三区视频在线观看| 亚洲国产天堂av| 久久一区二区中文字幕| 久热精品视频在线| 久久久久无码国产精品不卡| 国内揄拍国内精品久久| 91高清视频免费| 黄色av一级片| 久久国产剧场电影| 91久久爱成人| 婷婷视频在线观看| 国产女人aaa级久久久级| 五月天国产一区| 国产精品一区二区三区视频网站| 亚洲免费观看视频| 国产精品裸体瑜伽视频| 日本欧美日韩| 欧美一级日韩一级| 内射中出日韩无国产剧情| 国产中文字幕一区二区三区| 日韩视频在线免费观看| 欧美色图一区二区| 久久av最新网址| 国产精品网红福利| 黄色aaa大片| 欧美韩日一区二区三区| 青青草综合视频| 日韩精品99| 日韩免费成人网| 久久精品—区二区三区舞蹈| 91精品国产调教在线观看| 91精品国产91久久| 91尤物国产福利在线观看| 91免费视频网址| 日本黄色播放器| 韩日毛片在线观看| 91麻豆精品国产91久久久 | 亚洲精品一区二区三区蜜桃下载 | 亚洲宅男天堂在线观看无病毒| 男女高潮又爽又黄又无遮挡| 91精品国产色综合久久不卡粉嫩| 日韩国产欧美精品在线| 日韩一级片av| 蜜桃视频在线一区| 麻豆成人小视频| 手机av在线播放| 色综合av在线| 一区二区三区人妻| 国产欧美高清视频在线| 欧美日韩国产91| 国产精品伦一区二区三区| 99久久伊人精品| 宅男av一区二区三区| 九色porny丨入口在线| 欧美日韩大陆一区二区| 91福利视频免费观看| 久久99国产精一区二区三区| 欧美另类在线播放| 亚洲天堂视频网| 26uuu另类欧美亚洲曰本| 欧美黄网在线观看| 无人区在线高清完整免费版 一区二| 91精品国产色综合久久久蜜香臀| 免费看黄色的视频| 亚洲激情不卡| 成人网中文字幕| 国产一级网站视频在线| 亚洲一区二区三区美女| 欧美激情第3页| 免费黄色成人| 热99精品里视频精品| 成 人 免费 黄 色| 亚洲欧洲精品成人久久奇米网| 亚洲熟妇av一区二区三区| 大型av综合网站| 欧美—级高清免费播放| 国产乱淫a∨片免费观看| 国产精品人人做人人爽人人添| 能在线观看的av| 国产精品nxnn| 韩国日本不卡在线| 欧美一级性视频| 亚洲一级片在线观看| 免费成人黄色大片| 日韩欧美精品| 成人精品在线视频| 精品麻豆一区二区三区| 欧美日韩精品一区二区天天拍小说| 三上悠亚ssⅰn939无码播放 | 99精品视频在线免费观看| 日本一区二区三区四区五区六区| 日本欧美在线| 久久成人免费视频| 国产色视频在线| 中文字幕中文字幕一区| gogogo高清免费观看在线视频| 欧美三级午夜理伦三级在线观看| 91精品国产高清自在线| 丰满人妻一区二区三区无码av| 亚洲欧洲中文日韩久久av乱码| 午夜福利123| 日韩在线看片| 国产精品视频播放| 91福利在线视频| 丰满岳妇乱一区二区三区| 奇米777第四色| 制服诱惑一区二区| 精品日本一区二区三区| 黑人巨大亚洲一区二区久| 亚洲日韩欧美视频一区| 最近中文字幕在线免费观看| 欧美韩日一区二区三区| 一区二区三区视频在线观看免费| 一本一本久久a久久综合精品| 亚洲最大福利视频| 激情av在线| 精品呦交小u女在线| 亚洲图片欧美在线| 一区二区三区欧美在线观看| 娇妻高潮浓精白浆xxⅹ| 国产亚洲毛片| 日韩不卡一二区| 黑人久久a级毛片免费观看| 欧美亚洲午夜视频在线观看| 国产区视频在线播放| 欧美欧美欧美欧美| 欧美福利视频一区二区| 久久久久国色av免费看影院| 粉色视频免费看| 国产专区一区| 亚洲精品日韩精品| 99亚洲乱人伦aⅴ精品| 538国产精品一区二区在线| 2021av在线| 欧美tk丨vk视频| 亚洲性生活大片| 亚洲午夜免费视频| 国产1区2区在线观看| 国产精品99久久久久久有的能看| 欧美私人情侣网站| 影音先锋日韩精品| 免费精品视频一区| 日本一区二区三区视频在线看| 欧美精品电影在线| 韩国三级在线观看久| 538prom精品视频线放| 国产综合精品视频| 国产免费久久精品| 自拍视频一区二区| 韩国女主播成人在线| 免费毛片小视频| 亚洲乱码电影| 一区二区三区欧美成人| 欧美变态网站| 96pao国产成视频永久免费| 免费一二一二在线视频| 在线观看久久久久久| 四虎精品成人影院观看地址| 在线成人av影院| 无码人妻丰满熟妇精品区| 亚洲激情综合网| 国产精品视频一区二区在线观看| 91丝袜呻吟高潮美腿白嫩在线观看| 五月激情婷婷在线| 日韩va亚洲va欧美va久久| 1024精品视频| 国内精品久久久久久久97牛牛| 亚洲自拍偷拍二区| 香蕉国产成人午夜av影院| 国产亚洲二区| 色播一区二区| 成人性生交xxxxx网站| 国产综合av| 97视频在线观看成人| 伊人精品影院| 久久福利视频导航| 黄色网页在线播放| 亚洲天堂精品在线| 国产无套粉嫩白浆在线2022年| 亚洲国产精品高清久久久| www.激情五月.com| 欧美一区二视频| 国产欧美久久久精品免费| 欧美日韩电影在线| 一区二区视频免费观看| 欧美久久婷婷综合色| 中文字幕天堂在线| 色天使色偷偷av一区二区| 国产无套在线观看| 欧美日韩国产专区| 国产综合精品视频| 欧美天天综合色影久久精品| 中文字幕xxxx| 在线欧美一区二区| 制服丝袜在线一区| 欧美视频一区二区三区在线观看| 久久久成人免费视频| 欧洲视频一区二区| 亚洲一级av毛片| 在线不卡中文字幕播放| 国产露脸国语对白在线| 精品成人一区二区三区| 神宫寺奈绪一区二区三区| 日韩av在线天堂网| 免费在线毛片| 一本色道久久综合狠狠躁篇怎么玩 | 一区二区视频欧美| 日本人体一区二区| 久久亚洲美女| 五月婷婷丁香色| 国产综合色产在线精品| 免费欧美一级片| 久久这里只有精品6| 中文字幕第20页| 国产精品久久久久久久第一福利| 国产一级淫片久久久片a级| 一区二区三区四区蜜桃| 日韩精品国产一区二区| 欧美视频国产精品| ,一级淫片a看免费| 精品国产91久久久久久久妲己 | 国精品人伦一区二区三区蜜桃| 亚洲欧美日韩国产综合在线| 久青草视频在线观看| 性久久久久久久| 无码人妻精品一区二区三区不卡| 欧美一级欧美三级在线观看| 欧美一级免费片| 色综合亚洲精品激情狠狠| 激情av在线播放| 日韩美女中文字幕| 成人噜噜噜噜| 精品在线视频一区二区三区| 久久久久久久久久久9不雅视频 | 狠狠狠综合7777久夜色撩人| xxxx欧美18另类的高清| 99色在线观看| 亚洲在线免费观看| 亚洲小说图片| 国产麻豆电影在线观看| 老司机午夜精品视频| 女王人厕视频2ⅴk| 国产午夜精品一区二区| 老妇女50岁三级| 精品视频1区2区| 天天操天天干天天舔| 正在播放亚洲1区| 密臀av在线播放| 成人高清在线观看| 成人三级视频| 欧美 日韩 亚洲 一区| 国产亚洲精品久久久久婷婷瑜伽| 蜜桃色一区二区三区| 国产精品私人影院| 日韩黄色精品视频| 精品久久久久av影院| 四虎久久免费| 国产精品777| 最新亚洲精品| 精品视频在线观看一区| 国产麻豆欧美日韩一区| 少妇愉情理伦三级| 欧美三级电影网| 日本又骚又刺激的视频在线观看| 欧美日本亚洲视频| 欧美成人家庭影院| 亚洲免费在线精品一区| 久久高清国产| 亚洲一区二区三区四区五区六区| 亚洲一区二区偷拍精品| 99热这里只有精品在线观看| 一区二区亚洲精品国产| 亚洲午夜天堂| 精品一区二区三区国产| 欧美日韩一卡| 女人扒开腿免费视频app| 亚洲激情男女视频| 国产乱码一区二区| 久久精品这里热有精品| 久久丁香四色| 国产在线无码精品| 国内成人自拍视频| 丝袜美腿小色网| 91精品国产麻豆| 青青影院在线观看| 成人激情在线播放| 亚洲视频中文| 日本不卡视频一区| 亚洲国产精品精华液网站| 色偷偷在线观看| 久久久久久久色| 99热这里只有精品首页| 浮妇高潮喷白浆视频| 99精品视频一区二区| 91美女免费看| 日韩一卡二卡三卡| 1区2区3区在线| 久久www免费人成精品| 国内精品久久久久久久97牛牛| 强迫凌虐淫辱の牝奴在线观看| 天天做天天摸天天爽国产一区| 天天色天天操天天射| 91国语精品自产拍在线观看性色| 欧美sss在线视频| 国产成人精品无码播放| 2021中文字幕一区亚洲| 在线观看国产黄| 欧美xxxx综合视频| 精品福利网址导航| 干日本少妇首页| 亚洲天堂网中文字| 精品人妻一区二区三区麻豆91 | 欧美日韩一区在线| 黄色成人在线| 成人免费视频网站入口| 久久久噜噜噜| 亚洲AV成人无码网站天堂久久| 欧美精品第1页| 美女扒开腿让男人桶爽久久软| 久久综合一区| 久久99久久99精品免视看婷婷| 三上悠亚影音先锋| 日韩欧美成人一区| 亚洲最新无码中文字幕久久| 欧美日韩一区二区视频在线观看 | 中文字幕资源在线观看| 亚洲午夜在线电影| 国产高清在线观看| 97netav|