成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

扒一扒Bean注入到Spring的那些姿勢

開發(fā) 架構(gòu)
配置文件的方式就是以外部化的配置方式來聲明Spring Bean,在Spring容器啟動(dòng)時(shí)指定配置文件。配置文件方式現(xiàn)在用的不多了,但是為了文章的完整性和連續(xù)性,這里我還是列出來了,知道的小伙伴可以自行跳過這節(jié)。

大家好,我是三友~~

這篇文章我準(zhǔn)備來扒一扒Bean注入到Spring的那些姿勢。

其實(shí)關(guān)于Bean注入Spring容器的方式網(wǎng)上也有很多相關(guān)文章,但是很多文章可能會(huì)存在以下常見的問題

  • 注入方式總結(jié)的不全
  • 沒有分析可以使用這些注入方式背后的原因
  • 沒有這些注入方式在源碼中的應(yīng)用示例
  • ...

所以本文就帶著解決上述的問題的目的來重新梳理一下Bean注入到Spring的那些姿勢。

配置文件

配置文件的方式就是以外部化的配置方式來聲明Spring Bean,在Spring容器啟動(dòng)時(shí)指定配置文件。配置文件方式現(xiàn)在用的不多了,但是為了文章的完整性和連續(xù)性,這里我還是列出來了,知道的小伙伴可以自行跳過這節(jié)。

配置文件的類型Spring主要支持xml和properties兩種類型。

xml

在XmlBeanInjectionDemo.xml文件中聲明一個(gè)class為類型為User的Bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
">

<bean class="com.sanyou.spring.bean.injection.User"/>

</beans>

User

@Data
@ToString
public class User {

private String username;

}

測試:

public class XmlBeanInjectionDemo {

public static void main(String[] args){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:XmlBeanInjectionDemo.xml");
applicationContext.refresh();
User user = applicationContext.getBean(User.class);
System.out.println(user);
}

}

結(jié)果:

User(username=null)

可以看出成功將User注入到Spring中,由于沒有設(shè)置username屬性值,所以是null。

properties

除了xml,spring還支持properties配置文件聲明Bean的方式。

如下,在PropertiesBeanInjectionDemo.properties文件中聲明了class類型為User的Bean,并且設(shè)置User的username屬性為sanyou。

user.(class) = com.sanyou.spring.bean.injection.User
user.username = sanyou

測試:

public class PropertiesBeanInjectionDemo {

public static void main(String[] args){
GenericApplicationContext applicationContext = new GenericApplicationContext();
//創(chuàng)建一個(gè)PropertiesBeanDefinitionReader,可以從properties讀取Bean的信息,將讀到的Bean信息放到applicationContext中
PropertiesBeanDefinitionReader propReader = new PropertiesBeanDefinitionReader(applicationContext);
//創(chuàng)建一個(gè)properties文件對(duì)應(yīng)的Resource對(duì)象
Resource classPathResource = new ClassPathResource("PropertiesBeanInjectionDemo.properties");
//加載配置文件
propReader.loadBeanDefinitions(classPathResource);
applicationContext.refresh();
User user = applicationContext.getBean(User.class);
System.out.println(user);
}

}

結(jié)果:

User(username=sanyou)

成功獲取到User對(duì)象,并且username的屬性為properties設(shè)置的sanyou。

除了可以配置屬性之外還支持其它的配置,如何配置可以查看PropertiesBeanDefinitionReader類上的注釋。

圖片

注解聲明

上一節(jié)介紹了通過配置文件的方式來聲明Bean,但是配置文件這種方式最大的缺點(diǎn)就是不方便,因?yàn)殡S著項(xiàng)目的不斷擴(kuò)大,可能會(huì)產(chǎn)生大量的配置文件。為了解決這個(gè)問題,Spring在2.x的版本中開始支持注解的方式來聲明Bean。

@Component + @ComponentScan

這種方式其實(shí)就不用多說,在項(xiàng)目中自定義的業(yè)務(wù)類就是通過@Component及其派生注解(@Service、@Controller等)來注入到Spring容器中的。

在SpringBoot環(huán)境底下,一般情況下不需要我們主動(dòng)調(diào)用@ComponentScan注解,因?yàn)锧SpringBootApplication會(huì)調(diào)用@ComponentScan注解,掃描啟動(dòng)引導(dǎo)類(加了@SpringBootApplication注解的類)所在的包及其子包下所有加了@Component注解及其派生注解的類,注入到Spring容器中。

圖片

@Bean

