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

Java Agent :構建 SpringBoot 應用無痕調試注入器

開發 前端
在生產環境中,線上問題定位往往面臨著巨大挑戰 —— 我們不能隨意重啟應用,更不能暫停服務進行調試。Java Agent技術為我們提供了一種優雅的解決方案,它允許我們在不修改源代碼、不重啟應用的情況下,對運行中的JVM進行字節碼增強,實現無痕調試。

前言

圖片圖片

在生產環境中,線上問題定位往往面臨著巨大挑戰 —— 我們不能隨意重啟應用,更不能暫停服務進行調試。Java Agent技術為我們提供了一種優雅的解決方案,它允許我們在不修改源代碼、不重啟應用的情況下,對運行中的JVM進行字節碼增強,實現無痕調試

什么是 Java Agent?

Java AgentJava SE 5引入的一項技術,它本質上是一個特殊的JAR文件,能夠在主程序運行前或運行時動態修改類的字節碼。這種特性使得Java Agent非常適合實現:

  • 應用監控與性能分析
  • 線上問題診斷與調試
  • AOP編程(無侵入式)
  • 代碼覆蓋率分析

Java Agent有兩種加載方式:

  • 啟動時加載:通過-javaagent參數指定,在JVM啟動時加載
  • 運行時加載:通過Attach API動態附加到運行中的JVM進程

技術選型

實現Java Agent需要操作字節碼,目前主流的字節碼操作庫有:

  • ASM:輕量級、高性能,直接操作字節碼指令
  • Javassist:更高層次的API,支持源碼級別的修改
  • CGLIB:基于ASM,主要用于生成代理類

考慮到性能和靈活性,我們選擇ASM作為字節碼操作庫,它能讓我們更精細地控制字節碼生成過程,適合生產環境使用。

實現步驟

依賴配置

