代理模式及AOP整理

代理模式

这是SpringAOP的底层!

好处:

1.使真实角色的操作更加纯粹,不用去关注一些公共业务

2.公共业务交给代理角色,实现了业务的分工

3.公共业务发生扩展的时候,方便集中管理

缺点:

一个真实角色就会产生一个代理角色;代码量会翻倍,开发效率会变低(解决办法动态代理

代理模式的分类:

1.静态代理

【角色分析】

抽象的角色:一般使用接口或者抽象类来解决

真实角色:被代理的角色

代理角色:代理真实角色,代理后,一般会做一些附属操作

客户:访问代理对象的人!

//接口/租房的接口
public interface Rent {
    public void rent();
}
//真实角色
//房东 
public class Host implements Rent {
    @Override
    public void rent() {
        System.out.println("房东要出租房屋!");
    }
}
//代理角色
//代理角色 要 实现方法
public class Proxy implements Rent{
    private Host host;
    public Proxy() {
    }
    public Proxy(Host host) {
        this.host = host;
    }
    @Override
    public void rent() {
        host.rent();
        seeHouse();
        hetong();
        fare();
    }
    //看房
    public void seeHouse(){
        System.out.println("中介可以带你看房");
    }
    //收中介费
    public void fare(){
        System.out.println("中介要收中介费");
    }
    //签合同
    public void hetong(){
        System.out.println("中介跟你签合同");
    }
}
//客户端访问代理角色
//租客
public class Client {
    public static void main(String[] args) {
        //房东要出租房屋
        Host host = new Host();
        //代理 帮房东出租房屋 ,代理角色一般会有一些附属操作
        Proxy proxy = new Proxy(host);
        //直接面向代理租房
        proxy.rent();
    }
}
//例子二
//业务接口
public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}
//实现类 真实对象
public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("增加了用户");
    }
​
    @Override
    public void delete() {
        System.out.println("删除了用户");
    }
  ...
    //代理对象 增加业务
    public class UserServiceProxy implements UserService{
    UserServiceImpl userService;
   //用set方式实现注入
    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }
    @Override
    public void add() {
      log("add");
        userService.add();
    }
    @Override
    public void delete() {
        log("delete");
        userService.delete();
    }
       //增加日志方法
    public void log(String msg){
        System.out.println("[Debug] 使用了"+msg+"方法");
    }
      ...
        //客户端访问
public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService=new UserServiceImpl();
        UserServiceProxy proxy = new UserServiceProxy();
        //要代理谁?
        proxy.setUserService(userService);
        proxy.add();
    }
}
public class GameProxyPlayer implements IGamePlayer{
    private String name;
    //将代练和游戏玩家 建立关系
    private GamePlayer gamePlayer;

    public GameProxyPlayer(String name) {
        this.name = name;
        this.gamePlayer=new GamePlayer(name);
    }

    @Override
    public void start() {
        System.out.println("拿到"+name+"的用户名密码");
        gamePlayer.start();
    }

    @Override
    public void play() {
        System.out.println("代练击杀了曹贼这个玩家");

    }
}
public class GamePlayer implements IGamePlayer{
    private String name;

    public GamePlayer(String name) {
        this.name = name;
    }

    //登录游戏
    public void start(){
        System.out.println("====登录游戏====");
        System.out.println(name+"开始了游戏");
//        this.name=name;
    }
    //打怪
    public void play(){
        System.out.println(name+"被曹贼玩家击杀了====");
    }
}
public class ProxyTest {
    @Test
    public void test(){
        GamePlayer gamePlayer = new GamePlayer("张三");
        gamePlayer.start();
        gamePlayer.play();
    }
    //让代练来玩游戏 用接口来接收
    @Test
    public void test01(){
        IGamePlayer gamePlayer = new GameProxyPlayer("张三");
        gamePlayer.start();
        gamePlayer.play();
    }
    @Test
    public void proxy(){
        IGamePlayer obj =(IGamePlayer) MyTest.createProxy(new GamePlayer("阿呆"));
        obj.start();
    }
}

2.动态代理(底层都是反射)

动态代理和静态代理角色一致,动态代理的代理类 是动态生成的,不是我们直接写好的!

