aop实现原理_Java:由浅入深揭开 AOP 实现原理
點擊上方“Java專欄”,選擇“置頂或者星標”
第一時間閱讀精彩文章!
1、? 程序員進階必備資源免費送「21種技術方向!」 點擊查看?2、??《Java面試手冊》.PDF? ??點擊查看
作者:馬佩
juejin.im/post/5bf4fc84f265da611b57f906
概述:
最近在開發中遇到了一個剛好可以用AOP實現的例子,就順便研究了AOP的實現原理,把學習到的東西進行一個總結。文章中用到的編程語言為kotlin,需要的可以在IDEA中直接轉為java。
這篇文章將會按照如下目錄展開:
AOP簡介
代碼中實現舉例
AOP實現原理
部分源碼解析
1. AOP簡介
相信大家或多或少的了解過AOP,都知道它是面向切面編程,在網上搜索可以找到很多的解釋。這里我用一句話來總結:AOP是能夠讓我們在不影響原有功能的前提下,為軟件橫向擴展功能。
那么橫向擴展怎么理解呢,我們在WEB項目開發中,通常都遵守三層原則,包括控制層(Controller)->業務層(Service)->數據層(dao),那么從這個結構下來的為縱向,它具體的某一層就是我們所說的橫向。我們的AOP就是可以作用于這某一個橫向模塊當中的所有方法。
我們在來看一下AOP和OOP的區別:AOP是OOP的補充,當我們需要為多個對象引入一個公共行為,比如日志,操作記錄等,就需要在每個對象中引用公共行為,這樣程序就產生了大量的重復代碼,使用AOP可以完美解決這個問題。
接下來介紹一下提到AOP就必須要了解的知識點:
切面:攔截器類,其中會定義切點以及通知
切點:具體攔截的某個業務點。
通知:切面當中的方法,聲明通知方法在目標業務層的執行位置,通知類型如下:
前置通知:@Before 在目標業務方法執行之前執行
后置通知:@After 在目標業務方法執行之后執行
返回通知:@AfterReturning 在目標業務方法返回結果之后執行
異常通知:@AfterThrowing 在目標業務方法拋出異常之后
環繞通知:@Around 功能強大,可代替以上四種通知,還可以控制目標業務方法是否執行以及何時執行
2. 代碼中實現舉例
上面已經大概的介紹了AOP中需要了解的基本知識,也知道了AOP的好處,那怎么在代碼中實現呢?給大家舉個例子:我們現在有個學校管理系統,已經實現了對老師和學生的增刪改,又新來個需求,說是對老師和學生的每次增刪改做一個記錄,到時候校長可以查看記錄的列表。
那么問題來了,怎么樣處理是最好的解決辦法呢?這里我羅列了三種解決辦法,我們來看下他的優缺點。
最簡單的就是第一種方法,我們直接在每次的增刪改的函數當中直接實現這個記錄的方法,這樣代碼的重復度太高,耦合性太強,不建議使用。
其次就是我們最長使用的,將記錄這個方法抽離出來,其他的增刪改調用這個記錄函數即可,顯然代碼重復度降低,但是這樣的調用還是沒有降低耦合性。
這個時候我們想一下AOP的定義,再想想我們的場景,其實我們就是要在不改變原來增刪改的方法,給這個系統增加記錄的方法,而且作用的也是一個層面的方法。這個時候我們就可以采用AOP來實現了。
我們來看下代碼的具體實現:
1,首先我定義了一個自定義注解作為切點
@Target(AnnotationTarget.FUNCTION)??//注解作用的范圍,這里聲明為函數
@Order(Ordered.HIGHEST_PRECEDENCE)??
//聲明注解的優先級為最高,假設有多個注解,先執行這個
annotation?class?Hanler(val?handler:?HandlerType)??//自定義注解類,HandlerType是一個枚舉類型,里面定義的就是學生和老師的增刪改操作,在這里就不展示具體內容了
2,接下來就是要定義切面類了
@Aspect???//該注解聲明這個類為一個切面類@Component
class?HandlerAspect{
?@Autowired
?private?lateinit?var?handlerService:?HandlerService
@AfterReturning("@annotation(handler)")???//當有函數注釋了注解,將會在函數正常返回后在執行我們定義的方法
fun?hanler(hanler:?Hanler)?{
????handlerService.add(handler.operate.value)???//這里是真正執行記錄的方法
}
}
3,最后就是我們本來的業務方法了
/***?刪除學生方法
*/
@Handler(operate=?Handler.STUDENT_DELETE)???//當執行到刪除學生方法時,切面類就會起作用了,當學生正常刪除后就會執行記錄方法,我們就可以看到記錄方法生成的數據
fun?delete(id:String)?{
???studentService.delete(id)
}
3. AOP實現原理
我們現在了解了代碼中如何實現,那么AOP實現的原理是什么呢?之前看了一個博客說到,提到AOP大家都知道他的實現原理是動態代理,顯然我之前就是不知道的,哈哈,但是相信閱讀文章的你們一定是知道的。
講到動態代理就不得不說代理模式了,代理模式的定義:給某一個對象提供一個代理,并由代理對象控制對原對象的引用。
代理模式包含如下角色:
subject:抽象主題角色,是一個接口。該接口是對象和它的代理共用的接口;
RealSubject:真實主題角色,是實現抽象主題接口的類;
Proxy:代理角色,內部含有對真實對象RealSubject的引用,從而可以操作真實對象。
代理對象提供與真實對象相同的接口,以便代替真實對象。同時,代理對象可以在執行真實對象操作時,附加其他的操作,相當于對真實對象進行封裝。如下圖所示:
那么代理又分為靜態代理和動態代理,這里寫兩個小的demo,動態代理采用的就是JDK代理。舉個例子就是現在一個班上的學生需要交作業,現在由班長代理交作業,那么班長就是代理,學生就是被代理的對象。
3.1 靜態代理
首先,我們創建一個Person接口。這個接口就是學生(被代理類),和班長(代理類)的公共接口,他們都有交作業的行為。這樣,學生交作業就可以讓班長來代理執行。
/**?*?創建person接口
?*/
public?interface?Person?{
????//交作業
????void?giveTask();
}
Student類實現Person接口,Student可以具體實施交作業這個行為。
public?class?Student?implements?Person?{????private?String?name;
????public?Student(String?name)?{
????????this.name?=?name;
????}
????public?void?giveTask()?{
????????System.out.println(name?+?"交語文作業");
????}
}
StudentsProxy類,這個類也實現了Person接口,但是還另外持有一個學生類對象,那么他可以代理學生類對象執行交作業的行為。
/**?*?學生代理類,也實現了Person接口,保存一個學生實體,這樣就可以代理學生產生行為
?*/
public?class?StudentsProxy?implements?Person{
????//被代理的學生
????Student?stu;
????public?StudentsProxy(Person?stu)?{
????????//?只代理學生對象
????????if(stu.getClass()?==?Student.class)?{
????????????this.stu?=?(Student)stu;
????????}
????}
????//代理交作業,調用被代理學生的交作業的行為
????public?void?giveTask()?{
????????stu.giveTask();
????}
}
下面測試一下,看代理模式如何使用:
public?class?StaticProxyTest?{????public?static?void?main(String[]?args)?{
????????//被代理的學生林淺,他的作業上交有代理對象monitor完成
????????Person?linqian?=?new?Student("林淺");
????????//生成代理對象,并將林淺傳給代理對象
????????Person?monitor?=?new?StudentsProxy(linqian);
????????//班長代理交作業
????????monitor.giveTask();
????}
}
運行結果:
這里并沒有直接通過林淺(被代理對象)來執行交作業的行為,而是通過班長(代理對象)來代理執行了。這就是代理模式。
代理模式就是在訪問實際對象時引入一定程度的間接性,這里的間接性就是指不直接調用實際對象的方法,那么我們在代理過程中就可以加上一些其他用途。
比如班長在幫林淺交作業的時候想告訴老師最近林淺的進步很大,就可以輕松的通過代理模式辦到。在代理類的交作業之前加入方法即可。這個優點就可以運用在spring中的AOP,我們能在一個切點之前執行一些操作,在一個切點之后執行一些操作,這個切點就是一個個方法。這些方法所在類肯定就是被代理了,在代理過程中切入了一些其他操作。
3.2 動態代理
動態代理和靜態代理的區別是,靜態代理的的代理類是我們自己定義好的,在程序運行之前就已經變異完成,但是動態代理的代理類是在程序運行時創建的。
相比于靜態代理,動態代理的優勢在于可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。比如我們想在每個代理方法之前都加一個處理方法,我們上面的例子中只有一個代理方法,如果還有很多的代理方法,就太麻煩了,我們來看下動態代理是怎么去實現的。
首先還是定義一個Person接口:
/**?*?創建person接口
?*/
public?interface?Person?{
????//交作業
????void?giveTask();
}
接下來是創建需要被代理的實際類,也就是學生類:
public?class?Student?implements?Person?{????private?String?name;
????public?Student(String?name)?{
????????this.name?=?name;
????}
????public?void?giveTask()?{
????????System.out.println(name?+?"交語文作業");
????}
}
創建StuInvocationHandler類,實現InvocationHandler接口,這個類中持有一個被代理對象的實例target。InvocationHandler中有一個invoke方法,所有執行代理對象的方法都會被替換成執行invoke方法。
public?class?StuInvocationHandler?implements?InvocationHandler?{????//invocationHandler持有的被代理對象
????T?target;
????public?StuInvocationHandler(T?target)?{
????????this.target?=?target;
????}
????/**
?????*?proxy:代表動態代理對象
?????* method:代表正在執行的方法
?????* args:代表調用目標方法時傳入的實參
?????*/
????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
????????System.out.println("代理執行"?+method.getName()?+?"方法");
????????Object?result?=?method.invoke(target,?args);
????????return?result;
????}
}
那么接下來我們就可以具體的創建代理對象了。
/**?*?代理類
?*/
public?class?ProxyTest?{
????public?static?void?main(String[]?args)?{
????????//創建一個實例對象,這個對象是被代理的對象
????????Person?linqian?=?new?Student("林淺");
????????//創建一個與代理對象相關聯的InvocationHandler
????????InvocationHandler?stuHandler?=?new?StuInvocationHandler(linqian);//創建一個代理對象stuProxy來代理linqian,代理對象的每個執行方法都會替換執行Invocation中的invoke方法
????????Person?stuProxy?=?(Person)?Proxy.newProxyInstance(Person.class.getClassLoader(),?new?Class>[]{Person.class},?stuHandler);//代理執行交作業的方法
????????stuProxy.giveTask();
????}
}
我們執行代理測試類,首先我們創建了一個需要被代理的學生林淺,將林淺傳入stuHandler中,我們在創建代理對象stuProxy時,將stuHandler作為參數,那么所有執行代理對象的方法都會被替換成執行invoke方法,也就是說,最后執行的是StuInvocationHandler中的invoke方法。所以在看到下面的運行結果也就理所當然了。
那么到這里問題就來了,為什么代理對象執行的方法都會通過InvocationHandler中的invoke方法來執行,帶著這個問題,我們需要看一下動態代理的源碼,對他進行簡單的分析。更多:動態代理解析
上面我們使用Proxy類的newProxyInstance方法創建了一個動態代理對象,看一下他的源碼:
public?static?Object?newProxyInstance(ClassLoader?loader,?Class>[]?interfaces,?InvocationHandler?h)throws?IllegalArgumentException{????????Objects.requireNonNull(h);
????????final?Class>[]?intfs?=?interfaces.clone();
????????final?SecurityManager?sm?=?System.getSecurityManager();
????????if?(sm?!=?null)?{
????????????checkProxyAccess(Reflection.getCallerClass(),?loader,?intfs);
????????}
????????/*
?????????*?Look?up?or?generate?the?designated?proxy?class.
?????????*/
????????Class>?cl?=?getProxyClass0(loader,?intfs);
????????/*
?????????*?Invoke?its?constructor?with?the?designated?invocation?handler.
?????????*/
????????try?{
????????????if?(sm?!=?null)?{
????????????????checkNewProxyPermission(Reflection.getCallerClass(),?cl);
????????????}
????????????final?Constructor>?cons?=?cl.getConstructor(constructorParams);
????????????final?InvocationHandler?ih?=?h;
????????????if?(!Modifier.isPublic(cl.getModifiers()))?{
????????????????AccessController.doPrivileged(new?PrivilegedAction()?{public?Void?run()?{
????????????????????????cons.setAccessible(true);return?null;
????????????????????}
????????????????});
????????????}return?cons.newInstance(new?Object[]{h});
????????}?catch?(IllegalAccessException|InstantiationException?e)?{throw?new?InternalError(e.toString(),?e);
????????}?catch?(InvocationTargetException?e)?{
????????????Throwable?t?=?e.getCause();if?(t?instanceof?RuntimeException)?{throw?(RuntimeException)?t;
????????????}?else?{throw?new?InternalError(t.toString(),?t);
????????????}
????????}?catch?(NoSuchMethodException?e)?{throw?new?InternalError(e.toString(),?e);
????????}
??}
然后,我們需要重點關注Class> cl = getProxyClass0(loader, intfs)這句代碼,這里產生了代理類,這個類就是動態代理的關鍵,由于是動態生成的類文件,我們將這個類文件打印到文件中。
???????byte[]?classFile?=?ProxyGenerator.generateProxyClass("$Proxy0",?Student.class.getInterfaces());????????String?path?=?"/Users/mapei/Desktop/okay/65707.class";
????????try{
????????????FileOutputStream?fos?=?new?FileOutputStream(path);
????????????fos.write(classFile);
????????????fos.flush();
????????????System.out.println("代理類class文件寫入成功");
????????}catch?(Exception?e)?{
????????????System.out.println("寫文件錯誤");
????????}
對這個class文件進行反編譯,我們看看jdk為我們生成了什么樣的內容:
import?java.lang.reflect.InvocationHandler;import?java.lang.reflect.Method;
import?java.lang.reflect.Proxy;
import?java.lang.reflect.UndeclaredThrowableException;
import?proxy.Person;
public?final?class?$Proxy0?extends?Proxy?implements?Person{
??private?static?Method?m1;
??private?static?Method?m2;
??private?static?Method?m3;
??private?static?Method?m0;
??/**
??*注意這里是生成代理類的構造方法,方法參數為InvocationHandler類型,看到這,是不是就有點明白
??*為何代理對象調用方法都是執行InvocationHandler中的invoke方法,而InvocationHandler又持有一個
??*被代理對象的實例,就可以去調用真正的對象實例。
??*/
??public?$Proxy0(InvocationHandler?paramInvocationHandler)
????throws?
??{
????super(paramInvocationHandler);
??}
??//這個靜態塊本來是在最后的,我把它拿到前面來,方便描述
???static
??{
????try
????{
??????//看看這兒靜態塊兒里面的住giveTask通過反射得到的名字m3,其他的先不管
??????m1?=?Class.forName("java.lang.Object").getMethod("equals",?new?Class[]?{?Class.forName("java.lang.Object")?});
??????m2?=?Class.forName("java.lang.Object").getMethod("toString",?new?Class[0]);
??????m3?=?Class.forName("proxy.Person").getMethod("giveTask",?new?Class[0]);
??????m0?=?Class.forName("java.lang.Object").getMethod("hashCode",?new?Class[0]);
??????return;
????}
????catch?(NoSuchMethodException?localNoSuchMethodException)
????{
??????throw?new?NoSuchMethodError(localNoSuchMethodException.getMessage());
????}
????catch?(ClassNotFoundException?localClassNotFoundException)
????{
??????throw?new?NoClassDefFoundError(localClassNotFoundException.getMessage());
????}
??}
??/**
??*?
??*這里調用代理對象的giveMoney方法,直接就調用了InvocationHandler中的invoke方法,并把m3傳了進去。
??*this.h.invoke(this, m3, null);我們可以對將InvocationHandler看做一個中介類,中介類持有一個被代理對象,在invoke方法中調用了被代理對象的相應方法。通過聚合方式持有被代理對象的引用,把外部對invoke的調用最終都轉為對被代理對象的調用。
??*/
??public?final?void?giveTask()throws?{
????try
????{
??????this.h.invoke(this,?m3,?null);
??????return;
????}
????catch?(Error|RuntimeException?localError)
????{
??????throw?localError;
????}
????catch?(Throwable?localThrowable)
????{
??????throw?new?UndeclaredThrowableException(localThrowable);
????}
??}
}
看完了動態代理的源碼,我們接下來就要看一下Spring中AOP實現的源碼是怎樣的?
4. 部分源碼解析
aop創建代理的源碼分析
1,看一下bean如何被包裝為proxy
???????????protected?Object?createProxy(???????????Class>?beanClass,?String?beanName,?Object[]?specificInterceptors,?TargetSource?targetSource)?{
???????if?(this.beanFactory?instanceof?ConfigurableListableBeanFactory)?{
???????????AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)?this.beanFactory,?beanName,?beanClass);
???????}
???????//?1.創建proxyFactory,proxy的生產主要就是在proxyFactory做的
???????ProxyFactory?proxyFactory?=?new?ProxyFactory();
???????proxyFactory.copyFrom(this);
???????if?(!proxyFactory.isProxyTargetClass())?{
???????????if?(shouldProxyTargetClass(beanClass,?beanName))?{
???????????????proxyFactory.setProxyTargetClass(true);
???????????}
???????????else?{
???????????????evaluateProxyInterfaces(beanClass,?proxyFactory);
???????????}
???????}
???????//?2.將當前bean適合的advice,重新封裝下,封裝為Advisor類,然后添加到ProxyFactory中
???????Advisor[]?advisors?=?buildAdvisors(beanName,?specificInterceptors);
???????for?(Advisor?advisor?:?advisors)?{
???????????proxyFactory.addAdvisor(advisor);
???????}
???????proxyFactory.setTargetSource(targetSource);
???????customizeProxyFactory(proxyFactory);
???????proxyFactory.setFrozen(this.freezeProxy);
???????if?(advisorsPreFiltered())?{
???????????proxyFactory.setPreFiltered(true);
???????}
???????//?3.調用getProxy獲取bean對應的proxy
???????return?proxyFactory.getProxy(getProxyClassLoader());
???}
2,創建何種類型的Proxy?JDKProxy還是CGLIBProxy?
????public?Object?getProxy(ClassLoader?classLoader)?{????????return?createAopProxy().getProxy(classLoader);
????}
????//?createAopProxy()方法就是決定究竟創建何種類型的proxy
????protected?final?synchronized?AopProxy?createAopProxy()?{
????????if?(!this.active)?{
????????????activate();
????????}
????????//?關鍵方法createAopProxy()
????????return?getAopProxyFactory().createAopProxy(this);
????}
????//?createAopProxy()
????public?AopProxy?createAopProxy(AdvisedSupport?config)?throws?AopConfigException?{
????????//?1.config.isOptimize()是否使用優化的代理策略,目前使用與CGLIB
????????//?config.isProxyTargetClass()?是否目標類本身被代理而不是目標類的接口
????????//?hasNoUserSuppliedProxyInterfaces()是否存在代理接口
????????if?(config.isOptimize()?||?config.isProxyTargetClass()?||?hasNoUserSuppliedProxyInterfaces(config))?{
????????????Class>?targetClass?=?config.getTargetClass();
????????????if?(targetClass?==?null)?{
????????????????throw?new?AopConfigException("TargetSource?cannot?determine?target?class:?"?+
????????????????????????"Either?an?interface?or?a?target?is?required?for?proxy?creation.");
????????????}
????????????//?2.如果目標類是接口類(目標對象實現了接口),則直接使用JDKproxy
????????????if?(targetClass.isInterface()?||?Proxy.isProxyClass(targetClass))?{
????????????????return?new?JdkDynamicAopProxy(config);
????????????}
????????????//?3.其他情況則使用CGLIBproxy
????????????return?new?ObjenesisCglibAopProxy(config);
????????}
????????else?{
????????????return?new?JdkDynamicAopProxy(config);
????????}
????}
3,getProxy()方法
???final?class?JdkDynamicAopProxy?implements?AopProxy,?InvocationHandler,?Serializable//?JdkDynamicAopProxy類結構,由此可知,其實現了InvocationHandler,則必定有invoke方法,來被調用,也就是用戶調用bean相關方法時,此invoke()被真正調用???//?getProxy()
???public?Object?getProxy(ClassLoader?classLoader)?{
???????if?(logger.isDebugEnabled())?{
???????????logger.debug("Creating?JDK?dynamic?proxy:?target?source?is?"?+?this.advised.getTargetSource());
???????}
???????Class>[]?proxiedInterfaces?=?AopProxyUtils.completeProxiedInterfaces(this.advised,?true);
???????findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
???????//?JDK?proxy?動態代理的標準用法
???????return?Proxy.newProxyInstance(classLoader,?proxiedInterfaces,?this);
???}
4,invoke()方法法
????//使用了JDK動態代理模式,真正的方法執行在invoke()方法里,看到這里在想一下上面動態代理的例子,是不是就完全明白Spring源碼實現動態代理的原理了。????????????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
????????MethodInvocation?invocation;
????????Object?oldProxy?=?null;
????????boolean?setProxyContext?=?false;
????????TargetSource?targetSource?=?this.advised.targetSource;
????????Class>?targetClass?=?null;
????????Object?target?=?null;
????????try?{
????????????//?1.以下的幾個判斷,主要是為了判斷method是否為equals、hashCode等Object的方法
????????????if?(!this.equalsDefined?&&?AopUtils.isEqualsMethod(method))?{
????????????????//?The?target?does?not?implement?the?equals(Object)?method?itself.
????????????????return?equals(args[0]);
????????????}
????????????else?if?(!this.hashCodeDefined?&&?AopUtils.isHashCodeMethod(method))?{
????????????????//?The?target?does?not?implement?the?hashCode()?method?itself.
????????????????return?hashCode();
????????????}
????????????else?if?(method.getDeclaringClass()?==?DecoratingProxy.class)?{
????????????????//?There?is?only?getDecoratedClass()?declared?->?dispatch?to?proxy?config.
????????????????return?AopProxyUtils.ultimateTargetClass(this.advised);
????????????}
????????????else?if?(!this.advised.opaque?&&?method.getDeclaringClass().isInterface()?&&
????????????????????method.getDeclaringClass().isAssignableFrom(Advised.class))?{
????????????????//?Service?invocations?on?ProxyConfig?with?the?proxy?config...
????????????????return?AopUtils.invokeJoinpointUsingReflection(this.advised,?method,?args);
????????????}
????????????Object?retVal;
????????????if?(this.advised.exposeProxy)?{
????????????????//?Make?invocation?available?if?necessary.
????????????????oldProxy?=?AopContext.setCurrentProxy(proxy);
????????????????setProxyContext?=?true;
????????????}
????????????//?May?be?null.?Get?as?late?as?possible?to?minimize?the?time?we?"own"?the?target,
????????????//?in?case?it?comes?from?a?pool.
????????????target?=?targetSource.getTarget();
????????????if?(target?!=?null)?{
????????????????targetClass?=?target.getClass();
????????????}
????????????//?2.獲取當前bean被攔截方法鏈表
????????????List?chain?=?this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,?targetClass);//?3.如果為空,則直接調用target的methodif?(chain.isEmpty())?{
????????????????Object[]?argsToUse?=?AopProxyUtils.adaptArgumentsIfNecessary(method,?args);
????????????????retVal?=?AopUtils.invokeJoinpointUsingReflection(target,?method,?argsToUse);
????????????}//?4.不為空,則逐一調用chain中的每一個攔截方法的proceed,這里的一系列執行的原因以及proceed執行的內容,我?在這里就不詳細講了,大家感興趣可以自己去研讀哈else?{//?We?need?to?create?a?method?invocation...
????????????????invocation?=?new?ReflectiveMethodInvocation(proxy,?target,?method,?args,?targetClass,?chain);//?Proceed?to?the?joinpoint?through?the?interceptor?chain.
????????????????retVal?=?invocation.proceed();
????????????}
????????????...return?retVal;
????????}
????}
????}
那么到了這里,我要講的內容就差不多結束了,如果有什么不對的,或者有什么疑惑,歡迎大家指點
以上,便是今天的分享,希望大家喜歡,覺得內容不錯的,歡迎點擊「在看」支持,謝謝各位
喜歡文章,點個在看?總結
以上是生活随笔為你收集整理的aop实现原理_Java:由浅入深揭开 AOP 实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 75寸电视跌出白菜价 LCD面板价格继续
- 下一篇: 缤智的尺寸、皓影的价格!广汽本田e:NP