Spring-AOP(面向切面)

Spring-AOP(面向切面)

场景模拟(计算器)

功能接口

java 复制代码
public interface Calculator {
    int add(int i, int j);
    int minus(int i, int j);
    int multiply(int i, int j);
    int div(int i, int j);
}

实现类

java 复制代码
public class CalculateLogImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        System.out.println("[日志]add方法开始,参数是"+ i+" " + j);
        int result = i + j;
        System.out.println("方法内部:resultAdd = " + result);
        System.out.println("[日志]add方法结束,结果是:" + result);
        return result;
    }

    @Override
    public int minus(int i, int j) {
        System.out.println("[日志]minus 方法开始,参数是"+ i+" " + j);
        int result = i - j;
        System.out.println("方法内部:resultMinus = " + result);
        System.out.println("[日志]minus 方法结束,结果是:" + result);
        return result;

    }

    @Override
    public int multiply(int i, int j) {
        System.out.println("[日志]multiply 方法开始,参数是"+ i+" " + j);
        int result = i * j;
        System.out.println("方法内部:resultMultiply = " + result);
        System.out.println("[日志]multiply 方法结束,结果是:" + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        System.out.println("[日志]div 方法开始,参数是"+ i+" " + j);
        int result = i / j;
        System.out.println("方法内部:resultDiv = " + result);
        System.out.println("[日志]div 方法结束,结果是:" + result);
        return result;
    }
}

在含有日志输出的实现类中可以了解到:与核心业务功能没有关系的日志输出加杂在模块中,对核心业务功能有干扰。

思路:解耦将附加功能从业务功能模块中抽取出来

代理模式

概念

二十三种设计模式中的一种,属于结构型模式,它的作用就是通过提供一个代理类,让我们在调用目标方法时,不再直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来(解耦)。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中起来便于管理。

静态代理

目标对象

接口
java 复制代码
public interface Calculator {
    int add(int i, int j);
    int minus(int i, int j);
    int multiply(int i, int j);
    int div(int i, int j);
}
实现类
java 复制代码
public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部:resultAdd = " + result);
        return result;
    }

    @Override
    public int minus(int i, int j) {
        int result = i - j;
        System.out.println("方法内部:resultMinus = " + result);
        return result;

    }

    @Override
    public int multiply(int i, int j) {
        int result = i * j;
        System.out.println("方法内部:resultMultiply = " + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部:resultDiv = " + result);
        return result;
    }
}

代理类

java 复制代码
public class CalculatorStaticProxy implements Calculator {
    //被代理的目标对象传递过来
    private Calculator calculator;

    public CalculatorStaticProxy(Calculator calculator) {
        this.calculator = calculator;
    }

    @Override
    public int add(int i, int j) {
        System.out.println("[日志]add方法开始,参数是"+ i+" " + j);
        //调用目标对象的方法实现核心业务
        int result = calculator.add(i, j);
        System.out.println("[日志]add方法结束,结果是:" + result);
        return result;
    }

    @Override
    public int minus(int i, int j) {
        System.out.println("[日志]minus 方法开始,参数是"+ i+" " + j);
        int result = calculator.minus(i, j);
        System.out.println("[日志]minus 方法结束,结果是:" + result);
        return result;
    }

    @Override
    public int multiply(int i, int j) {
        System.out.println("[日志]multiply 方法开始,参数是"+ i+" " + j);
        int result = calculator.multiply(i,j);
        System.out.println("[日志]multiply 方法结束,结果是:" + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        System.out.println("[日志]div 方法开始,参数是"+ i+" " + j);
        int result = calculator.div(i, j);
        System.out.println("[日志]div 方法结束,结果是:" + result);
        return result;
    }
}

测试

java 复制代码
@Test
public void testStaticProxy(){
    Calculator calculator = new CalculatorStaticProxy(new CalculatorImpl());
    calculator.add(10, 5);
    System.out.println("-------------------");
    calculator.minus(10, 5);
    System.out.println("-------------------");
    calculator.multiply(10, 5);
    System.out.println("-------------------");
    calculator.div(10, 5);
}

/*
*   [日志]add方法开始,参数是10 5
    方法内部:resultAdd = 15
    [日志]add方法结束,结果是:15
    -------------------
    [日志]minus 方法开始,参数是10 5
    方法内部:resultMinus = 5
    [日志]minus 方法结束,结果是:5
    -------------------
    [日志]multiply 方法开始,参数是10 5
    方法内部:resultMultiply = 50
    [日志]multiply 方法结束,结果是:50
    -------------------
    [日志]div 方法开始,参数是10 5
    方法内部:resultDiv = 2
    [日志]div 方法结束,结果是:2
* */

静态代理类实现了解耦,但是由于代理类的代码都是写死的,就不具备复用的功能,在其他类需要相同功能的类需要进行代理时,也需要重新写代理类,增加了代码冗余,解决这一问题需要使用到动态代理方法。

动态代理

Proxy工具类

java 复制代码
public class ProxyUtil {
    //目标对象
    private Object target;

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