<?xml versinotallow="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocatinotallow="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.yian</groupId>
    <artifactId>springboot-debug-agent</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <asm.version>9.3</asm.version>
    </properties>

    <dependencies>
        <!-- ASM字節碼操作庫 -->
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm</artifactId>
            <version>${asm.version}</version>
        </dependency>
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm-commons</artifactId>
            <version>${asm.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifestEntries>
                            <!-- 啟動時加載的入口類 -->
                            <Premain-Class>com.yian.agent.DebugAgent</Premain-Class>
                            <!-- 運行時加載的入口類 -->
                            <Agent-Class>com.yian.agent.DebugAgent</Agent-Class>
                            <!-- 允許重定義類 -->
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <!-- 允許重轉換類 -->
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
  • Premain-Class:指定啟動時加載Agent的入口類
  • Agent-Class:指定運行時加載Agent的入口類
  • Can-Redefine-ClassesCan-Retransform-Classes:允許Agent重定義和轉換類

實現 Agent 入口類

Agent入口類需要實現premain(啟動時加載)和agentmain(運行時加載)方法:

/**
 * Java Agent入口類,實現無痕調試注入功能
 */
public class DebugAgent {
    private static final Logger logger = Logger.getLogger(DebugAgent.class.getName());
    private static Instrumentation instrumentation;

    /**
     * JVM啟動時加載Agent的入口方法
     */
    public static void premain(String agentArgs, Instrumentation inst) {
        logger.info("DebugAgent premain 啟動...");
        initialize(agentArgs, inst);
    }

    /**
     * 運行時動態加載Agent的入口方法
     */
    public static void agentmain(String agentArgs, Instrumentation inst) {
        logger.info("DebugAgent agentmain 啟動...");
        initialize(agentArgs, inst);
        
        // 運行時加載需要觸發類重轉換
        try {
            Class<?>[] allLoadedClasses = inst.getAllLoadedClasses();
            for (Class<?> clazz : allLoadedClasses) {
                if (AgentConfig.shouldTransform(clazz.getName())) {
                    inst.retransformClasses(clazz);
                    logger.info("已重新轉換類: " + clazz.getName());
                }
            }
        } catch (Exception e) {
            logger.severe("類重轉換失敗: " + e.getMessage());
        }
    }

    /**
     * 初始化Agent
     */
    private static void initialize(String agentArgs, Instrumentation inst) {
        instrumentation = inst;
        // 解析Agent參數
        AgentConfig.parse(agentArgs);
        // 添加類轉換器
        inst.addTransformer(new MethodMonitorTransformer(), true);
        logger.info("DebugAgent 初始化完成,配置: " + AgentConfig.getConfigInfo());
    }

    public static Instrumentation getInstrumentation() {
        return instrumentation;
    }
}

入口類的核心職責:

  • 接收并解析Agent參數
  • 初始化Instrumentation實例
  • 注冊類轉換器
  • 運行時加載時觸發類重轉換

配置解析實現

我們需要靈活的配置機制,讓用戶可以指定需要監控的類和方法:

/**
 * Agent配置類,解析和存儲注入規則
 */
public class AgentConfig {
    private static final Logger logger = Logger.getLogger(AgentConfig.class.getName());
    
    // 包含規則(正則表達式)
    private static final List<Pattern> includePatterns = new ArrayList<>();
    // 排除規則(正則表達式)
    private static final List<Pattern> excludePatterns = new ArrayList<>();
    // 日志文件路徑
    private static String logFile;
    
    /**
     * 解析Agent參數
     * 格式: include=com.yian.*;exclude=com.yian.test.*;logFile=/tmp/agent.log
     */
    public static void parse(String agentArgs) {
        if (agentArgs == null || agentArgs.trim().isEmpty()) {
            logger.info("未指定Agent參數,使用默認配置");
            // 添加默認規則,監控所有Spring組件
            includePatterns.add(Pattern.compile("com\\.yian\\..*"));
            return;
        }
        
        String[] configItems = agentArgs.split(";");
        for (String item : configItems) {
            String[] keyValue = item.split("=", 2);
            if (keyValue.length != 2) continue;
            
            String key = keyValue[0].trim();
            String value = keyValue[1].trim();
            
            switch (key) {
                case"include":
                    includePatterns.add(Pattern.compile(convertToRegex(value)));
                    break;
                case"exclude":
                    excludePatterns.add(Pattern.compile(convertToRegex(value)));
                    break;
                case"logFile":
                    logFile = value;
                    break;
                default:
                    logger.warning("未知的配置項: " + key);
            }
        }
        
        // 如果沒有指定包含規則,添加默認規則
        if (includePatterns.isEmpty()) {
            includePatterns.add(Pattern.compile("com\\.yian\\..*"));
        }
    }
    
    /**
     * 將通配符表達式轉換為正則表達式
     */
    private static String convertToRegex(String wildcard) {
        return wildcard.replace(".", "\\.").replace("*", ".*").replace("?", ".");
    }
    
    /**
     * 判斷類是否需要被轉換
     */
    public static boolean shouldTransform(String className) {
        // 將類名轉換為全限定名格式(例如:com/yian/MyClass -> com.yian.MyClass)
        String qualifiedName = className.replace("/", ".");
        
        // 檢查是否匹配排除規則
        for (Pattern pattern : excludePatterns) {
            if (pattern.matcher(qualifiedName).matches()) {
                returnfalse;
            }
        }
        
        // 檢查是否匹配包含規則
        for (Pattern pattern : includePatterns) {
            if (pattern.matcher(qualifiedName).matches()) {
                returntrue;
            }
        }
        
        returnfalse;
    }
    
    public static String getLogFile() {
        return logFile;
    }
    
    public static String getConfigInfo() {
        return String.format("include=%s, exclude=%s, logFile=%s",
                includePatterns, excludePatterns, logFile);
    }
}
  • 解析命令行參數(include/exclude規則、日志路徑)
  • 通配符到正則表達式的轉換(方便用戶使用*通配符)
  • 類匹配邏輯(決定哪些類需要被增強)

字節碼轉換器實現

類轉換器是Agent的核心,它決定了如何修改類的字節碼:

/**
 * 類轉換器,負責將監控邏輯注入到目標類中
 */
public class MethodMonitorTransformer implements ClassFileTransformer {
    private static final Logger logger = Logger.getLogger(MethodMonitorTransformer.class.getName());
    
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                           ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        // 判斷是否需要轉換該類
        if (!AgentConfig.shouldTransform(className)) {
            return null;
        }
        
        try {
            logger.info("開始轉換類: " + className);
            
            // 讀取類字節碼
            ClassReader cr = new ClassReader(classfileBuffer);
            ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
            
            // 使用自定義的ClassVisitor處理類
            DebugClassVisitor cv = new DebugClassVisitor(cw, className.replace("/", "."));
            
            // 處理類,SKIP_DEBUG可以提高性能,不處理調試信息
            cr.accept(cv, ClassReader.SKIP_DEBUG);
            
            // 返回修改后的字節碼
            return cw.toByteArray();
        } catch (Exception e) {
            logger.severe("轉換類 " + className + " 失敗: " + e.getMessage());
            e.printStackTrace();
            return null;
        }
    }
}

