三(二)、AOP配置
一、AOP的配置(注解)
步骤一、导入jar包:
处理那5个jar包之外,还需要导入:
- aopalliance
- aspectjweaver
- spring-aop
- spring-aspects
步骤二、在配置文件中加入aop、context的命名空间
步骤三分为基于注解方式配置AOP和xml方式配置aop;
基于注解方式(本篇)
①在配置文件中加入如下配置;
- 1 <!-- 使AspjectJ注释起作用,自动匹配的类生成代理对象 -->
- 2 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
②把横切关注点的代码都加入到切面的类中,
③切面首先是一个IOC中的bean,即加@Conponent注释
④切面需要加入@Aspect注释
⑤在类中声明各种通知:
- @Before:前置通知,在方法执行前执行;
- @After:后置通知,在方法执行后执行
@AfterRunning:返回通知,在方法返回结果后执行
@Afterthrowing:异常通知之后
@Around:环绕通知
二、AOP常用通知:
- @Before:前置通知,在方法执行前执行;
- @Before("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.*(int, int) )") // 作用于接口中的所有方法
- public void beforeMethod(JoinPoint joinPoint) {
- String method = joinPoint.getSignature().getName();// 方法的签名
- List<Object> args = Arrays.asList(joinPoint.getArgs());// 方法的参数
- System.out.println("the method " + method + " begins with" + args);
- }
- @After:后置通知,在方法执行后执行,在后置通知中,不能访问目标方法执行的结果,如果有异常也执行,通知在异常之前;
- 1 @After("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.*(int, int) )")
- 2 public void afterMethod(JoinPoint joinPoint) {
- 3 String method = joinPoint.getSignature().getName();
- 4 List<Object> args = Arrays.asList(joinPoint.getArgs());
- 5 System.out.println("the method " + method + " is end to " + args);
- 6 }
@AfterRunning:返回通知,在方法返回结果后执行,可以访问到返回值;
- 1 @AfterReturning(value = "execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.add(int, int) )", returning = "result")
- 2 public void afterReturn(JoinPoint joinPoint, Object result) {
- 3 String method = joinPoint.getSignature().getName();
- 4 System.out.println("the method " + method + " is end with " + result);
- 5 }
@Afterthrowing:异常通知之后,可以访问到异常,并且可以指定异常类型,只有符合该异常类型时才被执行
- 1 @AfterThrowing(value = "execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.add(int, int) )", throwing = "ex")
- 2 public void afterThrowing(JoinPoint joinPoint, Object ex) {
- 3 String method = joinPoint.getSignature().getName();
- 4 System.out.println("the method " + method + " occured exception: " + ex);
- 5 }
@Around:环绕通知;
环绕通知类似动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行日志方法 且必须有返回值,返回值是目标方法的返回值
- 1 @Around("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.add(int, int) )")
- 2 public Object aroundMethod(ProceedingJoinPoint point) {
- 3 Object result = null;
- 4 String method = point.getSignature().getName();
- 5 // 执行目标方法
- 6 try {
- 7 // 前置通知
- 8 System.out.println("the method " + method + " is begin with " + Arrays.asList(point.getArgs()));
- 9 result = point.proceed();
- 10 // 返回通知
- 11 System.out.println("the method " + method + " is end to " + result);
- 12 } catch (Throwable e) {
- 13 // TODO Auto-generated catch block
- 14 System.out.println("the method " + method + " occured exception: " + e);
- 15 throw new RuntimeException(e);
- 16 }
- 17 System.out.println("the method " + method + " ends");
- 18
- 19 return 100;
- 20 }
三、切点表达式
表达式
- 1 execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
这里问号表示当前项可以有也可以没有,其中各项的语义如下:
- modifiers-pattern:方法的可见性,如public,protected;
- ret-type-pattern:方法的返回值类型,如int,void等;
- declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
- name-pattern:方法名类型,如buisinessService();
- param-pattern:方法的参数类型,如java.lang.String;
- throws-pattern:方法抛出的异常类型,如java.lang.Exception;
举例说明:
- // @Before("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.add(int, int) )")// 作用于接口中的add方法
- // @Before("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.*(int, int) )") // 作用于接口中的所有方法 (所有方法是指public int类型的)
- // @Before("execution(* ixiuming.spring.aop.impl.ArithmeticCaculator.*(int, int) )") // 第一个* 表示:任意修饰符和任意返回值,第二个*代码任意参数为(int,int)方法
- // @Before("execution( public * ixiuming.spring.aop.impl.ArithmeticCaculator.*(..) )") // 第二个*代表任意方法;..代表任意个数的参数,即所有公有方法
- // @Before("execution( public double ixiuming.spring.aop.impl.ArithmeticCaculator.*(double.) )") // 返回所有double的第一个参数为double的public的方法
@Pointcut
使用@Pointcut 来声明切入点表达式,后面的其他通知直接使用方法名来引用当前的切入点表达式;如下代码,前置通知使用了方法名为declareJoinPointExpress来引用切点表达式;
这样做的好处是,可以统一管理切点表达式;
- 1 @Pointcut("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.*(..))")
- 2 public void declareJoinPointExpress() {
- 3
- 4 }
- 5
- 6 //声明该方法是一个前置通知:在目标方法之前执行
- 7 @Before("declareJoinPointExpress()")
- 8 public void beforeMethod(JoinPoint joinPoint) {
- 9 String method = joinPoint.getSignature().getName();
- 10 List<Object> args = Arrays.asList(joinPoint.getArgs());
- 11 System.out.println("the method " + method + " begins with" + args);
- 12 }
四、实例说明AOP配置:
以实现三(一)中的 为ArithmeticCaculator添加 各方法 执行前 和计算结果后的日志的AOP方案为实例;
步骤一、为ArithmeticCaculatorImpl类添加@Component 注解 来表示 该组件需要被sping容器管理