动态代理分为三大类:

基于接口:【JDK动态代理】动态代理会判断类实现的接口不能超过65535,class文件中两个字节表示实现接口的数量,由于十六进制,最大数量为FF FF 

public class MyTest {
    //公共jdk动态代理对象生成
    public static Object createProxy(Object needProxy){

        ClassLoader classLoader = needProxy.getClass().getClassLoader();
        Class<?>[] interfaces =needProxy.getClass().getInterfaces();
        //传入被代理的对象
        InvocationHandler handler =new MyInvocationHandler(needProxy);

      //这里三个参数 得到一个代理类
        Object o =  Proxy.newProxyInstance(classLoader, interfaces, handler);
        System.out.println(o.getClass());

        return o;
    }

 /*   @Test
    public void test(){
        //创建代理类
        // ClassLoader loader 指定的被代理类的类/接口的类加载器,
        // Class<?>[] interfaces,指定被代理类的接口的类型
        // InvocationHandler h 委托执行的增强处理类,比如日志功能
        ClassLoader classLoader = ICalculator.class.getClassLoader();
        Class<?>[] interfaces =new Class[]{ICalculator.class};
        //用匿名 类实现这个方法
        *//*InvocationHandler handler=new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return null;
            }
        };*//*
        InvocationHandler handler =new MyInvocationHandler(new Calculator());
        //这里要代理计算机这个类 三个参数 得到一个代理类
        ICalculator o = (ICalculator)Proxy.newProxyInstance(classLoader, interfaces, handler);
        System.out.println(o.getClass());
        //实际调用 执行add方法的时候,会执行handler里面委派的invoke方法 如果不写内容,会返回null
        System.out.println(o.add(1,2));
    }*/

}
/**
 * @Author xiehu
 * @Date 2022/9/3 14:17
 * @Version 1.0
 * @Description 实现类 所有被代理类的所有方法 都会经过这个invoke方法 如果返回null输出的就是null,输出的结果就是最终结果
 */
public class MyInvocationHandler implements InvocationHandler {

    Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (args != null && args.length > 0) {
            System.out.println("日志:调用了add方法,执行方法的参数是" + Arrays.asList(args));
        }else {
            System.out.println("日志:没有参数-----");
        }

        //执行被代理的真正的方法
        /* 被代理的对象 Object target
           被代理的方法的参数
         */
        Object res = method.invoke(target, args);

        System.out.println("日志:返回值" + res);
        return res;
    }
}

基于类:【cglib】

java字节码实现:【javassist】

两个类

Proxy :代理 提供了创建动态代理类和实例的静态方法

InvocationHandler(反射包下得接口) : 调用处理程序并返回结果

//租房的接口
public interface Rent {
    public void rent();
}
//房东 真实角色
public class Host implements Rent {
    @Override
    public void rent() {
        System.out.println("房东要出租房屋!");
    }
}
​
//我们用这个类自动生成代理类 InvocationHandler 调用处理程序并返回结果
public class ProxyInvocationHandler implements InvocationHandler {
    //被代理的接口
    private Rent rent;
​
    public void setRent(Rent rent) {
        this.rent = rent;
    }
    //生成得到 代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                rent.getClass().getInterfaces(),  this);
    }
    //处理代理实例,并返回结果 调用代理类的一些执行方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       //执行接口上的方法
        //动态代理的本质,就是使用反射机制实现
        seeHouse();
        Object result = method.invoke(rent, args);
        fare();
        return result;
    }
    public void seeHouse(){
        System.out.println("中介带看房");
    }
    public void fare(){
        System.out.println("收中介费");
    }
}
​
public class Client {
    public static void main(String[] args) {
        //真实角色
        Host host=new Host();
        //代理角色 暂时没有
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //通过调用程序处理角色 来处理我们要调用的接口对象!
        pih.setRent(host);
        //这里的proxy就是动态生成的,我们并没有写
        Rent proxy = (Rent) pih.getProxy();
        proxy.rent();
​
    }
}

抽取共性:

//我们用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
    //被代理的接口
    private Object target;