轉換器的工作流程:

  • 檢查類是否需要被轉換(基于AgentConfig的規則)
  • 使用ASMClassReader讀取類字節碼
  • 創建ClassWriter用于生成修改后的字節碼
  • 使用自定義的ClassVisitor處理類結構
  • 返回修改后的字節碼

字節碼增強實現

最后,我們需要實現具體的字節碼增強邏輯,在方法調用前后插入監控代碼:

/**
 * 自定義ClassVisitor,用于訪問類的方法并注入監控邏輯
 */
public class DebugClassVisitor extends ClassVisitor {
    private final String className;
    
    public DebugClassVisitor(ClassVisitor classVisitor, String className) {
        super(Opcodes.ASM9, classVisitor);
        this.className = className;
    }
    
    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        
        // 過濾掉構造方法和靜態初始化方法
        if (name.equals("<init>") || name.equals("<clinit>")) {
            return mv;
        }
        
        // 過濾掉native方法
        if ((access & Opcodes.ACC_NATIVE) != 0) {
            return mv;
        }
        
        // 使用自定義的MethodVisitor處理方法
        return new DebugMethodVisitor(mv, className, name, descriptor, access);
    }
}
/**
 * 自定義MethodVisitor,用于在方法執行前后注入監控邏輯
 */
public class DebugMethodVisitor extends AdviceAdapter {
    private static final Logger logger = Logger.getLogger(DebugMethodVisitor.class.getName());
    
    private final String className;
    private final String methodName;
    private final String methodDesc;
    
    // 局部變量索引,用于存儲方法開始時間
    private int startTimeVar;
    // 用于標識是否是靜態方法
    private final boolean isStatic;

    protected DebugMethodVisitor(MethodVisitor methodVisitor, String className, 
                                String methodName, String methodDesc, int access) {
        super(Opcodes.ASM9, methodVisitor, access, methodName, methodDesc);
        this.className = className;
        this.methodName = methodName;
        this.methodDesc = methodDesc;
        this.isStatic = (access & Opcodes.ACC_STATIC) != 0;
    }
    
    /**
     * 在方法進入時插入代碼
     */
    @Override
    protected void onMethodEnter() {
        // 記錄方法開始時間
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        startTimeVar = newLocal(Type.LONG_TYPE);
        mv.visitVarInsn(LSTORE, startTimeVar);
        
        // 打印方法調用信息
        mv.visitLdcInsn(className);
        mv.visitLdcInsn(methodName);
        mv.visitMethodInsn(INVOKESTATIC, "com/yian/agent/MethodMonitor", 
                         "logMethodEnter", "(Ljava/lang/String;Ljava/lang/String;)V", false);
        
        // 打印方法參數
        printParameters();
    }
    
    /**
     * 打印方法參數
     */
    private void printParameters() {
        Type[] argumentTypes = Type.getArgumentTypes(methodDesc);
        
        // 創建參數數組
        mv.visitIntInsn(BIPUSH, argumentTypes.length);
        mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
        int paramsArrayVar = newLocal(Type.getType(Object[].class));
        mv.visitVarInsn(ASTORE, paramsArrayVar);
        
        // 填充參數數組
        int paramIndex = isStatic ? 0 : 1; // 非靜態方法第一個參數是this
        for (int i = 0; i < argumentTypes.length; i++) {
            Type type = argumentTypes[i];
            int size = type.getSize();
            
            // 加載數組和索引
            mv.visitVarInsn(ALOAD, paramsArrayVar);
            mv.visitIntInsn(BIPUSH, i);
            
            // 加載參數值并裝箱
            loadArg(paramIndex);
            box(type);
            
            // 存入數組
            mv.visitInsn(AASTORE);
            
            paramIndex += size;
        }
        
        // 調用日志方法記錄參數
        mv.visitLdcInsn(className);
        mv.visitLdcInsn(methodName);
        mv.visitVarInsn(ALOAD, paramsArrayVar);
        mv.visitMethodInsn(INVOKESTATIC, "com/yian/agent/MethodMonitor", 
                         "logMethodParameters", "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V", false);
    }
    