雖然上面@Component + @ComponentScan的這種方式可以將Bean注入到Spring中,但是有個(gè)問題那就是對(duì)于第三方j(luò)ar包來說,如果這個(gè)類沒加@Component注解,那么@ComponentScan就掃不到,這樣就無法注入到Spring容器中,所以Spring提供了一種@Bean的方式來聲明Bean。

比如,在使用MybatisPlus的分頁插件的時(shí)候,就可以按如下方式這么來聲明。

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}

此時(shí)就能將MybatisPlusInterceptor這個(gè)Bean注入到Spring容器中。

@Import

@Import注解也可以用來將Bean注入到Spring容器中,@Import注解導(dǎo)入的類可以分為三種情況:

  • 普通類
  • 類實(shí)現(xiàn)了ImportSelector接口
  • 類實(shí)現(xiàn)了ImportBeanDefinitionRegistrar接口

普通類

普通類其實(shí)就很簡單,就是將@Import導(dǎo)入的類注入到Spring容器中,這沒什么好說的。

類實(shí)現(xiàn)了ImportSelector接口

public interface ImportSelector {

String[] selectImports(AnnotationMetadata importingClassMetadata);

@Nullable
default Predicate<String> getExclusionFilter(){
return null;
}

}

當(dāng)@Import導(dǎo)入的類實(shí)現(xiàn)了ImportSelector接口的時(shí)候,Spring就會(huì)調(diào)用selectImports方法的實(shí)現(xiàn),獲取一批類的全限定名,最終這些類就會(huì)被注冊到Spring容器中。

比如如下代碼中,UserImportSelector實(shí)現(xiàn)了ImportSelector,selectImports方法返回User的全限定名

public class UserImportSelector implements ImportSelector {

@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
System.out.println("調(diào)用 UserImportSelector 的 selectImports 方法獲取一批類限定名");
return new String[]{"com.sanyou.spring.bean.injection.User"};
}

}

當(dāng)使用@Import注解導(dǎo)入U(xiǎn)serImportSelector這個(gè)類的時(shí)候,其實(shí)最終就會(huì)把User注入到Spring容器中,如下測試

@Import(UserImportSelector.class)
public class ImportSelectorDemo {

public static void main(String[] args){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//將 ImportSelectorDemo 注冊到容器中
applicationContext.register(ImportSelectorDemo.class);
applicationContext.refresh();

User user = applicationContext.getBean(User.class);
System.out.println(user);
}

}

運(yùn)行結(jié)果

User(username=null)

對(duì)于類實(shí)現(xiàn)了ImportBeanDefinitionRegistrar接口的情況,這個(gè)后面說。

一般來說,@Import都是配合@EnableXX這類注解來使用的,比如常見的@EnableScheduling、@EnableAsync注解等,其實(shí)最終都是靠@Import來實(shí)現(xiàn)的。

圖片

@EnableScheduling

圖片

@EnableAsync

講完通過注解的方式來聲明Bean之后,可以來思考一個(gè)問題,那就是既然注解方式這么簡單,為什么Spring還寫一堆代碼來支持配置文件這種聲明的方式?

其實(shí)答案很簡單,跟Spring的發(fā)展歷程有關(guān)。Spring在創(chuàng)建之初Java還不支持注解,所以只能通過配置文件的方式來聲明Bean,在Java1.5版本開始支持注解之后,Spring才開始支持通過注解的方式來聲明Bean。

注冊BeanDefinition

在說注冊BeanDefinition之前,先來聊聊什么是BeanDefinition?

BeanDefinition是Spring Bean創(chuàng)建環(huán)節(jié)中很重要的一個(gè)東西,它封裝了Bean創(chuàng)建過程中所需要的元信息。

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
//設(shè)置Bean className
void setBeanClassName(@Nullable String beanClassName);

//獲取Bean className
@Nullable
String getBeanClassName();

//設(shè)置是否是懶加載
void setLazyInit(boolean lazyInit);

//判斷是否是懶加載
boolean isLazyInit();

//判斷是否是單例
boolean isSingleton();

}

如上代碼是BeanDefinition接口的部分方法,從這方法的定義名稱可以看出,一個(gè)Bean所創(chuàng)建過程中所需要的一些信息都可以從BeanDefinition中獲取,比如這個(gè)Bean的class類型,這個(gè)Bean是否是懶加載,這個(gè)Bean是否是單例的等等,因?yàn)橛辛诉@些信息,Spring才知道要?jiǎng)?chuàng)建一個(gè)什么樣的Bean。

有了BeanDefinition這個(gè)概念之后,再來看一下配置文件和注解聲明這些方式往Spring容器注入Bean的原理。