    //反回代理对象
    public Object getProxy(){
        //使用Proxy中的newProxyInstance()
        /**
         * Proxy.newProxyInstance()
         * ClassLoader:加载动态生成代理类的类加载器
         * Class<?>[] interfaces:目标对象实现的所有接口的class类型数组
         * InvocationHandler:设置代理对象实现目标对象方法的过程
         */
        //ClassLoader:加载动态生成代理类的类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();
        //Class<?>[] interfaces:目标对象实现的所有接口的class类型数组
        Class<?>[] interfaces = target.getClass().getInterfaces();
        //InvocationHandler:设置代理对象实现目标对象方法的过程
        InvocationHandler invocationHandler = new InvocationHandler(){
            /**
             *
             * @param proxy :代理对象
             * @param method :需要重写目标对象的方法
             * @param args:method方法中的参数
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy,
                                 Method method,
                                 Object[] args) throws Throwable {
                System.out.println("[动态代理][日志]" + method.getName() + ", 参数:" + Arrays.toString(args));
                //调用目标的方法
                Object result = method.invoke(target, args);
                System.out.println("[动态代理][日志]" + method.getName() + ", 结果:" + result);
                return result;
            }
        };
        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }
}

测试类

java 复制代码
@Test
public void testProxy(){
    //动态创建代理对象
    ProxyUtil proxyUtil = new ProxyUtil(new CalculatorImpl());
    Calculator proxy = (Calculator) proxyUtil.getProxy();
    proxy.add(1, 2);
    System.out.println("---------");
    proxy.minus(1, 2);
    System.out.println("---------");
    proxy.multiply(1, 2);
    System.out.println("---------");
    proxy.div(1, 2);
}



/*
*   [动态代理][日志]add, 参数:[1, 2]
    方法内部:resultAdd = 3
    [动态代理][日志]add, 结果:3
    ---------
    [动态代理][日志]minus, 参数:[1, 2]
    方法内部:resultMinus = -1
    [动态代理][日志]minus, 结果:-1
    ---------
    [动态代理][日志]multiply, 参数:[1, 2]
    方法内部:resultMultiply = 2
    [动态代理][日志]multiply, 结果:2
    ---------
    [动态代理][日志]div, 参数:[1, 2]
    方法内部:resultDiv = 0
    [动态代理][日志]div, 结果:0
* */

AOP概念及相关术语

简介

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译的方式和运行期动态代理方式实现,在不修改源码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,提高开发效率。

相关术语

横切关注点

分散在各个模块中解决同一问题,如用户验证、日志管理、事务处理、事务缓存都属于横切关注点

从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个方面的增强。这个概念不是语法层面的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。

通知(增强)

增强,就是想要增强的功能,比如:安全、事务、日志等。每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。

前置通知:在被代理的目标方法前执行
返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
异常通知:在被代理的目标方法异常结束后执行(死于非命)
后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置。

切面

封装通知方法的类。

目标

被代理的目标对象。

代理

向目标对象应用通知之后创建的代理对象

连接点

这也是一个逻辑概念,不是语法定义的。把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。也就是spring允许使用通知的地方。

切入点

定位连接点的方式,每个类的方法中都包含多个连接点,如果把连接点看作数据库中的记录,那么切入点就是查询记录的SQL语句。Spring 的AOP技术可以通过切入点定位到特定的连接点,即可以使用增强方法的位置。

切入点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。

作用

简化代码:把方法中固定位置的重复代码抽取出来,让抽取的方法更专注于自己的核心功能,提高内聚性。
代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了


基于注解的AOP

AspectJ:是AOP思想的一种实现。本质上是静态代理,将代理逻辑"植入"被代理的目标,编译得到的字节码文件,所以最终效果是动态的。weaver就是植入器。Spring只是借用了AspectJ中的注解。

动态代理分类

JDK动态代理:有接口,生成接口实现类代理对象,代理对象和目标对象都实现同样的接口。
cglib动态代理:没有接口,继承目标类,生成子类代理对象

准备工作

引入相关的依赖
xml 复制代码
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.9</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>6.0.9</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>6.0.9</version>
</dependency>
创建目标资源

接口

java 复制代码
public interface Calculator {
    int add(int i, int j);
    int minus(int i, int j);
    int multiply(int i, int j);
    int div(int i, int j);
}

实现类

java 复制代码
public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部:resultAdd = " + result);
        return result;
    }

    @Override
    public int minus(int i, int j) {
        int result = i - j;
        System.out.println("方法内部:resultMinus = " + result);
        return result;

    }

    @Override
    public int multiply(int i, int j) {
        int result = i * j;
        System.out.println("方法内部:resultMultiply = " + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部:resultDiv = " + result);
        return result;
    }
}
配置spring配置文件
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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--开启组件扫描-->
    <context:component-scan base-package="com.louis.annotation_aop"></context:component-scan>
<!--开启aspectJ自动代理,为目标对象生成代理-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
创建切面类
切入点表达式

语法细节

用*号代替"权限修饰符"和"返回值部分"表示"权限修饰符"和"返回值"不限

包名的部分

①一个"*"号只能代表包的层次结构中的一层,表示这一层是任意的。如:*.com匹配hello.com但不匹配hello.louis.com

②使用"*..."表示任意包名、包的层次深度任意

类名的部分

①类名整体使用*代替,表示类名任意

②可以使用*代替类名的一部分。如:*xxx表示匹配所有名称以xxx结尾的类或接口

方法名的部分

①可以使用*代替,表示方法名任意

②可以使用*代替方法名的一部分。如:*xxx表示匹配所有名称以xxx结尾的方法

方法参数列表部分

①可以使用(...)表示任意参数列表

②可以使用(int,...)表示参数列表以一个int类型的参数开头

③基本数据类型和对应的包装类型是不一样的(切入点中使用int和实际方法中使用Integer是不匹配的)

方法返回值部分

如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符。如:excution(public int...Service.(...,int))

切面类(通知类型)
java 复制代码
@Aspect
@Component //表示在spring的ioc容器中进行管理
public class LogAspect {
    //设置切入点和通知类型

    //通知类型:
    // 前置:@Before(value="通过切入点表达式配置切入点")
    //切入点表达式:execution(访问修饰符 增强方法返回类型 方法所在类的全路径.方法名(参数列表))
//    @Before("execution(* com.louis.annotation_aop.impl.LogAspect.*(..))")
    @Before("execution(public int com.louis.annotation_aop.impl.CalculatorImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("前置通知,增强的方法名称" + name + " , 参数" + Arrays.toString(args));
    }
    // 返回:@AfterReturning,返回通知与后置通知有很大的区别,后置通知在返回通知之后执行,返回通知能够得到目标方法的返回值,使用属性returning
    //它的参数可以随便命名,但是切面方法的参数必须和上面的一致
    @AfterReturning(value = "execution(* com.louis.annotation_aop.impl.CalculatorImpl.*(..))", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println("返回通知, 增强方法名称" + name + " , 返回结果" + result);
    }
    // 异常:@AfterThrowing:目标方法出现异常,这个通知会执行,并且能够获得目标方法的异常信息
    @AfterThrowing(value = "execution(* com.louis.annotation_aop.impl.CalculatorImpl.*(..))", throwing = "Exception")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable Exception){
        String name = joinPoint.getSignature().getName();
        System.out.println("异常通知, 增强方法名称" + name + " , 返回结果" + Exception);
    }

    // 后置:@After()
    @After("execution(public int com.louis.annotation_aop.impl.CalculatorImpl.*(..))")
    public void afterMethod(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println("后置通知, 增强方法名称" + name);
    }
    // 环绕:@Around()
    @Around("execution(public int com.louis.annotation_aop.impl.CalculatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        String argsString = Arrays.toString(args);
        Object result = null;
        try {
            System.out.println("环绕通知, 目标方法之前执行");
            //调用目标方法
             result = joinPoint.proceed();

            System.out.println("环绕通知, 目标方法返回值之后执行");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知, 目标方法出现异常执行");
        } finally {
            System.out.println("环绕通知, 目标方法执行完毕");
        }
        return result;
    }
}
测试
java 复制代码
@Test
public void testAOPAdd(){
    ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
    Calculator calculator = context.getBean(Calculator.class);
    calculator.add(1,0);
}

/*
环绕通知, 目标方法之前执行
前置通知,增强的方法名称add , 参数[1, 0]
方法内部:resultAdd = 1
返回通知, 增强方法名称add , 返回结果1
后置通知, 增强方法名称add
环绕通知, 目标方法返回值之后执行
环绕通知, 目标方法执行完毕
* */

@Test
public void testDiv(){
    ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
    Calculator calculator = context.getBean(Calculator.class);
    calculator.div(1,0);
}

/*
环绕通知, 目标方法之前执行
前置通知,增强的方法名称div , 参数[1, 0]
异常通知, 增强方法名称div , 返回结果java.lang.ArithmeticException: / by zero
后置通知, 增强方法名称div
环绕通知, 目标方法出现异常执行
环绕通知, 目标方法执行完毕
* */

基于注解的AOP

重用切入点和切面优先级

重用切入点
java 复制代码
//重用切入点表达式,定义一个方法,之后在需要切入点的时候直接调用这个方法
/*
* 在同一个类下,可以直接使用:@Before("pointcut()")
* 在不同的类中,需要加入类的路径:  @Before("com.louis.annotation_aop.impl.LogAspect.pointcut()")
* */
@Pointcut(value = "execution(public int com.louis.annotation_aop.impl.CalculatorImpl.*(..))")
public void pointcut(){

}
切面优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序

优先级高的切面在外面,优先级低的切面在里面。

使用@Order注解可以控制切面的优先级

@Order(较小的数):优先级高

@Order(较大的数):优先级低

切面类

java 复制代码
@Component //表示在spring的ioc容器中进行管理
public class LogAspect {
    //前置通知
    public void beforeMethod(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("前置通知,增强的方法名称" + name + " , 参数" + Arrays.toString(args));
    }
    // 返回通知
    public void afterReturningMethod(JoinPoint joinPoint, Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println("返回通知, 增强方法名称" + name + " , 返回结果" + result);
    }
    // 异常通知
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable Exception){
        String name = joinPoint.getSignature().getName();
        System.out.println("异常通知, 增强方法名称" + name + " , 返回结果" + Exception);
    }