​
    public void setTarget(Object target) {
        this.target = target;
    }
    //生成得到 代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),  this);
    }
    //处理代理实例,并返回结果,调用代理类的一些执行方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       //执行接口上的方法
        //动态代理的本质,就是使用反射机制实现
        log(method.getName());
        Object result = method.invoke(target, args);
        return result;
    }
​
    public void log(String msg){
        System.out.println("执行了"+msg+"方法");
    }
}
//调用
public class Client {
    public static void main(String[] args) {
        //真实角色
        UserServiceImpl userService=new UserServiceImpl();
        //代理角色 不存在
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //设置要代理的对象
        pih.setTarget(userService);
        //动态生成代理类
        UserService proxy =(UserService) pih.getProxy();
​
        proxy.add();
​
    }
}

动态代理的好处:

1.使真实角色的操作更加纯粹,不用去关注一些公共业务

2.公共业务交给代理角色,实现了业务的分工

3.公共业务发生扩展的时候,方便集中管理

4.一个动态代理类代理的是一个接口,一般对应一类业务

5.一个动态代理类可以代理多个类,只要是实现了同一个接口即可!


AOP

OOP:Object Oriented Programming

AOP:Aspect Oriented Programming

        基于OOP基础,OOP面向的主要对象是类,AOP面向的主要对象是切面,在处理日志、安全管理、事务管理等方面具有非常重要的作用。AOP是Spring中重要的核心点,虽然IOC容器没有依赖AOP,但是AOP提供了非常强大的功能,用来堆IOC做补充,通俗点说的话就是在程序运行期间。在不修改原有代码的情况下 增强跟主要业务没有关系的公共功能代码到之前写好的方法中的指定位置,这种编程方式叫做AOP

        AOP会判断这个类是否有接口,如果有实现接口,就用jdk动态代理,否则有cglib动态代理,并且创建完代理类会将此代理类放入ioc容器,AOP更灵活,可以动态的去选择动态代理的方式,可以跟ioc配合集成。

spring实现AOP 需要加织入的依赖
<!--  1.加入依赖-->
    <dependencies>
<!-- spring需要这个依赖 ioc依赖 spring-context-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.22.RELEASE</version>
        </dependency>
<!-- aop 的依赖两个 aspectjweaver  spring aspects-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.22.RELEASE</version>
        </dependency>

    </dependencies>
 <!--外部框架 AOP-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>
        </dependency>
        <!--spring 跟aspect 配合的依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.22</version>
        </dependency>

如果用spring  还需要在xml中开启aop

<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">


    <!--扫描包:扫描类中所有注解,不扫描注解不是生效-->
    <context:component-scan base-package="cn.tulingxueyuan" >
    </context:component-scan>

    <!--因为我们使用的是注解方式的AOP,所以要开启注解AOP功能-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

方式一:使用Spring的API接口实现AOP 原生api接口

//业务接口
public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void select();
}
//实现类
public class UserServiceImpl implements UserService{
    @Override
    public void add() { System.out.println("增加一个用户"); }
    @Override
    public void delete() { System.out.println("删除一个用户"); }
    @Override
    public void update() { System.out.println("更新一个用户"); }
    @Override
    public void select() { System.out.println("查询一个用户"); }
}
//需要增加的内容
//直接使用Spring的API
public class Log implements MethodBeforeAdvice {
    /* method: 要执行的目标对象的方法
    * args : 参数
    * target:目标对象*/
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
    }
}
​
public class AfterLog implements AfterReturningAdvice {
    //returnValue 返回值
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了"+method.getName()+"方法,返回结果为"+returnValue);
    }
}
​

用最原始方法配置xml

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
    <bean id="userService" class="com.xie.service.UserServiceImpl"/>
    <bean id="log" class="com.xie.log.Log"/>
    <bean id="afterlog" class="com.xie.log.AfterLog"/>
<!--方式一:使用原生Spring api接口-->
<!--配置aop 导入aop的约束-->
<aop:config>
<!--切入点 我们在哪执行  expression表达式 execution(要执行的位置! * * * * *)-->
    <aop:pointcut id="pointcut" expression="execution(* com.xie.service.UserServiceImpl.*(..))"/>
<!--执行环绕增强-->
    <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
    <aop:advisor advice-ref="afterlog" pointcut-ref="pointcut"/>