圖片

Bean注入到Spring原理

如圖為Bean注入到Spring大致原理圖,整個(gè)過程大致分為以下幾個(gè)步驟

  • 通過BeanDefinitionReader組件讀取配置文件或者注解的信息,為每一個(gè)Bean生成一個(gè)BeanDefinition
  • BeanDefinition生成之后,添加到BeanDefinitionRegistry中,BeanDefinitionRegistry就是用來保存BeanDefinition
  • 當(dāng)需要?jiǎng)?chuàng)建Bean對(duì)象時(shí),會(huì)從BeanDefinitionRegistry中拿出需要?jiǎng)?chuàng)建的Bean對(duì)應(yīng)的BeanDefinition,根據(jù)BeanDefinition的信息來生成Bean
  • 當(dāng)生成的Bean是單例的時(shí)候,Spring會(huì)將Bean保存到SingletonBeanRegistry中,也就是平時(shí)說的三級(jí)緩存中的第一級(jí)緩存中,以免重復(fù)創(chuàng)建,需要使用的時(shí)候直接從SingletonBeanRegistry中查找

好了,通過以上分析我們知道,配置文件和注解聲明的方式其實(shí)都是聲明Bean的一種方式,最終都會(huì)轉(zhuǎn)換成BeanDefinition,Spring是基于BeanDefinition的信息來創(chuàng)建Bean。

既然Spring最終是基于BeanDefinition的信息來創(chuàng)建Bean,那么我們是不是可以跳過配置文件和注解聲明的方式,直接通過手動(dòng)創(chuàng)建和注冊BeanDefinition的方式實(shí)現(xiàn)往Spring容器中注入呢?

答案是可以的。

前面說過,BeanDefinition最終會(huì)被注冊到BeanDefinitionRegistry中,那么如何拿到BeanDefinitionRegistry呢?主要有以下兩種方式:

  • ImportBeanDefinitionRegistrar
  • BeanDefinitionRegistryPostProcessor

ImportBeanDefinitionRegistrar

上面在說@Import的時(shí)候,關(guān)于導(dǎo)入的類實(shí)現(xiàn)了ImportBeanDefinitionRegistrar接口的情況沒有說,主要是因?yàn)樵谶@里說比較合適

public interface ImportBeanDefinitionRegistrar {

default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator){
registerBeanDefinitions(importingClassMetadata, registry);
}

default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){
}

}

ImportBeanDefinitionRegistrar中有兩個(gè)方法,方法的參數(shù)就是BeanDefinitionRegistry。當(dāng)@Import導(dǎo)入的類實(shí)現(xiàn)了ImportBeanDefinitionRegistrar接口之后,Spring就會(huì)調(diào)用registerBeanDefinitions方法,傳入BeanDefinitionRegistry。

來個(gè)Demo

UserImportBeanDefinitionRegistrar實(shí)現(xiàn)ImportBeanDefinitionRegistrar

public class UserImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator){
//構(gòu)建一個(gè) BeanDefinition , Bean的類型為 User
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class)
//設(shè)置User這個(gè)Bean的屬性u(píng)sername的值為三友的java日記
.addPropertyValue("username", "三友的java日記")
.getBeanDefinition();
//把User的BeanDefinition注入到BeanDefinitionRegistry中
registry.registerBeanDefinition("user", beanDefinition);
}

}

測試類

@Import(UserImportBeanDefinitionRegistrar.class)
public class UserImportBeanDefinitionRegistrarDemo {

public static void main(String[] args){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(UserImportBeanDefinitionRegistrarDemo.class);
applicationContext.refresh();
User user = applicationContext.getBean(User.class);
System.out.println(user);
}

}

結(jié)果

User(username=三友的java日記)

從結(jié)果可以看出,成功將User注入到了Spring容器中。

上面的例子中有行代碼

applicationContext.register(UserImportBeanDefinitionRegistrarDemo.class);

這行代碼的意思就是把UserImportBeanDefinitionRegistrarDemo這個(gè)Bean注冊到Spring容器中,所以這里其實(shí)也算一種將Bean注入到Spring的方式,原理也跟上面一樣,會(huì)為UserImportBeanDefinitionRegistrarDemo生成一個(gè)BeanDefinition注冊到Spring容器中。

BeanDefinitionRegistryPostProcessor

除了ImportBeanDefinitionRegistrar可以拿到BeanDefinitionRegistry之外,還可以通過BeanDefinitionRegistryPostProcessor拿到BeanDefinitionRegistry