    // 后置通知
    public void afterMethod(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println("后置通知, 增强方法名称" + name);
    }
    // 环绕通知
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        String argsString = Arrays.toString(args);
        Object result = null;
        try {
            System.out.println("环绕通知, 目标方法之前执行");
            //调用目标方法
             result = joinPoint.proceed();

            System.out.println("环绕通知, 目标方法返回值之后执行");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知, 目标方法出现异常执行");
        } finally {
            System.out.println("环绕通知, 目标方法执行完毕");
        }
        return result;
    }


    //重用切入点表达式,定义一个方法,之后在需要切入点的时候直接调用这个方法
    /*
    * 在同一个类下,可以直接使用:@Before("pointcut()")
    * 在不同的类中,需要加入类的路径:  @Before("com.louis.annotation_aop.impl.LogAspect.pointcut()")
    * */
    @Pointcut(value = "execution(public int com.louis.xml_aop.impl.CalculatorImpl.*(..))")
    public void pointcut(){

    }
}

配置文件beanaop.xml

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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--开启组件扫描-->
    <context:component-scan base-package="com.louis.xml_aop"></context:component-scan>
    <!--配置aop五种通知类型-->
    <aop:config>
        <!--配置切面类-->
        <aop:aspect ref="logAspect">
            <!--配置切入点-->
            <aop:pointcut id="pointcut" expression="execution(* com.louis.xml_aop.impl.CalculatorImpl.*(..))"/>
            <!--配置五种通知类型-->
            <!--前置通知-->
            <aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
            <!--后置通知-->
            <aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
            <!--返回通知-->
            <aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointcut"></aop:after-returning>
            <!--异常通知-->
            <aop:after-throwing method="afterThrowingMethod" throwing="Exception" pointcut-ref="pointcut"></aop:after-throwing>
            <!--环绕通知-->
            <aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>