    /**
     * 在方法退出時插入代碼
     */
    @Override
    protected void onMethodExit(int opcode) {
        // 計算方法執行時間
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        mv.visitVarInsn(LLOAD, startTimeVar);
        mv.visitInsn(LSUB);
        int durationVar = newLocal(Type.LONG_TYPE);
        mv.visitVarInsn(LSTORE, durationVar);
        
        // 如果是正常返回,記錄返回值
        if ((opcode >= IRETURN && opcode <= RETURN)) {
            // 復制返回值到棧頂(因為onMethodExit時返回值已經在棧上)
            if (opcode != RETURN) { // 不是void返回
                dupReturnValue(opcode);
                box(Type.getReturnType(methodDesc));
                
                // 記錄返回值
                mv.visitLdcInsn(className);
                mv.visitLdcInsn(methodName);
                mv.visitVarInsn(LLOAD, durationVar);
                mv.visitMethodInsn(INVOKESTATIC, "com/yian/agent/MethodMonitor", 
                                 "logMethodReturn", "(Ljava/lang/String;Ljava/lang/String;JLjava/lang/Object;)V", false);
            } else {
                // void返回
                mv.visitLdcInsn(className);
                mv.visitLdcInsn(methodName);
                mv.visitVarInsn(LLOAD, durationVar);
                mv.visitInsn(ACONST_NULL);
                mv.visitMethodInsn(INVOKESTATIC, "com/yian/agent/MethodMonitor", 
                                 "logMethodReturn", "(Ljava/lang/String;Ljava/lang/String;JLjava/lang/Object;)V", false);
            }
        } 
        // 如果是異常退出,記錄異常信息
        elseif (opcode == ATHROW) {
            // 復制異常引用
            mv.visitInsn(DUP);
            
            // 記錄異常
            mv.visitLdcInsn(className);
            mv.visitLdcInsn(methodName);
            mv.visitVarInsn(LLOAD, durationVar);
            mv.visitMethodInsn(INVOKESTATIC, "com/yian/agent/MethodMonitor", 
                             "logMethodException", "(Ljava/lang/String;Ljava/lang/String;JLjava/lang/Throwable;)V", false);
        }
    }
    
    /**
     * 復制返回值到棧頂
     */
    private void dupReturnValue(int opcode) {
        switch (opcode) {
            case IRETURN:
            case FRETURN:
                mv.visitInsn(DUP);
                break;
            case LRETURN:
            case DRETURN:
                mv.visitInsn(DDUP);
                break;
            case ARETURN:
                mv.visitInsn(DUP);
                break;
            default:
                // 不處理void返回
        }
    }
}
/**
 * 方法監控工具類,提供日志記錄功能
 */
public class MethodMonitor {
    private static final Logger logger = Logger.getLogger(MethodMonitor.class.getName());
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    private static FileWriter logWriter;
    
    static {
        // 初始化日志寫入器
        String logFile = AgentConfig.getLogFile();
        if (logFile != null && !logFile.isEmpty()) {
            try {
                File file = new File(logFile);
                // 創建父目錄
                if (!file.getParentFile().exists()) {
                    file.getParentFile().mkdirs();
                }
                // 追加模式
                logWriter = new FileWriter(file, true);
            } catch (IOException e) {
                logger.severe("初始化日志文件失敗: " + e.getMessage());
                logWriter = null;
            }
        }
    }
    
    /**
     * 記錄方法進入
     */
    public static void logMethodEnter(String className, String methodName) {
        String message = String.format("[%s] 方法進入: %s.%s()", 
                DATE_FORMAT.format(new Date()), className, methodName);
        writeLog(message);
    }
    
