1-Spring AOP简介

1-1-什么是AOP

AOP的全称是Aspect-Oriented Programming,即面向切面编程(也称面向方面编程)。它是面向对象编程(OOP)的一种补充,目前已成为一种比较成熟的编程方式。

AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。

类与切面的关系

1-2-AOP术语

Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。

Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。

2-动态代理

2-1-JDK动态代理

JDK动态代理是通过java.lang.reflect.Proxy 类来实现的,我们可以调用Proxy类的newProxyInstance()方法来创建代理对象。对于使用业务接口的类,Spring默认会使用JDK动态代理来实现AOP。

2-2-案例演示

2-2-1-创建web项目

2-2-2-创建包、接口、实现类

package com.itheima.jdk;

public interface UserDao {
    void addUser();
    void deleteUser();

}

package com.itheima.jdk;

public class UserDaoImpl implements UserDao{

@Override
    public void addUser() {
        System.out.println("添加用户");
        
    }

@Override
    public void deleteUser() {
        System.out.println("删除用户");
        
    }

}

2-2-3-创建切面类

package com.itheima.aspect;

public class MyAspect {
    public void check_permissions() {
        System.out.println("模拟检查权限。。。");
        
    }
    public void log() {
        System.out.println("模拟检查日志。。");
    }
}

2-2-4-创建处理调用的类

public class JdkInvocationHandler implements InvocationHandler{
    //声明目标类接口
    private UserDao userDao;
    public Object createProxy(UserDao userDao) {
        this.userDao=userDao;
        ClassLoader classLoader = JdkInvocationHandler.class.getClassLoader();
        Class[] clazz = userDao.getClass().getInterfaces();
        return Proxy.newProxyInstance(classLoader, clazz, this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MyAspect myAspect = new MyAspect();
        myAspect.check_permissions();
        Object object = method.invoke(userDao, null);
        myAspect.log();
        
        return object;
    }    
}

2-2-5-创建测试类

public class JdkTest {
    public static void main(String[] args) {       
        JdkInvocationHandler handler = new JdkInvocationHandler();
        //创建目标对象
        UserDao userDao = new UserDaoImpl();
        //创建代理对象
        UserDao proxyUserDao = (UserDao) handler.createProxy(userDao);
        proxyUserDao.addUser();
        proxyUserDao.deleteUser();
    }

}

2-2-6-分析一下JDK生成动态的代理类

可以参考下面的链接

https://blog.csdn.net/jupiter_888/article/details/103852693

https://www.cnblogs.com/lonecloud/p/9158668.html

https://www.jb51.net/article/135597.htm

修改测试类,设置保存Java动态代理生成的类文件

public class JdkTest {
    public static void main(String[] args) throws Exception {
        // 设置保存Java动态代理生成的类文件
        saveGeneratedJdkProxyFiles();
        
       JdkInvocationHandler handler = new JdkInvocationHandler();
        //创建目标对象
        UserDao userDao = new UserDaoImpl();
        //创建代理对象
        UserDao proxyUserDao = (UserDao) handler.createProxy(userDao);

proxyUserDao.addUser();
        proxyUserDao.deleteUser();
    }
    
