Spring AOP(2)原理(代理模式和源码解析)

目录

一、代理模式

二、静态代理

三、动态代理

1、JDK动态代理

(1)JDK动态代理实现步骤

(2)定义JDK动态代理类

(3)代码简单讲解

2、CGLIB动态代理

[(1)CGLIB 动态代理类实现步骤](#(1)CGLIB 动态代理类实现步骤)

(2)添加依赖

(3)自定义MethodInterceptor(方法拦截器)

(4)创建代理类,并使用

(5)代码简单讲解

[四、Spring AOP 源码剖析(了解)](#四、Spring AOP 源码剖析(了解))

五、常见面试题

[1、什么是 AOP?](#1、什么是 AOP?)

[2、Spring AOP的实现方式有哪些?](#2、Spring AOP的实现方式有哪些?)

[3、Spring AOP 的实现原理?](#3、Spring AOP 的实现原理?)

[4、Spring 使用的是哪种代理方式?](#4、Spring 使用的是哪种代理方式?)

[5、JDK 和 CGLIB 的区别?](#5、JDK 和 CGLIB 的区别?)

六、总结


上篇文章学习了 Spring AOP 的应用,接下来我们来学习 Spring AOP 的原理,也就是 Spring 是如何实现 AOP 的。

Spring AOP 是基于动态代理来实现 AOP 的,咱门学习内容主要分以下两部分:

1、代理模式

2、Spring AOP 源码 剖析


一、代理模式

代理模式,也叫 委托模式。

定义为其他对象提供一种代理以控制这个对象的访问。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。

某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介作用。

使用代理前:

使用代理后:

生活中的代理:

艺人经纪人:广告商找艺人拍广告,需要经过经纪人,由经纪人来和艺人沟通。

房屋中介:房屋进行租赁时,卖方会把房屋授权给中介,由中介来代理看房,房屋咨询等服务。

经销商:厂商不直接对外销售产品,由经销商负责代理销售。

秘书/助理:合作伙伴找老板谈合作,需要先经过秘书/助理预约。

代理模式的主要角色:

1、Subject :业务接口类。可以是抽象类或者接口(不一定有)。

2、RealSubject :业务实现类。具体的业务执行,也就是被代理对象。

3、Proxy :代理类。RealSubject的代理。

比如 房屋出租:

Subject:就是提前定义了房东做的事情,交给中介代理,也是中介要做的事情。

RealSubject:房东。

Proxy:中介。

UML类图如下:

代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。根据代理的创建时期,代理模式分为静态代理和动态代理。

静态代理 :由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。

动态代理 :在程序运行时,运用反射机制动态创建而成。


二、静态代理

静态代理:在程序运行前,代理类的 .class文件 就已经存在了。(在出租房子之前,中介已经做好了相关的工作,就等租户来租房子了)。

我们通过代码来加深理解。以房租租赁为例:

1、定义接口(定义房东要做的事情,也是中介需要做的事情)

java 复制代码
public interface HouseSubject {
    void rentHouse();
}

2、实现接口(房东出租房子)

java 复制代码
public class RealHouseSubject implements HouseSubject{
    @Override
    public void rentHouse() {
        System.out.println("我是房东, 我出租房子");
    }
}

3、代理(中介,帮房东出租房子)

java 复制代码
public class HouseProxy implements HouseSubject{
    private HouseSubject target;

    public HouseProxy(HouseSubject target) {
        this.target = target;
    }

    @Override
    public void rentHouse() {
        //代理前
        System.out.println("我是中介, 开始代理");
        //出租房子
        target.rentHouse();;
        //代理后
        System.out.println("我是中介, 结束代理");
    }
}

4、使用

java 复制代码
public class Main {
    public static void main(String[] args) {
        HouseSubject subject = new RealHouseSubject();
        //创建代理类
        HouseProxy houseProxy = new HouseProxy(subject);
        //通过代理类访问⽬标⽅法
        houseProxy.rentHouse();
    }
}

运行结果:

上面这个代理实现方式就是静态代理(仿佛啥也没干)。从上述程序可以看出,虽然静态代理也完成了对目标对象的代理,但是由于代码都写死了,对目标对象的每个方法的增强都是手动完成的,非常不灵活。所以日常开发几乎看不到静态代理的场景。

接下来新增需求:中介又新增了其他业务:代理房屋出售。我们就需要对上述代码进行修改。

1、接口定义修改:

java 复制代码
public interface HouseSubject {
    void rentHouse();
    void saleHouse();
}

2、接口实现修改

java 复制代码
public class RealHouseSubject implements HouseSubject{
    @Override
    public void rentHouse() {
        System.out.println("我是房东, 我出租房子");
    }

    @Override
    public void saleHouse() {
        System.out.println("我是房东, 我出售房子");
    }
}

3、代理类修改

java 复制代码
public class HouseProxy implements HouseSubject{
    private HouseSubject target;

    public HouseProxy(HouseSubject target) {
        this.target = target;
    }

    @Override
    public void rentHouse() {
        //代理前
        System.out.println("我是中介, 开始代理");
        //出租房子
        target.rentHouse();;
        //代理后
        System.out.println("我是中介, 结束代理");
    }

    @Override
    public void saleHouse() {
        //代理前
        System.out.println("我是中介, 开始代理");
        //出租房子
        target.rentHouse();;
        //代理后
        System.out.println("我是中介, 结束代理");
    }
}

4、使用

java 复制代码
public class Main {
    public static void main(String[] args) {
        HouseSubject subject = new RealHouseSubject();
        //创建代理类
        HouseProxy houseProxy = new HouseProxy(subject);
        //通过代理类访问⽬标⽅法
        houseProxy.rentHouse();
        System.out.println("=========");
        houseProxy.saleHouse();
    }
}

运行结果:

从上述代码可以看出,我们修改接口(Subject)和业务实现类(RealSubject),还需要修改代理类(Proxy)。

同样的,如果新增接口(Subject)和业务实现类(RealSubject),也需要对每一个业务实现类新增代理类(Proxy)。

既然代理的流程是一样的,有没有一种办法,让他们通过一个代理类来实现呢?这就需要用到动态代理技术了。


三、动态代理

相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标对象都单独创建一个代理对象,而是把这个创建代理对象的工作推迟到程序运行时由JVM来实现 。也就是说动态代理在程序运行时,根据需要动态创建生成。

比如房屋中介,我不需要提前预测都有哪些业务,而是业务来了我再根据情况创建。

先看代码再来理解。Java也对动态代理进行了实现,并给我们提供一些API,常见的实现方式有两种:

1、JDK动态代理

2、CGLIB动态代理

动态代理在我们日常开发中使用的相对较少,但是在框架中几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。

1、JDK动态代理

(1)JDK动态代理实现步骤

1、定义一个接口及其实现类(静态代理中的 HouseSubject 和 RealHouseSubject)。

2、自定义 InvocationHandler 并重写 invoke 方法,在 invoke 方法中我们会调用目标方法(被代理类的方法),并自定义一些处理逻辑。

3、通过 Proxy.newProxyInstance(ClassLoader, Class<?>[ ] Interfaces, InvocationHandler h)方法创建代理对象。

(2)定义JDK动态代理类

创建 JDKInvocationHandler类 实现 InvocationHandler 接口:

java 复制代码
public class JDKInvocationHandler implements InvocationHandler {
    //目标对象,即被代理的对象
    private Object target;

    public JDKInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //代理增强内容
        System.out.println("我是中介,开始代理");
        //通过反射调用被代理类的方法
        Object result = method.invoke(target, args);
        //代理增强内容
        System.out.println("我是中介,结束代理");
        return result;
    }
}

创建一个代理对象并使用:

java 复制代码
public class Main {
    public static void main(String[] args) {
        /**
         * JDK动态代理
         */

        //创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
        RealHouseSubject target = new RealHouseSubject();//目标对象
        /**
         * newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
         * loader:加载我们的被代理类的ClassLoad
         * interfaces:要实现的接口
         * h:代理要做的事情,需要实现 InvocationHandler 这个接口
         */
        HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class[]{HouseSubject.class},
                new JDKInvocationHandler(target)
        );
        proxy.rentHouse();
        System.out.println("==============");
        proxy.saleHouse();
    }
}

运行程序,结果如下:

假设代理的是类,而不是对象,代码如下:

java 复制代码
public class Main {
    public static void main(String[] args) {
        RealHouseSubject target = new RealHouseSubject();
        RealHouseSubject proxy = (RealHouseSubject) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class[]{RealHouseSubject.class},
                new JDKInvocationHandler(target)
        );
        proxy.rentHouse();
        System.out.println("==============");
        proxy.saleHouse();
    }
}

运行程序,结果如下:(报错了)

报错原因 :RealHouseSubject is not an interface(RealHouseSubject 类不是接口),说明JDK 动态代理只能代理接口,不能代理类,不然会报错。

(3)代码简单讲解

主要是学习API的使用,我们按照 Java API 的规范来使用即可。

1、InvocationHandler:

InvocationHandler 接口是 Java 动态代理的关键接口之一,它定义了一个单一方法 invoke(),用于处理被代理对象的方法调用。

java 复制代码
    public interface InvocationHandler {
        /**
         * 参数说明
         * proxy:被代理对象
         * method:被代理对象需要实现的⽅法,即其中需要重写的⽅法
         * args:method所对应⽅法的参数
         */
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable;
    }

通过实现 InvocationHandler 接口,可以对被代理对象的方法进行功能增强。

2、Proxy:

Proxy 类中使用频率最高的方法:newProxyInstance(),这个方法主要用来生成一个代理对象。

java 复制代码
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
            throws IllegalArgumentException
    {
        //...代码省略
    }

这个方法一共有 3 个参数:

loader :类加载器,用于加载被代理对象。

interface :被代理类实现的一些接口(这个参数的定义,也决定了JDK动态代理只能代理实现了接口的一些类)。

h :代理要做的事情,实现 InvocationHandler 接口的对象。

2、CGLIB动态代理

JDK动态代理有一个最致命的问题 ,是只能代理实现了接口的类。

有些场景下,我们的业务码是直接实现的,并没有接口定义。为了解决这个问题,我们可以用 CGLIB 动态代理机制来解决。

CGLIB(Code Generation Library)是一个基于 ASM 的字节码生产库,它允许我们在运行时对字节码进行修改和动态生成。

CGLIB 通过继承方式实现代理,很多知名的开源框架都使用到了 CGLIB。例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。(其中 Spring 是基于动态代理实现的,动态代理是基于反射实现的

(1)CGLIB 动态代理类实现步骤

1、定义一个类(被代理类)。

2、自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于增强目标方法,和 JDK 动态代理中的 invoke 方法类似。

3、通过 Enhancer 类的 create() 创建代理类。

接下来看实现:

(2)添加依赖

和 JDK 动态代理不同,CGLIB(Code Generation Library)实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。

XML 复制代码
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

(3)自定义MethodInterceptor(方法拦截器)

实现 MethodInterceptor 接口:

java 复制代码
import org.springframework.cglib.proxy.MethodInterceptor;
import java.lang.reflect.Method;

public class CGLibInterceptor implements MethodInterceptor {
    private Object target;

    public CGLibInterceptor(Object target) {
        this.target = target;
    }

    /**
     * 调用代理对象的方法
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, org.springframework.cglib.proxy.MethodProxy proxy) throws Throwable {
        //代理增强内容
        System.out.println("我是中介,开始代理");
        Object result = method.invoke(target, args);
        //代理增强内容
        System.out.println("我是中介,结束代理");
        return result;
    }
}

(4)创建代理类,并使用

代理接口:

java 复制代码
public class Main {
    public static void main(String[] args) {
        //目标对象
        HouseSubject target = new RealHouseSubject();
        HouseSubject proxy  = (HouseSubject) Enhancer.create(target.getClass(), new CGLibInterceptor(target));
        proxy.rentHouse();
        System.out.println("=============");
        proxy.saleHouse();
    }
}

运行程序,执行结果如下:

代理类:

java 复制代码
public class Main {
    public static void main(String[] args) {
        //目标对象
        HouseSubject target = new RealHouseSubject();
        RealHouseSubject proxy  = (RealHouseSubject) Enhancer.create(target.getClass(), new CGLibInterceptor(target));
        proxy.rentHouse();
        System.out.println("=============");
        proxy.saleHouse();
    }
}

运行程序,执行结果如下:

(5)代码简单讲解

1、MethodInterceptor

MethodInterceptor 和 JDK动态代理中的 InvocationHandler 类似,它只定义了一个方法 intercept(),用于增强目标方法。

html 复制代码
    public interface MethodInterceptor extends Callback {
        /**
         * 参数说明:
         * o: 被代理的对象
         * method: ⽬标⽅法(被拦截的⽅法, 也就是需要增强的⽅法)
         * objects: ⽅法⼊参
         * methodProxy: ⽤于调⽤原始⽅法
         */
        Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
    }

2、Enhancer.create()

java 复制代码
    public static Object create(Class type, Callback callback) {
        //...代码省略
    }

type :被代理类的类型(类或接口)

callback :自定义方法拦截器 MethodInterceptor


四、Spring AOP 源码剖析(了解)

Spring AOP 主要基于两种方式实现的:JDK 及 CGLIB 的方式。

Spring 源码过于复杂,我们只摘出一些主要内容,以了解为主

Spring 对于 AOP 的实现,基本都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成 生成代理对象的逻辑在父类 AbstractAutoProxyCreator 中。

java 复制代码
protected Object createProxy(Class<?> beanClass,@Nullable String beanName,
@Nullable Object[]specificInterceptors,TargetSource targetSource){
        if(this.beanFactory instanceof ConfigurableListableBeanFactory){
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)
        this.beanFactory,beanName,beanClass);
        }
        //创建代理⼯⼚
        ProxyFactory proxyFactory=new ProxyFactory();
        proxyFactory.copyFrom(this);
        /**
         * 检查proxyTargetClass属性值,spring默认为false
         * proxyTargetClass 检查接⼝是否对类代理, ⽽不是对接⼝代理
         * 如果代理对象为类, 设置为true, 使⽤cglib代理
         */
        if(!proxyFactory.isProxyTargetClass()){
            //是否有设置cglib代理
            if(shouldProxyTargetClass(beanClass,beanName)){
            //设置proxyTargetClass为true,使⽤cglib代理
            proxyFactory.setProxyTargetClass(true);
        }else{
            /**
             * 如果beanClass实现了接⼝,且接⼝⾄少有⼀个⾃定义⽅法,则使⽤JDK代理
             * 否则CGLIB代理(设置ProxyTargetClass为true )
             * 即使我们配置了proxyTargetClass=false, 经过这⾥的⼀些判断还是可能会将其
             设为true
             */
            evaluateProxyInterfaces(beanClass,proxyFactory);
            }
            }
            Advisor[]advisors=buildAdvisors(beanName,specificInterceptors);
            proxyFactory.addAdvisors(advisors);
            proxyFactory.setTargetSource(targetSource);
            customizeProxyFactory(proxyFactory);
    
            proxyFactory.setFrozen(this.freezeProxy);
            if(advisorsPreFiltered()){
            proxyFactory.setPreFiltered(true);
        }

        // Use original ClassLoader if bean class not locally loaded in overriding class loader
        ClassLoader classLoader =getProxyClassLoader();
        if(classLoader instanceof SmartClassLoader&&classLoader != beanClass.getClassLoader()){
            classLoader=((SmartClassLoader)classLoader).getOriginalClassLoader();
        }
//从代理⼯⼚中获取代理
        return proxyFactory.getProxy(classLoader);
        }

代理工厂有一个重要的属性:proxyTargetClass,默认值为false。也可以通过程序设置

|------------------|--------------|---------|
| proxyTargetClass | ⽬标对象 | 代理⽅式 |
| false | 实现了接口 | jdk代理 |
| false | 未实现接口(只有实现类) | cglib代理 |
| true | 实现了接口 | cglib代理 |
| true | 未实现接口(只有实现类) | cglib代理 |

可以通过 @EnableAspectJAutoProxy(proxyTargetClass = true) 来设置。

注意:

Spring 默认 proxyTargetClass:false,会分为两种情况:

实现了接口 :使用 JDK 代理。

普通类 : 使用 CGLIB 代理。

Spring Boot 2.X 开始,默认使用 proxyTargetClass:true

默认使用 CGLIB 代理

SpringBoot设置 @EnableAspectJAutoProxy 无效,因为 Spring Boot 默认使用 AopAutoConfiguration 进行装配。

可以通过配置项 spring.aop.proxy-target-class=false 来进行修改,设置为 jdk 代理。

使用 context.getBean() 需要添加注解,使 HouseProxy,RealHouseSubject 被 Spring 管理。测试 AOP 代理,需要把这些类交给 AOP 管理(自定义注解或使用 @Aspect)

我们现在从源码中点进去看看代理工厂的代码:

java 复制代码
public class ProxyFactory extends ProxyCreatorSupport {
    //...代码省略
    //获取代理
    public Object getProxy(@Nullable ClassLoader classLoader) {
    //分两步 先createAopProxy,后getProxy
        return createAopProxy().getProxy(classLoader);
    }

    protected final synchronized AopProxy createAopProxy() {
        if (!this.active) {
            activate();
        }
        return getAopProxyFactory().createAopProxy(this);
    }
    //...代码省略
}

createAopProxy 的实现在 DefaultAopProxyFactory 中

java 复制代码
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
    //...代码省略
    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws
            AopConfigException {
        /**
         * 根据proxyTargetClass判断
         * 如果⽬标类是接⼝, 使⽤JDK动态代理
         * 否则使⽤cglib动态代理
         */
        if (!NativeDetector.inNativeImage() &&
                (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. ");
            }
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) ||
                    ClassUtils.isLambdaClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        } else {
            return new JdkDynamicAopProxy(config);
        }
    }
    //...代码省略
}

接下来就是创建代理了

JDK动态代理:

java 复制代码
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
    //...代码省略
    @Override
    public Object getProxy(@Nullable ClassLoader classLoader) {
        if (logger.isTraceEnabled()) {
            logger.trace("Creating JDK dynamic proxy: " +
                    this.advised.getTargetSource());
        }
        return Proxy.newProxyInstance(determineClassLoader(classLoader),
                this.proxiedInterfaces, this);
    }
    //...代码省略
}

