AOP面向切面编程(1.6w字长文讲清AOP)
AOP面向切面编程
- 一、代理模式
- 1. 概念
- 2. 静态代理
- 3. 动态代理
- 二、 AOP的核心套路
- 三、AOP术语
- 1. 横切关注点
- 2. 通知
- 3. 切面
- 4. 目标
- 5. 代理
- 6. 连接点
- 7. 切入点
- 四、基于注解的AOP
- 1. AOP概念介绍
- 2. 基于注解的AOP用到的技术
- 3. 具体操作
- 3.1 初步实现
- 3.2 各个通知获取细节信息
- 3.3 抽取切入点表达式
- 3.4 切入点表达式语法
- 3.5 环绕通知
- 3.6 切面的优先级
- 3.7 没有接口的情况
- 小结
- 五、AOP对获取bean的影响
一、代理模式
1. 概念
① 介绍
二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
使用代理后:
② 日常生活中的“代理”
- 广告商找大明星拍广告需要经过经纪人
- 合作伙伴找大老板谈合作要约见面时间需要经过秘书
③ 相关术语
- 代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。
- 目标:被代理“套用”了非核心逻辑代码的类、对象、方法。
2. 静态代理
我们有个需求,需要在一个学生类吃饭的方法中实现详细的吃饭过程,首先创建一个Student类,实现EatFood接口的吃东西的方法eat(String foodName),创建静态代理类进行增强:
public interface EatFood {void eat();
}
public class Student implements EatFood{@Overridepublic void eat() {System.out.println("开吃啦...");}
}
public class EatStaticProxy implements EatFood {//将被代理的目标对象声明为成员变量private EatFood taget;//这里只是方便设置属性public EatStaticProxy(EatFood taget) {this.taget = taget;}@Overridepublic int eat(String foodName) {// 附加功能由代理类中的代理方法来实现System.out.println("学生去打饭操作,打的菜是:" + foodName);// 通过目标对象来实现核心业务逻辑int num = taget.eat(foodName);System.out.println("学生吃饱了并把餐具放到指定位置,今日饭菜分数为:" + num);return num;}
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿吃饭功能来说,将来其他人教师、职工也要吃饭,那还得再声明更多个静态代理类,那就产生了大量重复的代码,吃饭功能还是分散的,没有统一管理。
提出进一步的需求:将吃饭功能集中到一个代理类中,将来有任何吃饭需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。
3. 动态代理
为了表达的贴切性,这里用哪个日志功能来演示动态代理,理解动态代理并不是要深入学习它的具体方法,而是要理解他的思想,即:提供一个代理类来代理我们的目标方法进行操作,实现间接调用,结构解耦。
① 生产代理对象的工厂类
JDK本身就支持动态代理,这是反射技术的一部分。下面我们还是创建一个代理类(生产代理对象的工厂类):
// 泛型T要求是目标对象实现的接口类型,本代理类根据这个接口来进行代理
public class LogDynamicProxyFactory<T> {// 将被代理的目标对象声明为成员变量private T target;public LogDynamicProxyFactory(T target) {this.target = target;}public T getProxy() {// 创建代理对象所需参数一:加载目标对象的类的类加载器ClassLoader classLoader = target.getClass().getClassLoader();// 创建代理对象所需参数二:目标对象的类所实现的所有接口组成的数组Class<?>[] interfaces = target.getClass().getInterfaces();// 创建代理对象所需参数三:InvocationHandler对象// Lambda表达式口诀:// 1、复制小括号// 2、写死右箭头// 3、落地大括号InvocationHandler handler = (// 代理对象,当前方法用不上这个对象Object proxy,// method就是代表目标方法的Method对象Method method,// 外部调用目标方法时传入的实际参数Object[] args)->{// 我们对InvocationHandler接口中invoke()方法的实现就是在调用目标方法// 围绕目标方法的调用,就可以添加我们的附加功能// 声明一个局部变量,用来存储目标方法的返回值Object targetMethodReturnValue = null;// 通过method对象获取方法名String methodName = method.getName();// 为了便于在打印时看到数组中的数据,把参数数组转换为ListList<Object> argumentList = Arrays.asList(args);try {// 在目标方法执行前:打印方法开始的日志System.out.println("[动态代理][日志] " + methodName + " 方法开始了,参数是:" + argumentList);// 调用目标方法:需要传入两个参数// 参数1:调用目标方法的目标对象// 参数2:外部调用目标方法时传入的实际参数// 调用后会返回目标方法的返回值targetMethodReturnValue = method.invoke(target, args);// 在目标方法成功后:打印方法成功结束的日志System.out.println("[动态代理][日志] " + methodName + " 方法成功结束了,返回值是:" + targetMethodReturnValue);}catch (Exception e){// 通过e对象获取异常类型的全类名String exceptionName = e.getClass().getName();// 通过e对象获取异常消息String message = e.getMessage();// 在目标方法失败后:打印方法抛出异常的日志System.out.println("[动态代理][日志] " + methodName + " 方法抛异常了,异常信息是:" + exceptionName + "," + message);}finally {// 在目标方法最终结束后:打印方法最终结束的日志System.out.println("[动态代理][日志] " + methodName + " 方法最终结束了");}// 这里必须将目标方法的返回值返回给外界,如果没有返回,外界将无法拿到目标方法的返回值return targetMethodReturnValue;};// 创建代理对象T proxy = (T) Proxy.newProxyInstance(classLoader, interfaces, handler);// 返回代理对象return proxy;}
}
这里的思路就是抓住Proxy.newProxyInstance就是生成代理对象的核心方法,需要什么参数我们就提供什么参数。
② 测试
@Test
public void testDynamicProxy() {// 1.创建被代理的目标对象Calculator target = new CalculatorPureImpl();// 2.创建能够生产代理对象的工厂对象LogDynamicProxyFactory<Calculator> factory = new LogDynamicProxyFactory<>(target);// 3.通过工厂对象生产目标对象的代理对象Calculator proxy = factory.getProxy();// 4.通过代理对象间接调用目标对象int addResult = proxy.add(10, 2);System.out.println("方法外部 addResult = " + addResult + "\n");int subResult = proxy.sub(10, 2);System.out.println("方法外部 subResult = " + subResult + "\n");int mulResult = proxy.mul(10, 2);System.out.println("方法外部 mulResult = " + mulResult + "\n");int divResult = proxy.div(10, 2);System.out.println("方法外部 divResult = " + divResult + "\n");
}
二、 AOP的核心套路
aop的核心套路就是对目标方法进行代理逻辑一层一层的套,将目标方法通过try-catch-finally块包起来,然后通过这个结构我们可以设置方法执行前的操作,方法执行成功后的操作,方法出异常时的操作以及方法最终结束的作用(就是后面学到的前置、返回、异常、后置通知)。甚至于可以将这已经包好的逻辑再当成一层“目标方法”在对其进行系列添加逻辑操作(也就是环绕通知了)
三、AOP术语
1. 横切关注点
从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。
2. 通知
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
- 前置通知:在被代理的目标方法前执行
- 返回通知:在被代理的目标方法成功结束后执行
- 异常通知:在被代理的目标方法异常结束后执行
- 后置通知:在被代理的目标方法最终结束后执行
- 环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
3. 切面
封装通知方法的类。
4. 目标
被代理的目标类
5. 代理
向目标对象应用通知之后创建的代理对象。
6. 连接点
这也是一个纯逻辑概念,不是语法定义的。
把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。
类里面哪些方法可以被增强,这些方法称为连接点
7. 切入点
定位连接点的方式。
每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。
如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。
Spring 的 AOP 技术可以通过切入点定位到特定的连接点。
切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
实际被真正增强的方法,称为切入点
四、基于注解的AOP
1. AOP概念介绍
① 名词解释
AOP:Aspect Oriented Programming面向切面编程
② AOP的作用
主要有两点
- 简化代理:将方法中重复的代理提取出来,让核心方法更专注于自己的功能,提高内聚性
- 代理增强:把特定的功能封装到切面类中,需要直接套用,被套用了切面逻辑的方法就被切面给增强了。
2. 基于注解的AOP用到的技术
- 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口。
- cglib:通过继承被代理的目标类实现代理,所以不需要目标类实现接口。
- AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
3. 具体操作
3.1 初步实现
创建maven工程
- 加入依赖
<dependencies><!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.1</version></dependency><!-- spring-aspects会帮我们传递过来aspectjweaver --><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.3.1</version></dependency><!-- Spring的测试包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.3.1</version></dependency><!-- junit测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency></dependencies>
- 准备被代理的目标资源
① 接口
public interface EatFood {int eat(String foodName);
}
② 创建目标类
在Spring环境下工作,所有的一切都必须放在IOC容器中。现在接口的实现类是AOP要代理的目标类,所以它也必须放入IOC容器。
@Component
public class Student implements EatFood {@Overridepublic int eat(String foodName) {//模拟学生对饭菜打分int score = (int)(Math.random()*9+1);System.out.println("学生用晚餐并对本餐进行了打分,今天吃的是:" + foodName + "。分数为:" + score);return score;}
}
- 创建切面类
@Component // @Aspect表示这个类是一个切面类
@Aspect // @Component注解保证这个切面类能够放入IOC容器
public class EatAspect {// @Before注解:声明当前方法是前置通知方法// value属性:指定切入点表达式,由切入点表达式控制当前通知方法要作用在哪一个目标方法上@Before(value = "execution(public int com.zengchuiyu.aoptest.api.EatFood.eat(String))")public void eatBefore(){System.out.println("[AOP前置通知]学生在打饭了");}@AfterReturning(value = "execution(public int com.zengchuiyu.aoptest.api.EatFood.eat(String))")public void eatAfterReturning(){System.out.println("[AOP返回通知]学生吃完饭将餐具放到指定位置");}@AfterThrowing(value = "execution(public int com.zengchuiyu.aoptest.api.EatFood.eat(String))")public void eatAfterThrowing(){System.out.println("[AOP异常通知]学生因故停止了用餐");}@After(value = "execution(public int com.zengchuiyu.aoptest.api.EatFood.eat(String))")public void eatAfter(){System.out.println("[AOP最终通知]学生离开了饭堂");}}
- 创建Spring的配置文件
<?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.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><!-- 开启基于注解的AOP功能 --><aop:aspectj-autoproxy/><!-- 配置自动扫描的包 --><context:component-scan base-package="com.zengchuiyu"/></beans>
- 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = {"classpath:spring-context.xml"})
public class StudentTest {@Autowiredprivate EatFood eatFood;@Testpublic void test1(){int score = eatFood.eat("梅菜扣肉");}}
- 运行结果
表名我们成功通过注解的方式实现了aop
- 通知执行的顺序
Spring版本5.3.x以前:
- 前置通知
- 目标操作
- 后置通知
- 返回通知或异常通知
Spring版本5.3.x以后:
- 前置通知
- 目标操作
- 返回通知或异常通知
- 后置通知
3.2 各个通知获取细节信息
回想初步操作,我们可以通过切面类的通知对目标方法进行增强,难受仔细思考难免在增强的时候我们其实是要用到目标方法的详细信息进行操作的,例如方法名、参数列表等,所以不得不引入另外一个接口:JoinPoint接口
- JoinPoint接口
org.aspectj.lang.JoinPoint
- 要点1:JoinPoint接口通过getSignature()方法获取目标方法的签名
- 要点2:通过目标方法签名对象获取方法名
- 要点3:通过JoinPoint对象获取外界调用目标方法时传入的实参列表组成的数组
需要获取方法签名、传入的实参等信息时,可以在通知方法声明JoinPoint类型的形参。
方法签名:定义了描述方法信息的一些方法
@Before(value = "execution(public int com.zengchuiyu.aoptest.api.EatFood.eat(String))")public void eatBefore(JoinPoint joinPoint){// 1.通过JoinPoint对象获取目标方法签名对象// 方法的签名:一个方法的全部声明信息Signature signature = joinPoint.getSignature();// 2.通过方法的签名对象获取目标方法的详细信息String methodName = signature.getName();System.out.println("methodName = " + methodName);int modifiers = signature.getModifiers();System.out.println("modifiers = " + modifiers);String declaringTypeName = signature.getDeclaringTypeName();System.out.println("declaringTypeName = " + declaringTypeName);// 3.通过JoinPoint对象获取外界调用目标方法时传入的实参列表Object[] args = joinPoint.getArgs();// 4.由于数组直接打印看不到具体数据,所以转换为List集合List<Object> argList = Arrays.asList(args);System.out.println("[AOP前置通知]学生在打饭了");}
- 方法返回值
在返回通知中获取目标方法返回值分两步:
第一步:在@AfterReturning注解中通过returning属性设置一个名称
第二步:使用returning属性设置的名称在通知方法中声明一个对应的形参
@AfterReturning(value = "execution(public int com.zengchuiyu.aoptest.api.EatFood.eat(String))",returning = "targetreturnValue")public void eatAfterReturning(Object targetreturnValue){System.out.println("[AOP返回通知]学生吃完饭将餐具放到指定位置");}
- 目标方法抛出的异常
在异常通知中获取目标方法抛出的异常分两步:
第一步:在@AfterThrowing注解中声明一个throwing属性设定形参名称
第二步:使用throwing属性指定的名称在通知方法声明形参,Spring会将目标方法抛出的异常对象从这里传给我们
3.3 抽取切入点表达式
① 声明
在一处声明切入点表达式之后,其他有需要的地方引用这个切入点表达式。易于维护,一处修改,处处生效。声明方式如下:
@Pointcut("execution(* *..*.eat(..))")public void declarPointCut(){}
② 同一个类内部引用
@Before(value = "declarPointCut()")public void eatBefore(JoinPoint joinPoint){
③ 在不同类中引用
@Before(value = "com.zengchuiyu.aoptest.aspect.EatCommonAspect.declarPointCut()")public void eatBefore(JoinPoint joinPoint){
④ 集中管理
而作为存放切入点表达式的类,可以把整个项目中所有切入点表达式全部集中过来,便于统一管理
3.4 切入点表达式语法
- 切入点表达式的作用
2. 语法细节
用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限
在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。
例如:*.Hello匹配com.Hello,不匹配com.atguigu.Hello
在包名的部分,使用“*…”表示包名任意、包的层次深度任意
在类名的部分,类名部分整体用*号代替,表示类名任意
在类名的部分,可以使用*号代替类名的一部分
*Service
上面例子表示匹配所有名称以Service结尾的类或接口
- 在方法名部分,可以使用*号表示方法名任意
- 在方法名部分,可以使用*号代替方法名的一部分
*Operation
上面例子表示匹配所有方法名以Operation结尾的方法
在方法参数列表部分,使用(…)表示参数列表任意
在方法参数列表部分,使用(int,…)表示参数列表以一个int类型的参数开头
在方法参数列表部分,基本数据类型和对应的包装类型是不一样的
切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
- 在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符
execution(public int *..*Service.*(.., int))
上面例子是对的,下面例子是错的:
execution(* int *..*Service.*(.., int))
但是public *表示权限修饰符明确,返回值任意是可以的。
对于execution()表达式整体可以使用三个逻辑运算符号
- execution() || execution()表示满足两个execution()中的任何一个即可
- execution() && execution()表示两个execution()表达式必须都满足
- !execution()表示不满足表达式的其他方法
- 总结
3.5 环绕通知
环绕通知对应整个try…catch…finally结构,包括前面四种通知的所有功能。(可以理解为把已经加入前四种的目标方法再当成一个目标方法)
// 使用@Around注解标明环绕通知方法
@Around(value = "com.atguigu.aop.aspect.AtguiguPointCut.transactionPointCut()")
public Object manageTransaction(// 通过在通知方法形参位置声明ProceedingJoinPoint类型的形参,// Spring会将这个类型的对象传给我们ProceedingJoinPoint joinPoint) {// 通过ProceedingJoinPoint对象获取外界调用目标方法时传入的实参数组Object[] args = joinPoint.getArgs();// 通过ProceedingJoinPoint对象获取目标方法的签名对象Signature signature = joinPoint.getSignature();// 通过签名对象获取目标方法的方法名String methodName = signature.getName();// 声明变量用来存储目标方法的返回值Object targetMethodReturnValue = null;try {// 在目标方法执行前:开启事务(模拟)System.out.println("[AOP 环绕通知] 开启事务,方法名:" + methodName + ",参数列表:" + Arrays.asList(args));// 过ProceedingJoinPoint对象调用目标方法// 目标方法的返回值一定要返回给外界调用者targetMethodReturnValue = joinPoint.proceed(args);// 在目标方法成功返回后:提交事务(模拟)System.out.println("[AOP 环绕通知] 提交事务,方法名:" + methodName + ",方法返回值:" + targetMethodReturnValue);}catch (Throwable e){// 在目标方法抛异常后:回滚事务(模拟)System.out.println("[AOP 环绕通知] 回滚事务,方法名:" + methodName + ",异常:" + e.getClass().getName());}finally {// 在目标方法最终结束后:释放数据库连接System.out.println("[AOP 环绕通知] 释放数据库连接,方法名:" + methodName);}return targetMethodReturnValue;
}
环绕通知和前置通知,后置通知有着很大的区别,主要有两个重要的区别:
1)目标方法的调用由环绕通知决定,即你可以决定是否调用目标方法,而前置和后置通知 是不能决定的,他们只是在方法的调用前后执行通知而已,即目标方法肯定是要执行的。
1) 目标方法的调用由环绕通知决定,即你可以决定是否调用目标方法,而前置和后置通知 是不能决定的,他们只是在方法的调用前后执行通知而已,即目标方法肯定是要执行的。
2) 环绕通知可以控制返回对象,即你可以返回一个与目标对象完全不同的返回值,虽然这很危险,但是你却可以办到。而后置方法是无法办到的,因为他是在目标方法返回值后调用
使用面向切面来处理一些问公共的问题,比如,权限管理,事务的委托
3.6 切面的优先级
① 概念
相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
- 优先级高的切面:外面
- 优先级低的切面:里面
使用@Order注解可以控制切面的优先级:
- @Order(较小的数):优先级高
- @Order(较大的数):优先级低
② 实际意义
实际开发时,如果有多个切面嵌套的情况,要慎重考虑。例如:如果事务切面优先级高,那么在缓存中命中数据的情况下,事务切面的操作都浪费了。
此时应该将缓存切面的优先级提高,在事务操作之前先检查缓存中是否存在目标数据。
3.7 没有接口的情况
在目标类没有实现任何接口的情况下,Spring会自动使用cglib技术实现代理。
小结
五、AOP对获取bean的影响
对实现了接口的类应用切面,我们通过
- 应用了切面后,真正放在IOC容器中的是代理类的对象
- 目标类并没有被放到IOC容器中,所以根据目标类的类型从IOC容器中是找不到的
对没实现接口的类应用切面
AOP面向切面编程(1.6w字长文讲清AOP)相关推荐
- java aop面向切面编程
最近一直在学java的spring boot,一直没有弄明白aop面向切面编程是什么意思.看到一篇文章写得很清楚,终于弄明白了,原来跟python的装饰器一样的效果.http://www.cnblog ...
- 【AOP 面向切面编程】Android Studio 使用 AspectJ 监控方法运行原理分析
文章目录 一.查看使用 AspectJ 后生成的 Class 字节码类 二.AspectJ 的本质 一.查看使用 AspectJ 后生成的 Class 字节码类 在 Android Studio 中查 ...
- 【AOP 面向切面编程】Android Studio 中配置 AspectJ ( 下载并配置AS中 jar 包 | 配置 Gradle 和 Gradle 插件版本 | 配置 Gradle 构建脚本 )
文章目录 一.AspectJ 下载 二.拷贝 aspectjrt.jar 到 Android Studio 三.配置 Gradle 和 Gradle 插件版本 四.配置 Gradle 构建脚本 一.A ...
- 【AOP 面向切面编程】AOP 简介 ( AspectJ 简介 | AspectJ 下载 )
文章目录 一.AOP 简介 二.AspectJ 简介 三.AspectJ 下载 一.AOP 简介 AOP 是 Aspect Oriented Programming 的缩写 , 面向切面编程 ; 利用 ...
- 切面是异步还是同步操作‘_Autofac的AOP面向切面编程研究
什么是AOP: 我的理解是 把系统性的编程工作封装起来 =>我给这个取个名字叫 "Aspect",然后通过AOP技术把它切进我们的业务逻辑代码 => "业务& ...
- Javascript aop(面向切面编程)之around(环绕)
Aop又叫面向切面编程,其中"通知"是切面的具体实现,分为before(前置通知).after(后置通知).around(环绕通知),用过spring的同学肯定对它非常熟悉,而在j ...
- 大数据WEB阶段Spring框架 AOP面向切面编程(一)
Spring - AOP面向切面编程(一) 一.代理模式概述 代理的特点:(目标对象即被代理者) 实现和目标对象相同的接口 具备和目标对象的方法 代理者不仅要做目标对象的方法 , 还要做一些额外的操作 ...
- 大数据WEB阶段Spring框架 AOP面向切面编程(二)
Spring AOP面向切面编程(二) 一.切入点的execution表达式 execution的表达形式: execution(修饰符? 返回值类型 所在包类? 方法名(参数列表) 异常?) ?表示 ...
- AOP(面向切面编程)大概了解一下
前言 上一篇在聊MemoryCache的时候,用到了Autofac提供的拦截器进行面向切面编程,很明显能体会到其优势,既然涉及到了,那就趁热打铁,一起来探探面向切面编程. 正文 1. 概述 在软件业, ...
- Autofac的AOP面向切面编程研究
我的理解是 把系统性的编程工作封装起来 =>我给这个取个名字叫 "Aspect",然后通过AOP技术把它切进我们的业务逻辑代码 => "业务" 这样 ...
最新文章
- Leetcode24.Swap Nodes in Pairs两两交换链表中的节点
- 金融系统 mysql,mysql - 适用于CRM,CMS和其他金融系统的RDBMS与NoSQL [已结束]
- 当 Android 开发者遇见 TensorFlow
- 启明云端分享 | SSD201\SSD202D 核心板如何批量烧录,母片制作教程分享
- group_concat默认长度限制
- 常用前端代码资源(转)
- 微服务架构工作笔记001---认识Service Mesh
- Ubuntu离线安装subversion
- 2.分布式服务架构:原理、设计与实战 --- 彻底解决分布式系统一致性的问题
- pla3d打印材料密度_FDM 3D打印机的常用耗材PLA的密度 创想三维
- OrCAD之Design Resources的文件导入新的.dsn文件后,旧的。dsn文件如何去除?
- vscode自动补全c语言_vscode代码自动补全失效
- 计算机内存不足16g内存,加装16G内存,电脑却运行卡顿、崩溃?原来是这项设置有问题!...
- 动态规划之《高楼扔鸡蛋》问题详解 LeetCode 887.鸡蛋掉落
- 微软技术大会-无人机为中国电信巡航基站
- 扫地机器人水箱背景_一种扫地机器人的水箱结构的制作方法
- android禁止安装第三方app,Android6.0 禁止安装未知来源应用
- PhpSpreadsheet 基本使用和导入 导出 模版生成Excel文件
- NRF51822 2.4G无线
- MIUI“息屏听剧”功能实现调研
热门文章
- 【数字信号调制】基于matlab GUI PCM编码+QAM调制【含Matlab源码 1095期】
- 【优化算法】鸽群优化算法(PIO)【含Matlab源码 1077期】
- 自动化生产线认知_什么是认知自动化?
- 关于树叶的活动设计_悦趣课堂、教学相长 ——济南市天桥区金色悦城幼儿园公开课活动纪实...
- 转载---ubutun18.04系统安装搜狗输入法
- android 扫描ble设备,Android设备扫描后列出BLE设备
- java二级考纲_二级JAVA程序设计考试大纲
- 23007 2017-2018-2 《程序设计与数据结构》第2周学习总结
- Java二十三设计模式之-----桥接模式
- Drawable解析1——ColorDrawable、BitmapDrawable、ClipDrawabl和ScaleDrawable