    /**
     * 記錄方法參數
     */
    public static void logMethodParameters(String className, String methodName, Object[] params) {
        StringBuilder sb = new StringBuilder();
        sb.append("[參數]: ");
        if (params != null) {
            for (int i = 0; i < params.length; i++) {
                sb.append("param").append(i).append("=")
                  .append(params[i] != null ? params[i].toString() : "null");
                if (i < params.length - 1) {
                    sb.append(", ");
                }
            }
        } else {
            sb.append("無參數");
        }
        
        String message = String.format("[%s] %s.%s(): %s", 
                DATE_FORMAT.format(new Date()), className, methodName, sb.toString());
        writeLog(message);
    }
    
    /**
     * 記錄方法返回
     */
    public static void logMethodReturn(String className, String methodName, long duration, Object returnValue) {
        String returnStr = returnValue != null ? returnValue.toString() : "void";
        String message = String.format("[%s] 方法返回: %s.%s() 執行時間: %dms, 返回值: %s", 
                DATE_FORMAT.format(new Date()), className, methodName, duration, returnStr);
        writeLog(message);
    }
    
    /**
     * 記錄方法異常
     */
    public static void logMethodException(String className, String methodName, long duration, Throwable throwable) {
        String exceptionMsg = throwable != null ? throwable.getClass().getName() + ": " + throwable.getMessage() : "未知異常";
        String message = String.format("[%s] 方法異常: %s.%s() 執行時間: %dms, 異常: %s", 
                DATE_FORMAT.format(new Date()), className, methodName, duration, exceptionMsg);
        writeLog(message);
        
        // 打印異常堆棧
        if (throwable != null) {
            throwable.printStackTrace();
        }
    }
    
    /**
     * 寫入日志到文件或控制臺
     */
    private static void writeLog(String message) {
        System.out.println(message); // 同時輸出到控制臺
        
        if (logWriter != null) {
            try {
                logWriter.write(message + "\n");
                logWriter.flush();
            } catch (IOException e) {
                logger.severe("寫入日志失敗: " + e.getMessage());
            }
        }
    }
}

字節碼增強的核心邏輯:

  • 使用DebugClassVisitor遍歷類中的所有方法
  • 對每個非構造方法使用DebugMethodVisitor進行處理
  • 在方法進入時(onMethodEnter):

記錄開始時間

打印方法調用信息

收集并打印方法參數

  • 在方法退出時(onMethodExit):
  • 計算執行時間
  • 記錄返回值(正常退出)
  • 記錄異常信息(異常退出)

MethodMonitor類提供了實際的日志記錄功能,支持輸出到控制臺和文件。

使用方法

在啟動SpringBoot應用時通過-javaagent參數指定Agent

java -javaagent:/path/to/springboot-debug-agent-1.0-SNAPSHOT-jar-with-dependencies.jar="include=com.yian.service.*;logFile=/tmp/debug.log" -jar your-springboot-app.jar

對于已經運行的應用,可以使用jattach工具動態附加Agent

# 安裝jattach(如果未安裝)
# Ubuntu: sudo apt install jattach
# CentOS: yum install jattach

# 動態附加Agent
jattach <pid> load instrument false "/path/to/springboot-debug-agent-1.0-SNAPSHOT-jar-with-dependencies.


責任編輯:武曉燕 來源: 一安未來
相關推薦

2025-09-09 07:46:02

DebugSpringAPI

2024-04-24 12:09:50

隱私數據泄露網絡安全

2022-01-05 15:07:41

Chrome瀏覽器無痕模式

2023-08-29 15:07:35

無服務器計算云計算

2020-12-31 05:57:36

DockerSpringBootMaven

2024-01-03 11:44:26

開發云服務

2023-09-27 07:26:09

2025-03-24 10:55:18

2020-10-09 07:00:00

無服務器應用監控架構

2017-09-13 07:23:03

2024-01-16 21:48:28

谷歌Chrome瀏覽器

2023-10-11 07:57:23

springboot微服務

2013-10-11 09:43:17

GoogleDisconnect搜索引擎

2025-06-27 16:07:08

AIAgent瀏覽器

2024-11-08 09:53:01

2018-10-24 12:15:06

無服務器軟件方式

2021-01-19 08:54:16