CGLIB动态代理:

java 复制代码
class CglibAopProxy implements AopProxy, Serializable {
    //...代码省略
    @Override
    public Object getProxy(@Nullable ClassLoader classLoader) {
    //...代码省略
    // Configure CGLIB Enhancer...
        Enhancer enhancer = createEnhancer();
    // Generate the proxy class and create a proxy instance.
        return createProxyClassAndInstance(enhancer, callbacks);
    }
    //...代码省略
}

五、常见面试题

1、什么是 AOP?

AOP是 面向切面编程,也是一种思想,切面指的是某一类特定问题,所以 AOP 也可以理解为 面向切面编程。

2、Spring AOP的实现方式有哪些?

(1)基于注解(@Aspect 或 自定义注解)

(2)基于 xml

(3)基于代理

3、Spring AOP 的实现原理?

基于动态代理实现的,其中的动态代理有两种形式:(1)JDK (2)CGLIB

4、Spring 使用的是哪种代理方式?

Spring 的 proxyTargetClass 默认为:false,其中:实现了接口,使用 JDK 代理;普通类:使用CGLIB代理。

Spring Boot 从 2.X 之后,proxyTargetClass 默认为:true,默认使用 CGLIB 代理

现在我们测试一下:当这个值为 true 时,则使用的是动态代理