圖片

BeanDefinitionRegistryPostProcessor

這種方式就不演示了。

手動(dòng)注冊BeanDefinition這種方式還是比較常見的。就比如說OpenFeign在啟用過程中,會(huì)為每個(gè)標(biāo)注了@FeignClient注解的接口創(chuàng)建一個(gè)BeanDefinition,然后再往Spring中的注冊的,如下是OpenFeign注冊FeignClient的部分代碼

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes){
//構(gòu)建BeanDefinition,class類型為FeignClientFactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
//注冊BeanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
}

注冊創(chuàng)建完成的Bean

上一節(jié)說可以跳過配置文件或者是注解,直接通過注冊BeanDefinition以達(dá)到將Bean注入到Spring中的目的。

既然已經(jīng)可以跳過配置文件或者是注解,那么我們可不可以更激進(jìn)一步,跳過注冊BeanDefinition這一步,直接往Spring中注冊一個(gè)已經(jīng)創(chuàng)建好的Bean呢?

答案依然是可以的。

因?yàn)樯厦嬖谔岬疆?dāng)創(chuàng)建的Bean是單例的時(shí)候,會(huì)將這個(gè)創(chuàng)建完成的Bean保存到SingletonBeanRegistry中,需要用到直接從SingletonBeanRegistry中查找。既然最終是從SingletonBeanRegistry中查找的Bean,那么直接注入一個(gè)創(chuàng)建好的Bean有什么不可以呢?

既然可以,那么如何拿到SingletonBeanRegistry呢?

其實(shí)拿到SingletonBeanRegistry的方法其實(shí)很多,因?yàn)镃onfigurableListableBeanFactory就繼承了SingletonBeanRegistry接口,所以只要能拿到ConfigurableListableBeanFactory就相當(dāng)于拿到了SingletonBeanRegistry。

圖片

ConfigurableListableBeanFactory類圖

而ConfigurableListableBeanFactory可以通過BeanFactoryPostProcessor來獲取

圖片BeanFactoryPostProcessor

來個(gè)Demo

RegisterUserBeanFactoryPostProcessor實(shí)現(xiàn)BeanFactoryPostProcessor, 往Spring容器中添加一個(gè)手動(dòng)創(chuàng)建的User對(duì)象

public class RegisterUserBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//創(chuàng)建一個(gè)User對(duì)象
User user = new User();
user.setUsername("三友的java日記");
//將這個(gè)User對(duì)象注入到Spring容器中
beanFactory.registerSingleton("user", user);
}

}

測試

public class RegisterUserDemo {

public static void main(String[] args){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(RegisterUserBeanFactoryPostProcessor.class);
applicationContext.refresh();
User user = applicationContext.getBean(User.class);
System.out.println(user);
}

}

結(jié)果

User(username=三友的java日記)

從結(jié)果還是可以看出,成功從Spring容器中獲取到了User對(duì)象。

這種直接將創(chuàng)建好的Bean注入到Spring容器中在Spring框架內(nèi)部使用的還是比較多的,Spring的一些內(nèi)建的Bean就是通過這個(gè)方式注入到Spring中的。

圖片

如上圖,在SpringBoot項(xiàng)目啟動(dòng)的過程中會(huì)往Spring容器中添加兩個(gè)創(chuàng)建好的Bean,如果你的程序需要使用到這些Bean,就可以通過依賴注入的方式獲取到。

雖然基于這種方式可以將Bean注入到Spring容器,但是這種方式注入的Bean是不經(jīng)過Bean的生命周期的,也就是說這個(gè)Bean中諸如@Autowired等注解和Bean生命周期相關(guān)的回調(diào)都不會(huì)生效的,注入到Spring時(shí)Bean是什么樣就是什么樣,Spring不做處理,僅僅只是做一個(gè)保存作用。

FactoryBean

FactoryBean是一種特殊的Bean的類型,通過FactoryBean也可以將Bean注入到Spring容器中。

圖片

FactoryBean

當(dāng)我們通過配置文件、注解聲明或者是注冊BeanDenifition的方式,往Spring容器中注入了一個(gè)class類型為FactoryBean類型的Bean時(shí)候,其實(shí)真正注入的Bean類型為getObjectType方法返回的類型,并且Bean的對(duì)象是通過getObject方法返回的。

來個(gè)Demo

UserFactoryBean實(shí)現(xiàn)了FactoryBean,getObjectType返回了User類型,所以這個(gè)UserFactoryBean會(huì)往Spring容器中注入U(xiǎn)ser這個(gè)Bean,并且User對(duì)象是通過getObject()方法的實(shí)現(xiàn)返回的。