测试

java 复制代码
@Test
public void testAOPXMLAdd(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beanaop.xml");
    Calculator calculator = context.getBean(Calculator.class);
    calculator.add(1,0);
}
/*
前置通知,增强的方法名称add , 参数[1, 0]
环绕通知, 目标方法之前执行
方法内部:resultAdd = 1
环绕通知, 目标方法返回值之后执行
环绕通知, 目标方法执行完毕
返回通知, 增强方法名称add , 返回结果1
后置通知, 增强方法名称add
* */
相关推荐
CodeAmaz几秒前
Spring编程式事务详解
java·数据库·spring
没有bug.的程序员2 分钟前
微服务基础设施清单:必须、应该、可以、无需的四级分类指南
java·jvm·微服务·云原生·容器·架构
武子康5 分钟前
Java-204 RabbitMQ Connection/Channel 工作流程:AMQP 发布消费、抓包帧结构与常见坑
java·分布式·消息队列·rabbitmq·ruby·java-activemq
郑州光合科技余经理6 分钟前
海外国际版同城服务系统开发:PHP技术栈
java·大数据·开发语言·前端·人工智能·架构·php
appearappear17 分钟前
Mac 上重新安装了Cursor 2.2.30,重新配置 springboot 过程记录
java·spring boot·后端
CryptoRzz25 分钟前
日本股票 API 对接实战指南(实时行情与 IPO 专题)
java·开发语言·python·区块链·maven
程序员水自流27 分钟前
MySQL数据库自带系统数据库功能介绍
java·数据库·mysql·oracle
谷哥的小弟32 分钟前
Spring Framework源码解析——RequestContext
java·后端·spring·框架·源码
天远Date Lab38 分钟前
Java微服务实战:聚合型“全能小微企业报告”接口的调用与数据清洗
java·大数据·python·微服务
lizz3142 分钟前
C++操作符重载深度解析
java·c++·算法