java 复制代码
@SpringBootApplication
public class SpringAopApplication {
    public static void main(String[] args) {
       ApplicationContext context = SpringApplication.run(SpringAopApplication.class, args);
        //代理类
        TestController bean = context.getBean(TestController.class);
        System.out.println(bean);
    }
}

要打断电才能观察到结果,如图:

我们获取Spring管理的对象,打断点,观察对象名称,如图:

可以看到,是 CGLIB代理。

当值设为 false 时;就要看代理对象是不是接口了,是接口用的就是JDK代理,代理对象是类就是CGLIB代理。

因为要代理接口,所以现在重新创建一个接口,内容如下:

java 复制代码
public interface IFace {
    void test();
}

再创建一个成 Controller 类,实现上面这个类

java 复制代码
@RequestMapping("/test2")
@RestController
public class TestController2 implements IFace{
    @MyAspect
    @RequestMapping("t1")
    @Override
    public void test() {
        System.out.println("测试测试");
    }
}

main方法如下:

java 复制代码
@SpringBootApplication
public class SpringAopApplication {
    public static void main(String[] args) {
       ApplicationContext context = SpringApplication.run(SpringAopApplication.class, args);

       //代理类
        TestController bean = context.getBean(TestController.class);
        System.out.println(bean);

        //代理接口
        IFace iFace = (IFace) context.getBean("testController2");
        System.out.println(iFace);
    }
}