    /**
     * 设置保存Java动态代理生成的类文件。
     *
     * @throws Exception
     */
    public static void saveGeneratedJdkProxyFiles() throws Exception {
        Field field = System.class.getDeclaredField("props");
        field.setAccessible(true);
        Properties props = (Properties) field.get(null);
        props.put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    }

}

反编译代理类,其位置在你项目源代码下,如下图

核心代码

public final class $Proxy0 extends Proxy
    implements UserDao
{

private static Method m1;
    private static Method m2;
    private static Method m0;
    private static Method m3;
    private static Method m4;

public $Proxy0(InvocationHandler invocationhandler)
    {
        super(invocationhandler);
    }

public final void addUser()
    {
        try
        {
            super.h.invoke(this, m3, null);
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

static
    {
        try
        {
            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]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            m3 = Class.forName("com.itheima.jdk.UserDao").getMethod("addUser", new Class[0]);
            m4 = Class.forName("com.itheima.jdk.UserDao").getMethod("deleteUser", new Class[0]);
        }

2-2-7-测试类运行过程图

2-3-CGLIB代理

CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它采用非常底层的字节码技术,对指定的目标类生成一个子类,并对子类进行增强。

2-4-案例演示

2-4-1-添加spring核心包、创建包、目标类

package com.itheima.cglib;

public class UserDao {

public void addUser() {
        System.out.println("添加用户");
        
    }

public void deleteUser() {
        System.out.println("删除用户");
        
    }

}

2-4-2-创建方法拦截器

//创建方法拦截器
public class CglibMethodInterceptor implements MethodInterceptor {
    //创建代理对象
    public Object createProxy(Object target) {
        //创建字节码增强器
        Enhancer enhancer = new Enhancer();
        //确定需要增强的类,作为增强器的父类
        enhancer.setSuperclass(target.getClass());
        
        enhancer.setCallback(this);
        //返回创建的代理类
        return enhancer.create();
    }
    /**
     * proxy 代理对象
     * method 拦截的方法
     * args 拦截方法参数数组
     * methodProxy 方法的代理对象,用于执行父类的方法
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        MyAspect myAspect = new MyAspect();
        myAspect.check_permissions();
        Object object = methodProxy.invokeSuper(proxy, args);
        myAspect.log();
        
        return object;
    }

}

2-4-3-创建测试类

public class CglibTest {
    public static void main(String[] args) {
        CglibMethodInterceptor interceptor = new CglibMethodInterceptor();
        UserDao userDao = new UserDao();
        UserDao userDao1 = (UserDao) interceptor.createProxy(userDao);
        userDao1.addUser();
        userDao1.deleteUser();
    }

}

3-基于代理类的AOP实现

3-1- Spring的通知类型

org.springframework.aop.MethodBeforeAdvice(前置通知)
     在目标方法执行前实施增强,可以应用于权限管理等功能。
org.springframework.aop.AfterReturningAdvice(后置通知)
     在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除
     临时文件等功能。
org.aopalliance.intercept.MethodInterceptor(环绕通知)
     在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。
org.springframework.aop.ThrowsAdvice(异常抛出通知)
     在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。
org.springframework.aop.IntroductionInterceptor(引介通知)
     在目标类中添加一些新的方法和属性,可以应用于修改老版本程序。

3-2- ProxyFactoryBean

ProxyFactoryBean是FactoryBean接口的实现类,FactoryBean负责实例化一个Bean,而ProxyFactoryBean负责为其他Bean创建代理实例。在Spring中,使用ProxyFactoryBean是创建AOP代理的基本方式。

ProxyFactoryBean类中的常用可配置属性如下:

3-3-环绕通知案例

3-3-1-添加aop相关的jar

3-3-2-创建包、切面类

package com.itheima.factorybean;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyAspect implements MethodInterceptor {

@Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        check_permissions();
        Object object = mi.proceed();
        log();
        return object;
    }

public void check_permissions() {
        System.out.println("模拟检查权限。。。");
        
    }
    public void log() {
        System.out.println("模拟检查日志。。");
    }
}

3-3-3-创建配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
    <!-- 1 目标类 -->
    <bean id="userDao" class="com.itheima.jdk.UserDaoImpl" />
    <!-- 2 切面类 -->
    <bean id="myAspect" class="com.itheima.factorybean.MyAspect" />
    <!-- 3 使用Spring代理工厂定义一个名称为userDaoProxy的代理对象 -->
    <bean id="userDaoProxy"
            class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 3.1 指定代理实现的接口-->
        <property name="proxyInterfaces"
                      value="com.itheima.jdk.UserDao" />
        <!-- 3.2 指定目标对象 -->
        <property name="target" ref="userDao" />
        <!-- 3.3 指定切面,织入环绕通知 -->
        <property name="interceptorNames" value="myAspect" />
        <!-- 3.4 指定代理方式,true:使用cglib,false(默认):使用jdk动态代理 -->
        <property name="proxyTargetClass" value="true" />
    </bean>
</beans>

3-3-4-创建测试类

// 测试类
public class ProxyFactoryBeanTest {
    public static void main(String args[]) {
       String xmlPath = "com/itheima/factorybean/applicationContext.xml";
       ApplicationContext applicationContext =
                                 new ClassPathXmlApplicationContext(xmlPath);
       // 从Spring容器获得内容
       UserDao userDao =
                       (UserDao) applicationContext.getBean("userDaoProxy");
       // 执行方法
       userDao.addUser();
       userDao.deleteUser();
    }
}

4-AspectJ开发

AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。

Spring 2.0以后,Spring AOP引入了对AspectJ的支持,并允许直接使用AspectJ进行编程,而Spring自身的AOP API也尽量与AspectJ保持一致。

新版本的Spring框架,也建议使用AspectJ来开发AOP。

使用AspectJ实现AOP有两种方式:一种是基于XML的声明式AspectJ,另一种是基于注解的声明式AspectJ。

4-1-基于XML的声明式AspectJ

基于XML的声明式AspectJ是指通过XML文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在<aop:config>元素内。

4-1-1-<aop:config>元素及其子元素如下:

4-1-2-XML文件中常用元素的配置方式如下:

<bean id="myAspect" class="com.itheima.aspectj.xml.MyAspect" />
           <aop:config>
                  <aop:aspect  id="aspect"  ref="myAspect">
                  <aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))“ id="myPointCut" />
                  <aop:before method="myBefore" pointcut-ref="myPointCut" />
                  <aop:after-returning method="myAfterReturning“ pointcut-ref="myPointCut"  returning="returnVal" />
                  <aop:around method="myAround" pointcut-ref="myPointCut" />
                  <aop:after-throwing method="myAfterThrowing“ pointcut-ref="myPointCut"  throwing="e" />
                  <aop:after method="myAfter" pointcut-ref="myPointCut" />
            </aop:aspect>
     </aop:config>

4-1-3-配置切面

<aop:aspect>将一个已定义好的Spring Bean转换成切面Bean

4-1-4-配置切入点

当<aop:pointcut>元素作为<aop:config>元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;

当<aop:pointcut>元素作为<aop:aspect>元素的子元素时,表示该切入点只对当前切面有效。

切入点表达式

execution(* com.itheima.jdk.*.*(..)) 是定义的切入点表达式,该切入点表达式的意思是匹配com.itheima.jdk包中任意类的任意方法的执行。

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

带有问号(?)的部分表示可选配置项,而其他部分属于必须配置项。

modifiers-pattern:定义的目标方法的访问修饰符,如public、private等

ret-type-pattern:定义的目标方法的返回值类型,如void、String等

declaring-type-pattern:定义的目标方法的类路径,如com.itheima.jdk.UserDaoImpl

name-pattern:具体需要被代理的目标方法,如add()方法

param-pattern:需要被代理的目标方法包含的参数

throws-pattern:需要被代理的目标方法抛出的异常类型

4-1-5-配置通知

5种常用通知

4-1-6-案例代码

1、导入AspectJ框架相关的包

spring-aspects-4.3.6.RELEASE.jar:实现包

aspectjweaver-1.8.10.jar:规范包

2、创建包、切面类

package com.itheima.aspectj.xml;

public class MyAspect {
    // 前置通知
    public void myBefore(JoinPoint joinPoint) {
        System.out.print("前置通知 :模拟执行权限检查...,");
        System.out.print("目标类是:"+joinPoint.getTarget() );
        System.out.println(",被织入增强处理的目标方法为:"
                            +joinPoint.getSignature().getName());
    }
    // 后置通知
    public void myAfterReturning(JoinPoint joinPoint) {
        System.out.print("后置通知:模拟记录日志...," );
        System.out.println("被织入增强处理的目标方法为:"
                          + joinPoint.getSignature().getName());
    }
    /**
     * 环绕通知
     * ProceedingJoinPoint 是JoinPoint子接口,表示可以执行目标方法
     * 1.必须是Object类型的返回值
     * 2.必须接收一个参数,类型为ProceedingJoinPoint
     * 3.必须throws Throwable
     */
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
             throws Throwable {
        // 开始
        System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
        // 执行当前目标方法
        Object obj = proceedingJoinPoint.proceed();
        // 结束
        System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
        return obj;
    }
    // 异常通知
    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("异常通知:" + "出错了" + e.getMessage());
    }
    // 最终通知
    public void myAfter() {
        System.out.println("最终通知:模拟方法结束后的释放资源...");
    }
}

3、创建配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
    <!-- 1 目标类 -->
    <bean id="userDao" class="com.itheima.jdk.UserDaoImpl" />
    <!-- 2 切面 -->
    <bean id="myAspect" class="com.itheima.aspectj.xml.MyAspect" />
    <!-- 3 aop编程 -->
    <aop:config>
        <!-- 配置切面 -->
        <aop:aspect ref="myAspect">
          <!-- 3.1 配置切入点,通知最后增强哪些方法 -->
          <aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))"
                                                      id="myPointCut" />
            <!-- 3.2 关联通知Advice和切入点pointCut -->
            <!-- 3.2.1 前置通知 -->
            <aop:before method="myBefore" pointcut-ref="myPointCut" />
            <!-- 3.2.2 后置通知,在方法返回之后执行,就可以获得返回值
             returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
            <aop:after-returning method="myAfterReturning"
                pointcut-ref="myPointCut" returning="returnVal" />
            <!-- 3.2.3 环绕通知 -->
            <aop:around method="myAround" pointcut-ref="myPointCut" />
            <!-- 3.2.4 抛出通知:用于处理程序发生异常-->
            <!-- * 注意:如果程序没有异常,将不会执行增强 -->
            <!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
            <aop:after-throwing method="myAfterThrowing"
                pointcut-ref="myPointCut" throwing="e" />
            <!-- 3.2.5 最终通知:无论程序发生任何事情,都将执行 -->
            <aop:after method="myAfter" pointcut-ref="myPointCut" />
        </aop:aspect>
    </aop:config>
</beans>

4、创建测试类

public class TestXmlAspectj {
    public static void main(String args[]) {
        String xmlPath =
                         "com/itheima/aspectj/xml/applicationContext.xml";
        ApplicationContext applicationContext =
                          new ClassPathXmlApplicationContext(xmlPath);
        // 1 从spring容器获得内容
        UserDao userDao = (UserDao) applicationContext.getBean("userDao");
        // 2 执行方法
        userDao.addUser();
    }
}

4-2-基于注解的声明式AspectJ

AspectJ框架为AOP的实现提供了一套注解,用以取代Spring配置文件中为实现AOP功能所配置的臃肿代码。

4-2-1-AspectJ的注解及其描述如下所示:

4-2-2-案例演示

1、创建包、复制切面类并修改

package com.itheima.aspectj.annotation;

@Aspect
@Component
public class MyAspect {
    // 定义切入点表达式
    @Pointcut("execution(* com.itheima.jdk.*.*(..))")
    // 使用一个返回值为void、方法体为空的方法来命名切入点
    private void myPointCut(){}
    // 前置通知
    @Before("myPointCut()")
    public void myBefore(JoinPoint joinPoint) {
        System.out.print("前置通知 :模拟执行权限检查...,");
        System.out.print("目标类是:"+joinPoint.getTarget() );
        System.out.println(",被织入增强处理的目标方法为:"
                       +joinPoint.getSignature().getName());
    }
    // 后置通知
    @AfterReturning(value="myPointCut()")
    public void myAfterReturning(JoinPoint joinPoint) {
        System.out.print("后置通知:模拟记录日志...," );
        System.out.println("被织入增强处理的目标方法为:"
                      + joinPoint.getSignature().getName());
    }
    // 环绕通知    
    @Around("myPointCut()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
            throws Throwable {
        // 开始
        System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
        // 执行当前目标方法
        Object obj = proceedingJoinPoint.proceed();
        // 结束
        System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
        return obj;
    }
    // 异常通知
    @AfterThrowing(value="myPointCut()",throwing="e")
    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("异常通知:" + "出错了" + e.getMessage());
    }
    // 最终通知
    @After("myPointCut()")
    public void myAfter() {
        System.out.println("最终通知:模拟方法结束后的释放资源...");
    }
}

2、目标类上添加注解

@Repository("userDao")
public class UserDaoImpl implements UserDao{

3、创建配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
  http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context-4.3.xsd">
      <!-- 指定需要扫描的包,使注解生效 -->
      <context:component-scan base-package="com.itheima" />
      <!-- 启动基于注解的声明式AspectJ支持 -->
      <aop:aspectj-autoproxy />
</beans>

4、创建测试类

public class TestAnnotationAspectj {
    public static void main(String args[]) {
        String xmlPath =
                  "com/itheima/aspectj/annotation/applicationContext.xml";
        ApplicationContext applicationContext =
                 new ClassPathXmlApplicationContext(xmlPath);
        // 1 从spring容器获得内容
        UserDao userDao = (UserDao) applicationContext.getBean("userDao");
        // 2 执行方法
        userDao.addUser();
    }
}

3 - Spring AOP相关推荐

