久久精品精选,精品九九视频,www久久只有这里有精品,亚洲熟女乱色综合一区
    分享

    看看Spring的源碼(一)——Bean加載過(guò)程

     埃德溫會(huì)館 2015-10-18

    本文發(fā)表于我的獨(dú)立博客:Geeekr

    最近幾天跟同事聊起Spring的一些問(wèn)題,對(duì)一些地方有些疑問(wèn),趁這兩天有點(diǎn)空,看看Spring的源碼,了解下具體的實(shí)現(xiàn)細(xì)節(jié)。本文基于Spring 4.0.5版本。

    首先Web項(xiàng)目使用Spring是通過(guò)在web.xml里面配置
    org.springframework.web.context.ContextLoaderListener初始化IOC容器的。

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    

    那就以此為切入點(diǎn)順藤摸瓜。

    public class ContextLoaderListener extends ContextLoader implements ServletContextListener 
    

    ContextLoaderListener繼承了ContextLoader,并且實(shí)現(xiàn)ServletContextListener接口。當(dāng)Server容器(一般指tomcat)啟動(dòng)時(shí),會(huì)收到事件初始化。

    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }
    

    initWebApplicationContext方法是在org.springframework.web.context.ContextLoader類里面。方法太長(zhǎng),分段讀一下。

    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        throw new IllegalStateException("Cannot initialize context because there is already a root application context present - " +"check whether you have multiple ContextLoader* definitions in your web.xml!");
    }
    Log logger = LogFactory.getLog(ContextLoader.class);
    servletContext.log("Initializing Spring root WebApplicationContext");
    if (logger.isInfoEnabled()) {
        logger.info("Root WebApplicationContext: initialization started");
    }
    long startTime = System.currentTimeMillis();
    

    首先是判斷servletContext中是否已經(jīng)注冊(cè)了WebApplicationContext,如果有則拋出異常,避免重復(fù)注冊(cè)。然后就是啟用log,啟動(dòng)計(jì)時(shí)。本方法的關(guān)鍵就在于try代碼塊里的內(nèi)容

    try {
        // Store context in local instance variable, to guarantee that
        // it is available on ServletContext shutdown.
        if (this.context == null) {
            this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit parent ->
                    // determine parent for root web application context, if any.
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
    
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = this.context;
        }
        else if (ccl != null) {
            currentContextPerThread.put(ccl, this.context);
        }
    
        if (logger.isDebugEnabled()) {
            logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                    WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
        }
        if (logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
        }
    
        return this.context;
    }
    

    這里面有幾個(gè)關(guān)鍵的方法。首先看一下createWebApplicationContext()

    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        Class<?> contextClass = determineContextClass(sc);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                    "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        }
        return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
    

    首先determineContextClass()方法查明具體的Context類,他會(huì)讀取servletContext的初始化參數(shù)contextClass,此參數(shù)我們一半不配置,所以Spring就會(huì)讀取跟org.springframework.web.context.WebApplicationContext同一個(gè)包下面的ContextLoader.properties文件讀取默認(rèn)設(shè)置,反射出org.springframework.web.context.support.XmlWebApplicationContext類來(lái)。接下來(lái)就是在configureAndRefreshWebApplicationContext()方法里將新創(chuàng)建的XmlWebApplicationContext進(jìn)行初始化。首先會(huì)設(shè)置一個(gè)默認(rèn)ID,即org.springframework.web.context.WebApplicationContext:+你項(xiàng)目的ContextPath。

    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        // The application context id is still set to its original default
        // value
        // -> assign a more useful id based on available information
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
        if (idParam != null) {
            wac.setId(idParam);
        } else {
            // Generate default id...
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }
    

    緊接著就是將ServletContext設(shè)置成XmlWebApplicationContext的屬性,這樣Spring就能在上下文里輕松拿到ServletContext了。

    wac.setServletContext(sc);
    

    接下來(lái)就是讀取web.xml文件中的contextConfigLocation參數(shù)。如果沒(méi)有配置就會(huì)去讀WEB-INF下的applicationContext.xml文件。

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:beans.xml</param-value>
    </context-param>
    

    并將值設(shè)置(就是我們的Spring配置文件的路徑)進(jìn)XmlWebApplicationContext中。然后就會(huì)在指定的路徑加載配置文件。

    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }
    

    接下來(lái)就是customizeContext(sc, wac)方法,此方法會(huì)根據(jù)用戶配置的globalInitializerClasses參數(shù)來(lái)初始化一些用戶自定義的屬性,一般我們不配置,所以這里什么也不做。

    最后登場(chǎng)的就是最核心的方法了,

    wac.refresh();
    

    在這個(gè)方法里,會(huì)完成資源文件的加載、配置文件解析、Bean定義的注冊(cè)、組件的初始化等核心工作,我們一探究竟。

    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();
    
            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);
    
            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);
    
                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);
    
                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);
    
                // Initialize message source for this context.
                initMessageSource();
    
                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();
    
                // Initialize other special beans in specific context subclasses.
                onRefresh();
    
                // Check for listener beans and register them.
                registerListeners();
    
                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);
    
                // Last step: publish corresponding event.
                finishRefresh();
            }
    
            catch (BeansException ex) {
                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();
    
                // Reset 'active' flag.
                cancelRefresh(ex);
    
                // Propagate exception to caller.
                throw ex;
            }
        }
    }
    

    次方法是同步的,避免重復(fù)刷新,每個(gè)步驟都放在單獨(dú)的方法內(nèi),流程清晰,是值得學(xué)習(xí)的地方。這里面有個(gè)重要的方法是finishBeanFactoryInitialization(beanFactory);,里面的內(nèi)容是Spring如何實(shí)例化bean,并注入依賴的,這個(gè)內(nèi)容下一節(jié)講,本節(jié)只說(shuō)明Spring是如何加載class文件的。

    首先就是prepareRefresh()方法。

    protected void prepareRefresh() {
        this.startupDate = System.currentTimeMillis();
    
        synchronized (this.activeMonitor) {
            this.active = true;
        }
    
        if (logger.isInfoEnabled()) {
            logger.info("Refreshing " + this);
        }
    
        // Initialize any placeholder property sources in the context environment
        initPropertySources();
    
        // Validate that all properties marked as required are resolvable
        // see ConfigurablePropertyResolver#setRequiredProperties
        getEnvironment().validateRequiredProperties();
    }
    

    此方法做一些準(zhǔn)備工作,如記錄開(kāi)始時(shí)間,輸出日志,initPropertySources();getEnvironment().validateRequiredProperties();一般沒(méi)干什么事。

    接下來(lái)就是初始化BeanFactory,是整個(gè)refresh()方法的核心,其中完成了配置文件的加載、解析、注冊(cè)

    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    

    看看它里面都做了些什么?

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
    }
    

    首先refreshBeanFactory()

    protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }
    

    我們看到會(huì)創(chuàng)建一個(gè)DefaultListableBeanFactory實(shí)例

    DefaultListableBeanFactory beanFactory = createBeanFactory();
    

    再設(shè)置一個(gè)ID

    beanFactory.setSerializationId(getId());
    

    然后設(shè)置一些自定義參數(shù):

    customizeBeanFactory(beanFactory);
    

    這里面最重要的就是loadBeanDefinitions(beanFactory);方法了。

    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // Create a new XmlBeanDefinitionReader for the given BeanFactory.
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    
        // Configure the bean definition reader with this context's
        // resource loading environment.
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
    
        // Allow a subclass to provide custom initialization of the reader,
        // then proceed with actually loading the bean definitions.
        initBeanDefinitionReader(beanDefinitionReader);
        loadBeanDefinitions(beanDefinitionReader);
    }
    

    此方法會(huì)通過(guò)XmlBeanDefinitionReader加載bean定義。具體的實(shí)現(xiàn)方法是在org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions方法中定義的。這里設(shè)計(jì)了層層調(diào)用,有好多重載方法,主要就是加載Spring所有的配置文件(可能會(huì)有多個(gè)),以備后面解析,注冊(cè)之用。我一路追蹤到org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(Element root)

    protected void doRegisterBeanDefinitions(Element root) {
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                    profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            if (!this.environment.acceptsProfiles(specifiedProfiles)) {
                return;
            }
        }
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(this.readerContext, root, parent);
        preProcessXml(root);
        parseBeanDefinitions(root, this.delegate);
        postProcessXml(root);
        this.delegate = parent;
    }
    

    這里創(chuàng)建了一個(gè)BeanDefinitionParserDelegate示例,解析XML的過(guò)程就是委托它完成的,我們不關(guān)心它是怎樣解析XML的,我們只關(guān)心是怎么加載類的,所以就要看parseBeanDefinitions(root, this.delegate)方法了。

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    if (delegate.isDefaultNamespace(ele)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }
    

    我們看到最終解析XML元素的是delegate.parseCustomElement(ele)方法,最終會(huì)走到一下方法.

    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        String namespaceUri = getNamespaceURI(ele);
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        }
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }
    

    這里會(huì)根據(jù)不同的XML節(jié)點(diǎn),會(huì)委托NamespaceHandlerSupport找出合適的BeanDefinitionParser,如果我們配置了

    <context:component-scan
        base-package="com.geeekr.service,com.geeekr.dao" />
    

    那么對(duì)應(yīng)BeanDefinitionParser就是org.springframework.context.annotation.ComponentScanBeanDefinitionParser,來(lái)看看它的parse方法。

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    
        // Actually scan for bean definitions and register them.
        ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
        Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
        registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
    
        return null;
    }
    

    不難看出這里定義了一個(gè)ClassPathBeanDefinitionScanner,通過(guò)它去掃描包中的類文件,注意:這里是類文件而不是類,因?yàn)楝F(xiàn)在這些類還沒(méi)有被加載,只是ClassLoader能找到這些class的路徑而已。到目前為止,感覺(jué)真想距離我們?cè)絹?lái)越近了。順著繼續(xù)往下摸。進(jìn)入doSacn方法里,映入眼簾的又是一大坨代碼,但是我們只關(guān)心觀點(diǎn)的部分。

    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            for (BeanDefinition candidate : candidates) {
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                candidate.setScope(scopeMetadata.getScopeName());
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                if (candidate instanceof AbstractBeanDefinition) {
                    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                }
                if (candidate instanceof AnnotatedBeanDefinition) {
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
                if (checkCandidate(beanName, candidate)) {
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);
                    registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }
        return beanDefinitions;
    }
    

    一眼就能看出是通過(guò)

    Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
    

    有時(shí)候不得不佩服這些外國(guó)人起名字的功力,把掃描出來(lái)的類叫做candidates(候選人);真是不服不行啊,這種名字真的很容易理解有不有?哈哈,貌似扯遠(yuǎn)了。繼續(xù)往下看。這里只列出方法的主題部分。

    public Set<BeanDefinition> findCandidateComponents(String basePackage) {
        Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
        try {
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + "/" + this.resourcePattern;
            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
            boolean traceEnabled = logger.isTraceEnabled();
            boolean debugEnabled = logger.isDebugEnabled();
            for (Resource resource : resources) {
                if (traceEnabled) {
                    logger.trace("Scanning " + resource);
                }
                if (resource.isReadable()) {
                    try {
                        MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                        if (isCandidateComponent(metadataReader)) {
                            ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                            sbd.setResource(resource);
                            sbd.setSource(resource);
    

    先看這兩句:

    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + "/" + this.resourcePattern;
    

    假設(shè)我們配置的需要掃描的包名為com.geeekr.service,那么packageSearchPath的值就是classpath*:com.geeekr.service/**/*.class,意思就是com.geeekr.service包(包括子包)下所有class文件;如果配置的是*,那么packageSearchPath的值就是classpath*:*/**/*.class。這里的表達(dá)式是Spring自己定義的。Spring會(huì)根據(jù)這種表達(dá)式找出相關(guān)的class文件。

    Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
    

    這一句就把相關(guān)class文件加載出來(lái)了,那我們就要看看,Spring究竟是如何把class文件找到的了。首先看看resourcePatternResolver的定義:

    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    

    進(jìn)入getResources方法

    @Override
    public Resource[] getResources(String locationPattern) throws IOException {
        Assert.notNull(locationPattern, "Location pattern must not be null");
        if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
            // a class path resource (multiple resources for same name possible)
            if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
                // a class path resource pattern
                return findPathMatchingResources(locationPattern);
            }
            else {
                // all class path resources with the given name
                return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
            }
        }
        else {
            // Only look for a pattern after a prefix here
            // (to not get fooled by a pattern symbol in a strange prefix).
            int prefixEnd = locationPattern.indexOf(":") + 1;
            if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
                // a file pattern
                return findPathMatchingResources(locationPattern);
            }
            else {
                // a single resource with the given name
                return new Resource[] {getResourceLoader().getResource(locationPattern)};
            }
        }
    }
    

    這里會(huì)先判斷表達(dá)式是否以classpath*:開(kāi)頭。前面我們看到Spring已經(jīng)給我們添加了這個(gè)頭,這里當(dāng)然符合條件了。接著會(huì)進(jìn)入findPathMatchingResources方法。在這里又把**/*.class去掉了,然后在調(diào)用getResources方法,然后在進(jìn)入findAllClassPathResources方法。這里的參數(shù)只剩下包名了例如com/geeekr/service/。

    protected Resource[] findAllClassPathResources(String location) throws IOException {
        String path = location;
        if (path.startsWith("/")) {
            path = path.substring(1);
        }
        ClassLoader cl = getClassLoader();
        Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
        Set<Resource> result = new LinkedHashSet<Resource>(16);
        while (resourceUrls.hasMoreElements()) {
            URL url = resourceUrls.nextElement();
            result.add(convertClassLoaderURL(url));
        }
        return result.toArray(new Resource[result.size()]);
    }
    

    真相大白了,Spring也是用的ClassLoader加載的class文件。一路追蹤,原始的ClassLoader是Thread.currentThread().getContextClassLoader();。到此為止,就拿到class文件了。
    Spring會(huì)將class信息封裝成BeanDefinition,然后再放進(jìn)DefaultListableBeanFactorybeanDefinitionMap中。

    拿到了class文件后,就要看看Spring是如何裝配bean的了,下一節(jié),繼續(xù)看。

      本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
      轉(zhuǎn)藏 分享 獻(xiàn)花(0

      0條評(píng)論

      發(fā)表

      請(qǐng)遵守用戶 評(píng)論公約

      類似文章 更多

      主站蜘蛛池模板: 精品亚洲精品日韩精品| 強壮公弄得我次次高潮A片| 精品一卡2卡三卡4卡乱码精品视频| 在线 欧美 中文 亚洲 精品| 三级三级三级A级全黄| 国产成人精品午夜福利| 午夜大片免费男女爽爽影院| 人妻在线无码一区二区三区| 人妻少妇精品视中文字幕国语| 高清自拍亚洲精品二区| 国产V亚洲V天堂无码久久久| 成人午夜大片免费看爽爽爽 | 99久久er热在这里只有精品99 | 一本久道久久综合狠狠爱| 在线播放深夜精品三级| 国产综合久久亚洲综合| 人妻少妇偷人精品一区| 国产AV巨作丝袜秘书| 亚洲精品天堂一区二区| 久久精品国产亚洲AV嫖农村妇女| 国产在线一区二区不卡| 亚洲综合欧美色五月俺也去| 国产超高清麻豆精品传媒麻豆精品| 狠狠色丁香婷婷综合潮喷| 国产成人8X人网站视频| 国产一区二区波多野结衣 | 97久久超碰亚洲视觉盛宴| 国产一区二区波多野结衣| 久9视频这里只有精品| 少妇人妻偷人精品系列| 88国产精品欧美一区二区三区| 国产综合久久99久久| 国产成人毛片无码视频软件| 国产偷窥熟女高潮精品视频| 四虎成人精品永久网站| 性奴sm虐辱暴力视频网站| 精品无码一区二区三区亚洲桃色| 精品视频在线观看免费观看| 国产一区二区三区在线观看免费| 亚洲欧美成人综合久久久| 亚洲精品色无码AV试看|