- 1 package lixiuming.spring.aop.impl;
- 2
- 3 import org.springframework.stereotype.Component;
- 4
- 5 @Component
- 6 public class ArithmeticCaculatorImpl2 implements ArithmeticCaculator {
- 7
- 8 @Override
- 9 public int add(int i, int j) {
- 10 int result = i+j;
- 11 return result;
- 12 }
- 13
- 14 @Override
- 15 public int sub(int i, int j) {
- 16 int result = i-j;
- 17 return result;
- 18 }
- 19
- 20 @Override
- 21 public int mul(int i, int j) {
- 22 int result = i*j;
- 23 return result;
- 24 }
- 25
- 26 @Override
- 27 public int div(int i, int j) {
- 28 int result = i/j;
- 29 return result;
- 30 }
- 31
- 32 }
步骤二、需要添加一个切面:
关于切面声明的说明:
- 切面需要放置在spring 容器中;所以首先需要一个@Component注解
- 声明一个切面用注解 @Aspect;
为实现上述实例,需要添加一个前置通知和后置通知;前置通知即,在目标方法执行之前执行;后置通知,即在目标方法执行后执行,无论是否发生异常。
- 1 package lixiuming.spring.aop.impl;
- 2
- 3 import java.util.Arrays;
- 4 import java.util.List;
- 5
- 6 import org.aspectj.lang.JoinPoint;
- 7 import org.aspectj.lang.annotation.After;
- 8 import org.aspectj.lang.annotation.Aspect;
- 9 import org.aspectj.lang.annotation.Before;
- 10 import org.springframework.stereotype.Component;
- 11
- 12 //把这个类声明为一个切面,需要把该类放入到IOC容器中,再声明为一个切面
- 13 @Aspect
- 14 @Component
- 15 public class LoggingAspect {
- 16 // 声明该方法是一个前置通知:在目标方法之前执行
- 18 @Before("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.*(int, int) )") // 作用于接口中的所有方法
- 22 public void beforeMethod(JoinPoint joinPoint) {
- 23 String method = joinPoint.getSignature().getName();// 方法的签名
- 24 List<Object> args = Arrays.asList(joinPoint.getArgs());// 方法的参数
- 25 System.out.println("the method " + method + " begins with" + args);
- 26 }
- 27
- 28 // 声明后置通知:在目标方法执行后(无论是否发生异常)执行的通知
- 29 // 在后置通知中,不能访问目标方法执行的结果,需要在返回通知里面访问
- 30 @After("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.*(int, int) )")
- 31 public void afterMethod(JoinPoint joinPoint) {
- 32 String method = joinPoint.getSignature().getName();
- 33 List<Object> args = Arrays.asList(joinPoint.getArgs());
- 34 System.out.println("the method " + method + " is end to " + args);
- 35 }
- 36
- 37 }
步骤三、配置文件:引入了context和aop的命名空间
- 1 <?xml version="1.0" encoding="UTF-8"?>
- 2 <beans xmlns="http://www.springframework.org/schema/beans"
- 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- 4 xmlns:aop="http://www.springframework.org/schema/aop"
- 5 xmlns:context="http://www.springframework.org/schema/context"
- 6 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
- 7 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
- 8 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
- 9
- 10 <context:component-scan base-package="lixiuming.spring.aop.impl"/>
- 11 <!-- 使AspjectJ注释起作用,自动匹配的类生成代理对象 -->
- 12 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- 13 </beans>
使用main方法测试:
- 1 public static void main(String[] args) {
- 2 ApplicationContext cxt = new ClassPathXmlApplicationContext("ApplicationContext.xml");
- 3 ArithmeticCaculator arithmeticCaculator = cxt.getBean(ArithmeticCaculator.class);
- 4
- 5 int result = arithmeticCaculator.add(1, 2);
- 6 System.out.println("-->" + result);
- 7
- 8 int result1 = arithmeticCaculator.div(4, 2);
- 9 System.out.println("-->" + result1);
- 10
- 11 int result2 = arithmeticCaculator.mul(4, 2);
- 12 System.out.println("-->" + result2);
- 13
- 14 int result3 = arithmeticCaculator.sub(4, 2);
- 15 System.out.println("-->" + result3);
- 16
- 17 }
测试运行结果:
the method add begins with[1, 2]
the method add is end to [1, 2]
-->3
the method div begins with[4, 2]
the method div is end to [4, 2]
-->2
the method mul begins with[4, 2]
the method mul is end to [4, 2]
-->8
the method sub begins with[4, 2]
the method sub is end to [4, 2]
-->2
步骤二中除了使用前置和后置通知,还可以使用环绕通知来实现上述功能;
代码如下:
- 1 package lixiuming.spring.aop.impl;
- 2
- 3 import java.util.Arrays;
- 4
- 5 import org.aspectj.lang.ProceedingJoinPoint;
- 6 import org.aspectj.lang.annotation.Around;
- 7 import org.aspectj.lang.annotation.Aspect;
- 8 import org.aspectj.lang.annotation.Pointcut;
- 9 import org.springframework.stereotype.Component;
- 10
- 11 //把这个类声明为一个切面,需要把该类放入到IOC容器中
- 12 @Aspect
- 13 @Component
- 14 public class LoggingAspect {
- 15
- 16 // 定义一个方法,用于声明切入点表达式,一般该方法中不需要其他的代码
- 17 // 使用@Pointcut 来声明切入点表达式,
- 18 // 后面的其他通知直接使用方法名来引用当前的切入点表达式
- 19 @Pointcut("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.*(..))")
- 20 public void declareJoinPointExpress() {
- 21
- 22 }
- 23
- 24 /**
- 25 * 环绕通知需要携带ProceedingJoinPoint类型的参数
- 26 * 环绕通知类似动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行日志方法 且必须有返回值,返回值是目标方法的返回值
- 27 */
- 28 @Around("declareJoinPointExpress()")
- 29 public Object aroundMethod(ProceedingJoinPoint point) {
- 30 Object result = null;
- 31 String method = point.getSignature().getName();
- 32 // 执行目标方法
- 33 try {
- 34 // 前置通知
- 35 System.out.println("the method " + method + " is begin with " + Arrays.asList(point.getArgs()));
- 36 result = point.proceed();
- 37 // 后置通知
- 38 System.out.println("the method " + method + " is end to " + Arrays.asList(point.getArgs()));
- 39 } catch (Throwable e) {
- 40 // TODO Auto-generated catch block
- 41 System.out.println("the method " + method + " occured exception: " + e);
- 42 throw new RuntimeException(e);
- 43 }
- 44
- 45 return result;
- 46 }
- 47 }
五、切面的优先级
使用@Order(index)指定执行顺序的优先级,index为数字,index越小,优先级越高;@Order位置为放置在@Aspect前面;代码如下:
- @Order(1)//执行顺序的优先级
- @Aspect
- @Component
- //验证通知
- public class VlidationAspect {
- @Before("LoggingAspect.declareJoinPointExpress()")
- public void validationArgs(JoinPoint jointPoint){
- System.out.println("-->validation:"+Arrays.asList(jointPoint.getArgs()));
- }
- }
六、xml方式配置AOP
ArithmeticCaculator 不变;ArithmeticCaculatorImpl移除@Component;
LoggingAspect:
- 1 package lixiuming.spring.aop.impl2;
- 2
- 3 import java.util.Arrays;
- 4 import java.util.List;
- 5
- 6 import org.aspectj.lang.JoinPoint;
- 7 import org.aspectj.lang.ProceedingJoinPoint;
- 8 import org.aspectj.lang.annotation.After;
- 9 import org.aspectj.lang.annotation.AfterReturning;
- 10 import org.aspectj.lang.annotation.AfterThrowing;
- 11 import org.aspectj.lang.annotation.Around;
- 12 import org.aspectj.lang.annotation.Aspect;
- 13 import org.aspectj.lang.annotation.Before;
- 14 import org.aspectj.lang.annotation.Pointcut;
- 15 import org.springframework.stereotype.Component;
- 16
- 17 public class LoggingAspect {
- 18
- 19 public void beforeMethod(JoinPoint joinPoint){
- 20 String method = joinPoint.getSignature().getName();
- 21 List<Object> args = Arrays.asList(joinPoint.getArgs());
- 22 System.out.println("the method "+method+" begins with"+args);
- 23 }
- 24
- 25 public void afterMethod(JoinPoint joinPoint){
- 26 String method = joinPoint.getSignature().getName();
- 27 List<Object> args = Arrays.asList(joinPoint.getArgs());
- 28 System.out.println("the method "+method+" is end to "+args);
- 29 }
- 30
- 31 /**
- 32 *在方法正常结束后执行的代码
- 33 *返回通知是可以访问到方法的返回值
- 34 */
- 35 public void afterReturn(JoinPoint joinPoint,Object result){
- 36 String method = joinPoint.getSignature().getName();
- 37 System.out.println("the method "+method+" is end with " +result);
- 38 }
- 39
- 40 public void afterThrowing(JoinPoint joinPoint,Object ex){
- 41 String method = joinPoint.getSignature().getName();
- 42 System.out.println("the method "+method+" occured exception: " + ex);
- 43 }
- 44
- 45 public Object aroundMethod(ProceedingJoinPoint point){
- 46 Object result = null;
- 47 String method = point.getSignature().getName();
- 48 //执行目标方法
- 49 try {
- 50 //前置通知
- 51 System.out.println("the method "+method+" is begin with "+Arrays.asList(point.getArgs()));
- 52 result = point.proceed();
- 53 //返回通知
- 54 System.out.println("the method "+method+" is end to "+ result);
- 55 } catch (Throwable e) {
- 56 // TODO Auto-generated catch block
- 57 System.out.println("the method "+method+" occured exception: " + e);
- 58 throw new RuntimeException(e);
- 59 }
- 60 System.out.println("the method "+method+" ends");
- 61
- 62 return 100;
- 63 }
- 64 }
- VlidationAspect:
- 1 package lixiuming.spring.aop.impl2;
- 2
- 3 import java.util.Arrays;
- 4
- 5 import org.aspectj.lang.JoinPoint;
- 6 import org.aspectj.lang.annotation.Aspect;
- 7 import org.aspectj.lang.annotation.Before;
- 8 import org.springframework.core.annotation.Order;
- 9 import org.springframework.stereotype.Component;
- 10 //验证通知
- 11 public class VlidationAspect {
- 12 public void validationArgs(JoinPoint jointPoint){
- 13 System.out.println("-->validation:"+Arrays.asList(jointPoint.getArgs()));
- 14 }
- 15
- 16
- 17 }
配置文件:
- 1 <?xml version="1.0" encoding="UTF-8"?>
- 2 <beans xmlns="http://www.springframework.org/schema/beans"
- 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- 4 xmlns:aop="http://www.springframework.org/schema/aop"
- 5 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
- 6 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
- 7
- 8 <!-- 配置bean -->
- 9 <bean id="aop" class="lixiuming.spring.aop.impl2.ArithmeticCaculatorImpl2"></bean>
- 10 <!-- 配置切面的bean -->
- 11 <bean id="LoggingAspect" class="lixiuming.spring.aop.impl2.LoggingAspect"></bean>
- 12 <bean id="VlidationAspect" class="lixiuming.spring.aop.impl2.VlidationAspect"></bean>
- 13
- 14 <!-- 配置AOP -->
- 15 <aop:config>
- 16 <!-- 配置切面表达式 -->
- 17 <aop:pointcut expression="execution(* lixiuming.spring.aop.impl2.ArithmeticCaculator.*(int,int))" id="pointcut"/>
- 18 <!-- 配置切面及通知 -->
- 19 <aop:aspect ref="LoggingAspect" order="2">
- 20 <aop:before method="beforeMethod" pointcut-ref="pointcut"/>
- 21 <aop:after method="afterMethod" pointcut-ref="pointcut"/>
- 22 <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/>
- 23 <aop:after-returning method="afterReturn" pointcut-ref="pointcut" returning="result"/>
- 24 <aop:around method="aroundMethod" pointcut-ref="pointcut"/>
- 25 </aop:aspect>
- 26
- 27 <aop:aspect ref="VlidationAspect" order="1">
- 28 <aop:before method="validationArgs" pointcut-ref="pointcut"/>
- 29 </aop:aspect>
- 30 </aop:config>
- 31 </beans>
三(二)、AOP配置的更多相关文章
- Spring AOP编程(二)-AOP实现的三种方式
AOP的实现有三种方式: l aop底层将采用代理机制进行实现. l 接口 + 实现类 :spring采用 jdk 的动态代理Proxy. l 实现类: ...
- Linux的VMWare下Centos7的三种网络配置过程(网络二)
Linux之VMWare下Centos7的三种网络配置过程 环境:虚拟软件:VMWare 14.0客户机:windows 10虚拟机:centos 7 VMware三种网络连接方式 Bridge(桥接 ...
- 框架源码系列九:依赖注入DI、三种Bean配置方式的注册和实例化过程
一.依赖注入DI 学习目标1)搞清楚构造参数依赖注入的过程及类2)搞清楚注解方式的属性依赖注入在哪里完成的.学习思路1)思考我们手写时是如何做的2)读 spring 源码对比看它的实现3)Spring ...
- Spring的第三天AOP之xml版
Spring的第三天AOP之xml版 ssm框架 spring AOP介绍 AOP(Aspect Oriented Programming),面向切面编程.它出来的目的并不是去取代oop,而是对它的 ...
- Spring第三天——AOP注解实现与事务管理
大致内容: aspectJ的aop操作(基于注解,对比day02配置操作)(会用) *jdbcTemplate操作(实现CRUD) *spring配置连接池 *spring事务管理 一.AspectJ ...
- Spring的AOP配置
Spring的AOP配置 1.先写一个普通类: package com.spring.aop; public class Common { public void execute(String us ...
- 从0开始搭建SQL Server AlwaysOn 第三篇(配置AlwaysOn)
从0开始搭建SQL Server AlwaysOn 第三篇(配置AlwaysOn) 第一篇http://www.cnblogs.com/lyhabc/p/4678330.html第二篇http://w ...
- ThinkPHP 3.2.3(二)配置
一.配置格式 1.PHP数组定义 默认所有配置文件的定义格式均采用返回PHP数组的方式,配置参数不区分大小写. 如果使用二维数组来配置更多的信息,则二级参数配置区分大小写.格式为: //项目配置文件r ...
- Maven提高篇系列之(二)——配置Plugin到某个Phase(以Selenium集成测试为例)
这是一个Maven提高篇的系列,包含有以下文章: Maven提高篇系列之(一)——多模块 vs 继承 Maven提高篇系列之(二)——配置Plugin到某个Phase(以Selenium集成测试为例) ...
随机推荐
- requestAnimationFrame 切换页面问题
requestAnimationFrame 切换页面时, 之前定时的内容还会继续执行. 所以 要注意处理动画函数内容,否则会出现死循环. 遇到的问题: 我在两个页面都有使用 requestAnimat ...
- 鸿蒙内核源码分析(汇编基础篇) | CPU在哪里打卡上班? | 百篇博客分析OpenHarmony源码 | v22.01
百篇博客系列篇.本篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪里打卡上班 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在 ...
- YbtOJ#643-机器决斗【贪心,李超树】
正题 题目链接:https://www.ybtoj.com.cn/problem/643 题目大意 \(n\)个机器人,第\(i\)个攻击力为\(A_i\),防御为\(D_i\). 然后你每次可以对一 ...
- GitHub 和 Gitee 开源免费 10 个超赞后台管理面板,看完惊呆了!
软件工程师在实际项目开发中不可避免需要依赖一些前后端的后台管理系统框架,而不是从零开始一点点的搭建,浪费人力.目前市面上有很多开放源码.且免费的后台管理面板,样式色彩也比较丰富美观. 今天整理 ...
- VUE自学日志01-MVC和MVVM
一.需要了解的基础概念 Model(M)是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,主要围绕数据库系统展开.这里的难点主要在于需要和前端约定统一的接口规则. View(V)是视图层,也就是 ...
- 架构师必备:MySQL主从同步原理和应用
日常工作中,MySQL数据库是必不可少的存储,其中读写分离基本是标配,而这背后需要MySQL开启主从同步,形成一主一从.或一主多从的架构,掌握主从同步的原理和知道如何实际应用,是一个架构师的必备技能. ...
- 接口自动化-Python3+request上传文件,发送multipart/form-data编码
1.安装requests_toolbelt pip install requests-toolbelt 2.发送文件中的数据 from requests_toolbelt import Multi ...
- TCP三次握手四次挥手,通俗易懂版
三次握手四次挥手 三次握手 其实很好理解,三次握手就是保证双手都有发送和接受的能力.那么最少三次才能验证完成 即----> 客户端发送---服务端收到----服务端发送-- 1.客户端发送 -- ...
- javascript-jquery对象的css处理
一.css基本属性处理 1.css()方法:获取css属性值.$("选择器").css(name);//获取匹配选择器的元素指定css属性值. 2.css()方法:设置css属性值 ...
- 【c++ Prime 学习笔记】第17章 标准库特殊设施
17.1 tuple类型 tuple是类似pair的模板: pair和tuple的成员类型都可以不相同 pair恰好有两个成员,tuple可有任意数量的成员 按照不同参数数量和类型实例化出的tuple ...