</aop:config>
</beans>

测试类:

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //动态代理代理的是接口 所以这里不能用实现类!
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

方式二:自定义类来实现AOP 主要是切面定义

<!--方式二:-->
    <bean id="diy" class="com.xie.diy.DiyPointCut"/>
    <aop:config>
        <!--自定义 切面 ref要引用的类-->
        <aop:aspect ref="diy">
            <!--切入点-->
            <aop:pointcut id="point" expression="execution(* com.xie.service.*.*(..))"/>
            <!--通知-->
            <aop:before method="before" pointcut-ref="point"/>
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>
package com.xie.diy;
​
public class DiyPointCut {
    public void before(){
        System.out.println("方法执行前");
    }
    public void after(){
        System.out.println("方法执行后");
    }
}

方式三:使用注解实现!

<!-- 方式三-->
    <bean id="annotationPointCut" class="com.xie.diy.AnnotaionPointCut"/>
<!--开启注解支持! JDK(默认) cglib (如果是true就会用cglib) -->
    <aop:aspectj-autoproxy proxy-target-class="false"/>
//方式三:使用注解方式实现AOP
//注解标注此类为一个切面
@Aspect
public class AnnotaionPointCut {
    //切入点 内容 找到这个类的所有方法,所有参数
    @Before("execution(* com.xie.service.UserServiceImpl.* (..))")
    public void before(){
        System.out.println("方法执行前~");
    }
​
    @After("execution(* com.xie.service.UserServiceImpl.* (..))")
    public void after(){
        System.out.println("方法执行后~~");
    }
    //在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
    @Around("execution(* com.xie.service.UserServiceImpl.* (..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前~");
        //获得签名
        Signature signature = jp.getSignature();
        System.out.println("signature:"+signature);
        //执行方法
        Object proceed = jp.proceed();
        System.out.println("环绕后~");
    }
}
​

切点表达式:

Spring AOP支持使用以下AspectJ切点标识符(PCD),用于切点表达式:
        execution: 用于匹配方法执行连接点。 这是使用Spring AOP时使用的主要切点标识符。 可以匹配到方法级别 ,细粒度
        within: 只能匹配类这级,只能指定类, 类下面的某个具体的方法无法指定, 粗粒度
        this: 匹配实现了某个接口: this(com.xyz.service.AccountService)
        target: 限制匹配到连接点(使用Spring AOP时方法的执行),其中目标对象(正在代理的应用程序对象)是给定类型的实 例。
        args: 限制与连接点的匹配(使用Spring AOP时方法的执行),其中变量是给定类型的实例。 AOP) where the arguments are instances of the given types.
        @target: 限制与连接点的匹配(使用Spring AOP时方法的执行),其中执行对象的类具有给定类型的注解。
        @args: 限制匹配连接点(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注解。
        @within: 限制与具有给定注解的类型中的连接点匹配(使用Spring AOP时在具有给定注解的类型中声明的方法的执行)。
        @annotation:限制匹配连接点(在Spring AOP中执行的方法具有给定的注解)。

访问修饰符:可不写,可以匹配任何一个访问修饰符

返回值:如果是jdk自带的类型可以不用写完整限定名(Integer...),如果是自定义类型需要写完整限定名,如果被切入的方法返回值不一样,可以用*代表所有的返回值都能匹配

包名: com.* == com.任意名字 ;但是只能匹配一级 不能匹配到下一级的子包,如果com..*则所有的都匹配到

类名:可以写* ,代表任何名字的类名。也可以模糊匹配 *ServiceImpl==> UserServiceImpl ==>RoleServiceImpl

方法名:可以写*,代表任何方法,也可模糊匹配 *ServiceImpl==> UserServiceImpl ==>RoleServiceImpl

参数:如果是jdk自带的类型可以不用写完整限定名(Integer...),如果是自定义类型需要写完整限定名。如果需要匹配任意参数,可以写: . .

 

合并切点表达式 

 &&  、| | 、 !

@Aspect
@Component
public class LogAspect {

    //前置通知  切点表达式
//    @Before("execution(* com.xie.service.impl.*.*(Integer))") 切入到参数为Integer类型的方法
//    @Before("execution(public * com.xie.service.impl.*.*(..))")
    @Before("execution(public * com.xie.service.impl.*.*(..)) && @annotation(jdk.nashorn.internal.runtime.logging.Logger)")
    public void before(){
        System.out.println("前置通知");
    }
}

切入的时候获取到切点的方法名,参数等信息

  @Before("execution(public * com.xie.service.impl.*.*(..)) || @annotation(jdk.nashorn.internal.runtime.logging.Logger)")
    public void before(JoinPoint joinPoint) {
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        //所有的参数
        Object[] args = joinPoint.getArgs();
        System.out.println(methodName+"[方法运行],参数是:"+ Arrays.asList(args));
        System.out.println("前置通知");
    }

    //后置通知 最终通知
    @After("execution(* com.xie.service.impl.*.*(..))")
    public void after(JoinPoint joinPoint) {
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        //所有的参数
        Object[] args = joinPoint.getArgs();
        System.out.println(methodName+"[方法运行],参数是:"+ Arrays.asList(args));
        System.out.println("后置通知====");
    }

获取切入方法的 返回值信息

//后置返回通知 返回值不确定
    @AfterReturning(value="execution(* com.xie.service.impl.*.*(..))",
                    returning ="returnValue")
    public void afterReturning(Object returnValue) {
        System.out.println("后置返回通知====返回值为:"+returnValue);
    }

 获取切入点方法执行时候产生的异常

 //后置异常通知
    @AfterThrowing(value="execution(* com.xie.service.impl.*.*(..))",
                    throwing = "ex")
    public void afterThrowing(Exception ex) {
        System.out.println("后置异常通知====,异常为:"+ex);
    }

打印出异常栈信息

表达式的抽取   声明了一个切点,其他通知对其进行引用,重用性更强

//表达式的抽取 这里声明了一个切点,其他地方对此切点进行引用
    @Pointcut("execution(public * com.xie.service.impl.*.*(..))")
    public void pointcut(){

    }
//最终通知
    @After("pointcut()")
    public void after(JoinPoint joinPoint) {
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        //所有的参数
        Object[] args = joinPoint.getArgs();
        System.out.println(methodName+"[方法运行],参数是:"+ Arrays.asList(args));
        System.out.println("最终通知====");
    }

可获取切入方法 指定的注解的name属性信息

  //前置通知  切点表达式
//    @Before("execution(* com.xie.service.impl.*.*(Integer))") 切入到参数为Integer类型的方法
//    @Before("execution(public * com.xie.service.impl.*.*(..))")
    @Before("pointcut() && @annotation(logger)")
    //匹配方法上 标记了 jdk.nashorn.internal.runtime.logging.Logger 这个logger的注解 只需要将注解对应参数上面的名字,会自动根据参数类型去对应 
    public void before(JoinPoint joinPoint, Logger logger) {
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        //所有的参数
        Object[] args = joinPoint.getArgs();
        System.out.println("前置通知"+methodName+"[方法运行],参数是:"+ Arrays.asList(args)+"注解的值是:"+logger.name());

    }
@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    IUserDao userDao;


    @Logger(name="用户的查询方法!")
    public User select(Integer id) {
        System.out.println("查询User");
        return userDao.select(id);
    }

 环绕通知的使用

        

 //环绕通知
    @Around("pointcut()")
    public void around(ProceedingJoinPoint joinPoint) {
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        //所有的参数
        Object[] args = joinPoint.getArgs();
        //返回值
        Object returnValue="";
        try {
            System.out.println("环绕:前置通知->执行方法为====" + methodName + "参数为====" + Arrays.asList(args));
            returnValue=joinPoint.proceed();
            System.out.println("环绕:后置返回通知->执行方法为====" + methodName + "参数为====" + Arrays.asList(args));
        } catch (Throwable throwable) {
            System.out.println("环绕:后置异常通知->异常为====" + throwable);
            throwable.printStackTrace();
        }finally{
            System.out.println("环绕:最终通知->返回值为===="+returnValue);
        }
        System.out.println("环绕通知==结束==");


    }

 

  

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Laughing_Xie

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值