public class UserFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
User user = new User();
user.setUsername("三友的java日記");
return user;
}

@Override
public Class<?> getObjectType() {
return User.class;
}
}

測試

public class UserFactoryBeanDemo {

public static void main(String[] args){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//將UserFactoryBean注入到Spring容器中
applicationContext.register(UserFactoryBean.class);
applicationContext.refresh();
User user = applicationContext.getBean(User.class);
System.out.println(user);
}

}

結(jié)果

User(username=三友的java日記)

成功通過UserFactoryBean將User這個(gè)Bean注入到Spring容器中了。

FactoryBean這中注入的方式使用也是非常多的,就拿上面舉例的OpenFeign來說,OpenFeign為每個(gè)FeignClient的接口創(chuàng)建的BeanDefinition的Bean的class類型FeignClientFactoryBean就是FactoryBean的實(shí)現(xiàn)。

class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {

// FeignClient接口類型
private Class<?> type;

@Override
public Object getObject() throws Exception {
return getTarget();
}

@Override
public Class<?> getObjectType() {
return type;
}
}

getObject()方法就會(huì)返回接口的動(dòng)態(tài)代理的對(duì)象,并且這個(gè)代理對(duì)象是由Feign創(chuàng)建的,這也就實(shí)現(xiàn)了Feign和Spring的整合。

總結(jié)

通過以上分析可以看出,將Bean注入到Spring容器中大致可以分為5類:

  • 配置文件
  • 注解聲明
  • 注冊BeanDefinition
  • 注冊創(chuàng)建完成的Bean
  • FactoryBean

以上幾種注入的方式,在日常業(yè)務(wù)開發(fā)中,基本上都是使用注解聲明的方式注入Spring中的;在第三方框架在和Spring整合時(shí),注冊BeanDefinition和FactoryBean這些注入方式也會(huì)使用的比較多;至于配置文件和注冊創(chuàng)建完成的Bean的方式,有但是不多。

最后,本文所有的示例代碼地址:https://github.com/sanyou3/spring-bean-injection.git

責(zé)任編輯:武曉燕 來源: 三友的java日記
相關(guān)推薦

2018-04-03 15:42:40

2015-08-18 09:12:54

app推廣渠道

2025-06-04 01:20:00

2015-09-16 14:11:47

2022-07-11 20:46:39

AQSJava

2019-10-21 10:59:52

編程語言JavaC

2019-09-10 07:29:44

2019-02-25 22:46:39

2020-01-15 15:29:52

InnoDB數(shù)據(jù)硬盤

2023-04-10 23:05:54

NacosOpenFeignRibbon

2015-09-16 14:04:06

大數(shù)據(jù)巨頭

2015-10-15 13:38:39

2019-01-03 11:09:19

2015-09-21 10:07:31

2017-09-07 18:45:51

C#

2025-04-30 07:26:04

2022-09-30 09:40:39

智能汽車

2021-05-13 05:25:16

數(shù)據(jù)分析數(shù)分培訓(xùn)大數(shù)據(jù)

2019-04-28 14:24:54

吳亦凡流量數(shù)據(jù)

2015-12-15 09:51:42

大公司技術(shù)知乎
點(diǎn)贊
收藏

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

主站蜘蛛池模板: 国际精品鲁一鲁一区二区小说 | 美女天堂在线 | 国产精品久久久久久一区二区三区 | 久草精品在线 | 成人久久久| 国产韩国精品一区二区三区 | 蜜桃视频一区二区三区 | 久久国产一区二区 | 国产免费一区 | 91精品国产综合久久久亚洲 | 视频一区二区在线观看 | 国产中文字幕在线观看 | www.五月婷婷.com | 东京久久| 国产欧美一区二区三区久久人妖 | a视频在线| 91看片在线观看 | 日韩成人免费 | 国产99精品| 毛片免费看 | 久久免费精品 | 国产欧美日韩综合精品一区二区 | 亚洲国产成人精 | 草久久 | 国产欧美精品区一区二区三区 | 久久91精品国产一区二区 | 天天综合久久 | 日本精品视频在线观看 | 韩日精品一区 | 日日骚网| 在线观看黄色电影 | 亚洲一区视频在线播放 | 99reav| 亚洲激情在线 | av在线天堂网 | 日韩网站免费观看 | 中文字幕视频三区 | 日韩看片 | 99re视频在线观看 | 国产日韩一区二区三区 | 国产日韩免费视频 |