QQ隱私無痕瀏覽

2022-05-12 11:08:31

PHPJava開發

2019-07-02 10:55:21

云計算服務器容器

2018-01-17 10:28:34

JavaHttp Server服務器
點贊
收藏

51CTO技術棧公眾號

337p亚洲精品色噜噜噜| 成人av免费在线| 精品国产区一区二区三区在线观看| 91亚洲精品久久久蜜桃借种| 黄页网站在线观看免费| 久久品道一品道久久精品| 国产美女久久久| 国产一级一片免费播放| 精品国产91| 欧美成人综合网站| 青青青国产在线视频| 一色桃子av在线| 久久精品视频一区| 国产高清在线一区| 瑟瑟视频在线免费观看| 亚洲日韩视频| 久久九九国产精品怡红院| 97人妻天天摸天天爽天天| 日韩午夜电影免费看| 欧美性猛交xxxxx水多| 一级一片免费播放| 番号集在线观看| av亚洲精华国产精华| 中文字幕日韩av电影| 国产欧美精品区一区二区三区| 上原亚衣av一区二区三区| 亚洲色偷偷色噜噜狠狠99网| 日韩美香港a一级毛片| 精品国产乱码久久久久久虫虫漫画 | 日本伊人午夜精品| 九色91av视频| 在线免费看av不卡| 欧美国产日产韩国视频| 免费看污片网站| 国产精品qvod| 欧美一区午夜视频在线观看| 国外成人在线视频| 丝袜亚洲另类欧美重口| 国产三级三级三级看三级| 黑人精品视频| 一区二区三区四区在线播放| 91免费网站视频| 日本高清视频在线观看| 欧美高清在线一区二区| 蜜桃av噜噜一区二区三| 少妇高潮久久久| 成人黄色国产精品网站大全在线免费观看 | 97caopron在线视频| 国产片一区二区三区| 免费日韩电影在线观看| 天堂а在线中文在线无限看推荐| 不卡av免费在线观看| 动漫一区二区在线| 黄色av网站免费在线观看| 国产福利精品一区| 成人在线免费网站| 黄色av免费观看| 99热在这里有精品免费| 欧美激情导航| 国产一级免费在线观看| 欧美精彩视频一区二区三区| 亚洲精品一区二区三区蜜桃久| 91在线品视觉盛宴免费| 日韩一区有码在线| 黄色一级片国产| 波多野结衣在线播放| 亚洲va韩国va欧美va| 又粗又黑又大的吊av| 超碰91在线观看| 色婷婷精品久久二区二区蜜臀av| 亚洲最大综合网| 福利一区三区| 精品久久人人做人人爰| 亚洲天堂成人av| 激情五月综合| xxxxxxxxx欧美| 久久久久久久久久91| 极品中文字幕一区| 国产精品wwww| 99久久精品免费看国产交换| 不卡在线观看av| 欧美日韩一区综合| 欧美精品日韩少妇| 亚洲自拍偷拍av| 久热免费在线观看| 高清一区二区| 亚洲欧美制服中文字幕| 夫妇露脸对白88av| 黄色欧美成人| 国产精品1区2区在线观看| 国产美女三级无套内谢| 成人免费视频免费观看| 无码免费一区二区三区免费播放| aa在线视频| 富二代精品短视频| 国产高清999| 秋霞影视一区二区三区| 色狠狠av一区二区三区香蕉蜜桃| 国产一级在线播放| 蜜臀精品一区二区三区在线观看 | 国产精品久久久久一区二区| av免费观看在线| 久久久精品国产免大香伊| 在线成人性视频| 乡村艳史在线观看| 欧美一区二区三区性视频| 国产黄色网址在线观看| 欧美久久综合| 国产精品自产拍在线观| 三级在线电影| 一区二区三区免费观看| 色悠悠久久综合网| 妖精一区二区三区精品视频| 欧美成人自拍视频| 波多野结衣高清在线| 国产91精品露脸国语对白| 亚洲自拍的二区三区| 日韩影院在线| 精品国产一区二区精华| 日本一级片免费| 青青青爽久久午夜综合久久午夜 | www激情五月| 不卡视频在线| 国产91|九色| www精品国产| 成人欧美一区二区三区1314| 成人黄色片视频| 欧美性生活一级片| 欧美激情一级二级| 国产高清视频免费观看| 亚洲图片你懂的| 毛片毛片毛片毛| 欧洲专线二区三区| 欧美一区二三区| 日韩一二三四| 欧美午夜视频在线观看| 国产高清成人久久| 亚洲性视频h| 国产高清自拍一区| 男女在线观看视频| 日韩一卡二卡三卡四卡| 国产1区2区3区4区| 国产99久久久国产精品潘金网站| 欧美做受777cos| 欧美专区视频| 欧美高清电影在线看| jizz中国少妇| 亚洲午夜av在线| 欧美xxxxx精品| 亚洲深夜影院| 欧美精品亚洲| 国产在视频一区二区三区吞精| 中文在线不卡视频| 亚洲在线免费观看视频| 国产精品传媒入口麻豆| 亚洲 激情 在线| 国产精品99一区二区三| 91午夜在线播放| 图片区小说区亚洲| 亚洲第一精品电影| 视频一区二区三区四区五区| 久久美女艺术照精彩视频福利播放 | 欧美嫩在线观看| 色老板免费视频| 国产99久久久国产精品潘金网站| 成人午夜精品久久久久久久蜜臀| 欧美在线导航| 国产精品免费久久久久久| 黄色在线播放网站| 精品久久人人做人人爰| 国产精品久久久久久久久久久久久久久久久 | melody高清在线观看| 欧美日韩国产系列| 久久久一二三区| 久久伊人蜜桃av一区二区| 热久久精品免费视频| 久久久国产精品| 国产日韩欧美一区二区| 深夜成人影院| 久久综合88中文色鬼| 色婷婷av一区二区三| 一本一道久久a久久精品综合蜜臀 一本一道综合狠狠老 | 久久精品黄色| 欧美精品videos另类日本| 亚洲av成人精品毛片| 在线观看欧美日本| 欧美成人三级视频| 国产偷国产偷精品高清尤物| 午夜视频在线网站| 亚洲人成人一区二区三区| 日韩亚洲不卡在线| youjizz亚洲| 国产精品久久久久高潮| 欧美xxxbbb| 亚洲视频免费一区| 亚洲国产精品视频在线| 91极品视觉盛宴| 免费观看一级视频| 国产欧美一区二区三区沐欲| 91精品国产高清91久久久久久 | 一级网站在线观看| 亚洲欧美日韩国产一区二区| 波多野结衣三级在线| 亚洲大片精品免费| 国产精品12| 久久精品国产福利| 欧美孕妇与黑人孕交| 在线网址91| 日韩在线观看免费网站| 西西人体44www大胆无码| 欧美一区二区三区在线观看| 成人免费视频国产免费| 亚洲午夜激情av| 成人免费视频国产免费观看| 国产午夜精品美女毛片视频| 黄色国产在线视频| 国产成人综合亚洲网站| 女同激情久久av久久| 日精品一区二区| 欧美精品99久久| 好看的日韩av电影| 欧洲金发美女大战黑人| 久久视频在线| 亚洲成人自拍视频| 免费成人av| 久久精品国产理论片免费| 日韩中文字幕| 亚洲综合色av| 国产视频一区二| 国产精品一区二区久久精品| 日韩成人影音| 国产精品对白刺激| 久久野战av| 国产成人精品一区| 成人影院av| 欧洲精品毛片网站| 在线手机中文字幕| 668精品在线视频| 欧美激情护士| 69视频在线免费观看| 鲁鲁在线中文| 1769国产精品| 日本欧美日韩| 国产精品久久av| 国产一区二区色噜噜| 国产欧美日韩精品在线观看| 日韩欧国产精品一区综合无码| 国产美女高潮久久白浆| 四虎在线精品| 亚洲综合自拍一区| 亚洲精品一区在线| 国产伦精品一区二区三区免 | 亚洲色图第一页| 国产视频网站在线| 中文字幕精品在线视频| 黄色av网站在线播放| 久久国产精彩视频| 美女尤物在线视频| 欧美一级淫片videoshd| 人人鲁人人莫人人爱精品| 国产日韩欧美视频在线| 激情综合五月| 国产传媒一区二区三区| 五月综合久久| 亚洲激情一区二区| 午夜天堂精品久久久久| 很污的网站在线观看| 先锋a资源在线看亚洲| 国产一二三四在线视频| 精品一区二区三区不卡| 佐佐木明希电影| 337p粉嫩大胆噜噜噜噜噜91av| 久久久久久国产精品无码| 国产精品久久久久久久岛一牛影视 | 久久精品日韩精品| 国产一级成人av| 玛丽玛丽电影原版免费观看1977| 视频一区欧美| 综合操久久久| 亚洲激情偷拍| 好男人www社区| 国产精品1区2区3区| 久久偷拍免费视频| 中文幕一区二区三区久久蜜桃| 波多野结衣不卡视频| 五月天激情综合| 亚洲自拍第二页| 精品三级在线观看| 国产高清av在线| 欧美大片免费看| 亚洲第一会所| 国产精品视频入口| 日韩在线综合| 国内精品视频一区二区三区| 美女视频一区二区| 中国xxxx性xxxx产国| 国产精品成人一区二区艾草| 日韩经典在线观看| 欧美一级生活片| 黄色的视频在线免费观看| 久久精品国产视频| 成人在线爆射| 精品日本一区二区三区在线观看| 色琪琪久久se色| 又粗又黑又大的吊av| 国产精品一级二级三级| 天天躁夜夜躁狠狠是什么心态| 亚洲综合成人在线| 97免费观看视频| 亚洲最新中文字幕| 欧产日产国产精品视频| 成人动漫在线视频| 午夜久久免费观看| 啊啊啊国产视频| 久久久久久一级片| 日韩久久久久久久久| 欧美一级日韩免费不卡| 国产资源在线看| 97精品久久久中文字幕免费| 秋霞影院一区| 宅男av一区二区三区| 日韩高清不卡一区| 免费污网站在线观看| 黄网站色欧美视频| 亚洲风情第一页| 欧美成人精品xxx| 天天综合在线观看| 亚洲国产精品一区在线观看不卡| 国产日韩欧美三区| avtt香蕉久久| 亚洲成人av电影在线| 亚洲福利在线观看视频| 久久99久久亚洲国产| 国产美女亚洲精品7777| 一本一道久久a久久精品综合 | 日本成人在线免费| 亚洲色图一区二区| 91久久精品国产91性色69| 伊人久久久久久久久久久| 久久久一本精品| 奇米精品在线| 老司机午夜精品视频| 97伦伦午夜电影理伦片| 欧美色视频日本版| 三级视频在线播放| 国产成一区二区| 欧美日韩中字| 亚洲久久中文字幕| 国产精品不卡一区| 国产精品欧美综合亚洲| 超碰91人人草人人干| 蜜桃精品视频| 国产日本在线播放| 99久久久久久99| 欧美一区二区三区久久久| 亚洲天堂网在线观看| 国产电影一区二区三区爱妃记| 亚洲精品国产一区| 激情图区综合网| 欧美又粗又大又长| 亚洲黄色免费三级| 偷拍视频一区二区三区| 亚洲一区二区高清视频| 国产一区二区三区免费播放| 九九热只有精品| 精品视频在线观看日韩| 欧美与亚洲与日本直播| 色呦呦网站入口| 粉嫩av一区二区三区在线播放 | 欧美乱熟臀69xxxxxx| a天堂中文在线官网在线| 国产精品伊人日日| 久久婷婷av| 污污的视频在线免费观看| 精品区一区二区| 亚洲第一二三四区| mm131午夜| 99精品1区2区| 91精品国产色综合久久不8| 久久久久久久av| 精品国产一区二区三区av片| 一级淫片在线观看| 天天色天天操综合| 992tv免费直播在线观看| av一区二区三区四区电影| 香蕉亚洲视频| 99久久婷婷国产综合| 亚洲剧情一区二区| 精品999日本久久久影院| aaa毛片在线观看| 亚洲欧洲制服丝袜| 男人天堂综合| 999国产在线| 久久香蕉精品| 久久这里只有精品国产| 亚洲一区二区国产| 国产区精品视频在线观看豆花| 国产三级日本三级在线播放| 亚洲国产精品欧美一二99| 在线观看免费网站黄|