  1. Spring AOP + Redis解决重复提交的问题

    Spring AOP + Redis解决重复提交的问题 用户在点击操作的时候,可能会连续点击多次,虽然前端可以通过设置按钮的disable的属性来控制按钮不可连续点击,但是如果别人拿到请求进行模拟,依 ...

  2. 利用Spring AOP与JAVA注解为系统增加日志功能

    Spring AOP一直是Spring的一个比较有特色的功能,利用它可以在现有的代码的任何地方,嵌入我们所想的逻辑功能,并且不需要改变我们现有的代码结构. 鉴于此,现在的系统已经完成了所有的功能的开发 ...

  3. Spring AOP的一些概念

            切面(Aspect): 一个关注点的模块化,这个关注点可能会横切多个对象.事务管理是J2EE应用中一个关于横切关注点的很好的例子. 在Spring AOP中,切面可以使用通用类(基于模 ...

  4. Spring AOP与IOC

    Spring AOP实现日志服务 pom.xml需要的jar <dependency><groupId>org.apache.commons</groupId>&l ...

  5. Spring AOP与IOC以及自定义注解

    Spring AOP实现日志服务 pom.xml需要的jar <dependency><groupId>org.apache.commons</groupId>&l ...

  6. Spring Aop的应用

    2019独角兽企业重金招聘Python工程师标准>>> AOP的基本概念 连接点( Jointpoint) : 表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化.方法执行 ...

  7. Spring AOP详解(转载)所需要的包

    上一篇文章中,<Spring Aop详解(转载)>里的代码都可以运行,只是包比较多,中间缺少了几个相应的包,根据报错,几经百度搜索,终于补全了所有包. 截图如下: 在主测试类里面,有人怀疑 ...

  8. 关于spring aop Advisor排序问题

    关于spring aop Advisor排序问题 当我们使用多个Advisor的时候有时候需要排序,这时候可以用注解org.springframework.core.annotation.Order或 ...

  9. 利用spring aop统一处理异常和打日志

    利用spring aop统一处理异常和打日志 spring aop的概念,很早就写博客介绍了,现在在工作中真正使用. 我们很容易写出的代码 我们很容易写出带有很多try catch 和 logger. ...

  10. 我所理解的Spring AOP的基本概念

    Spring AOP中的概念晦涩难懂,读官方文档更是像读天书,看了很多例子后,写一些自己理解的一些spring的概念.要理解面向切面编程,要首先理解代理模式和动态代理模式. 假设一个OA系统中的一个功 ...

最新文章

  1. 盛大文学推出“一人一书”计划,发布电子书战略
  2. iOS 获取屏幕最上层window以及响应者
  3. javascript获取textarea中光标的位置 兼容
  4. 请大家访问另一个我的博客!
  5. javascript构造函数继承
  6. C语言笔记:格式化输入输出(fprintf、fscanf、sscanf...)
  7. Linux-(C/C++)动态链接库生成以及使用(libxxx.so)
  8. 各种损失损失函数的使用场景和使用方法:KL散度
  9. Python界面 PyQT可视化开发(python3+PyQt5+Qt Designer)
  10. Linux 查看ssh登录日志 ssh登录记录
  11. HTTP中GET与POST的区别,99 %的人都理解错了
  12. SIFT算法原理介绍
  13. centos 查看版本号方法
  14. Google Cloud Platform
  15. SV学习(9)——随机函数、数组约束、随机控制
  16. Oracle调优总结--1(经典实践 重要)
  17. 如何重新认知性能优化及其度量方法
  18. Unity Cinemachine之第三人称摄像机CinemachineFreeLook属性详解
  19. (转)超棒的EXCEL使用技巧
  20. 计算机没考好的检讨书300百以上,考试反思检讨书300字范文7篇

热门文章

  1. Tomcat配置可以访问外部资源路径
  2. sql 查询 tag_Askgit:给git增加个翅膀,用sql挖掘仓库的信息
  3. 【Redis】Redis 使用 redisson 做分布式锁 复盘 maven 依赖 netty 冲突
  4. 90-40-010-源码-CUBE-引擎为MR写入Habse的构建
  5. 【Spring】Spring 自定义scope
  6. Kudu : 命令行接口
  7. Flink eventTime案例无输出
  8. 【Sentry】Sentry安装
  9. Drools 6.5 :入门程序
  10. 大数据面试-03-大数据工程师面试题