现在看看Spring对象,任然要使用断点才能看到,如图:

看看bean对象,如图:

5、JDK 和 CGLIB 的区别?

使用JDK 动态代理只能代理接口。

使用 CGLIB 动态代理 既可以代理接口,也可以代理类。


六、总结

1、AOP 是一种思想,是对某一类事情的集中处理。Spring 框架实现了AOP,称之为 Spring AOP。

2、Spring AOP 场景的实现方式有两种:(1)基于注解@Aspect来实现。(2)基于自定义注解来实现,还有一些更原始的方式,比如基于代理、基于 xml 配置的方式,但目标比较少见。

3、Spring AOP 是基于动态代理实现的,有两种方式:(1)基于 JDK 动态代理实现。(2)基于 CGLIB 动态代理实现。运行时使用哪种方式与项目配置的代理对象有关。

相关推荐
Code成立9 分钟前
《Java核心技术I》Swing的网格包布局
java·开发语言·swing
中草药z14 分钟前
【Spring】深入解析 Spring 原理:Bean 的多方面剖析(源码阅读)
java·数据库·spring boot·spring·bean·源码阅读
信徒_22 分钟前
常用设计模式
java·单例模式·设计模式
神仙别闹28 分钟前
基于C#实现的(WinForm)模拟操作系统文件管理系统
java·git·ffmpeg
小爬虫程序猿28 分钟前
利用Java爬虫速卖通按关键字搜索AliExpress商品
java·开发语言·爬虫
m0_7482567833 分钟前
SpringBoot 依赖之Spring Web
前端·spring boot·spring
组合缺一33 分钟前
Solon v3.0.5 发布!(Spring 可以退休了吗?)
java·后端·spring·solon
程序猿零零漆36 分钟前
SpringCloud 系列教程:微服务的未来(二)Mybatis-Plus的条件构造器、自定义SQL、Service接口基本用法
java·spring cloud·mybatis-plus
猿来入此小猿38 分钟前
基于SpringBoot在线音乐系统平台功能实现十二
java·spring boot·后端·毕业设计·音乐系统·音乐平台·毕业源码
愤怒的代码1 小时前
Spring Boot对访问密钥加解密——HMAC-SHA256
java·spring boot·后端