依旧是常用的学习讨论进行的论述
Spring的另一个核心就是AOP:Aspect Oriented Programming 面向切面编程
面向切面编程:基于OOP基础之上新的编程思想,OOP面向的主要对象是类,而AOP面向的主要对象是切面,在处理日志、安全管理、事务管理等方面有非常重要的作用。AOP是Spring中重要的核心点,虽然IOC容器没有依赖AOP,但是AOP提供了非常强大的功能,用来对IOC做补充。通俗点说的话就是在程序运行期间,将某段代码动态切入到指定方法的指定位置进行运行的这种编程方式。
在没有学习Spring的时候,添加日志我们该如何去做呢?(用了经典的计算器案例,进行演示)
–定义一个计算器接口
package com.kaisi.service;
//定义了个计算器接口
public interface Calculator {
public int add(Integer i,Integer j);
public int sub(Integer i,Integer j);
public int mul(Integer i,Integer j);
public int div(Integer i,Integer j);
}
–小工具类
/*
* 对冒牌日志进行封装,意思一下看看就行,没真的加日志
* */
public class LogUtil {
//可变参数,可以随意指定的一种写法
public static void start(Object ... objects){
System.out.println("开始执行,参数是:"+ Arrays.asList(objects));
}
public static void stop(Object ... objects){
System.out.println("开始结束,结果是:"+ Arrays.asList(objects));
}
}
–标准实现
package com.kaisi.service.impl;
import com.kaisi.service.Calculator;
import com.kaisi.util.LogUtil;
public class MyCalculator implements Calculator {
@Override
public int add(Integer i, Integer j) {
LogUtil.start(i,j);
int result = i+j;
LogUtil.stop(result);
return result;
}
@Override
public int sub(Integer i, Integer j) {
LogUtil.start(i,j);
int result = i-j;
LogUtil.stop(result);
return result;
}
@Override
public int mul(Integer i, Integer j) {
LogUtil.start(i,j);
int result = i*j;
LogUtil.stop(result);
return result;
}
@Override
public int div(Integer i, Integer j) {
LogUtil.start(i,j);
int result = i/j;
LogUtil.stop(result);
return result;
}
}
--测试类结果
```![在这里插入图片描述](https://img-blog.csdnimg.cn/20201008191433938.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTY4NzU0Ng==,size_16,color_FFFFFF,t_70#pic_center)
封装抽象后虽然简单了很多,但是并未包含具体方法名。而且,理想状态是在程序运行过程中动态的获取方法的名称及参数、结果等相关信息,此时可以通过使用**动态代理**的方式来进行实现。(慢慢引出了AOP的本质)
--编写Proxy代理类
```java
package com.kaisi.proxy;
import com.kaisi.service.Calculator;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/*
* 采用动态代理的方式进行加入额外功能
* */
public class CalculatorProxy {
/*
* 给传入的参数对象创建一个动态代理的对象
* */
public static Calculator getProxy(Calculator calculator){
//被代理对象的类加载器
ClassLoader classLoader = calculator.getClass().getClassLoader();
//被代理对象的接口
Class<?>[] interfaces = calculator.getClass().getInterfaces();
//方法执行器,执行被代理对象的目标方法
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
System.out.println(method.getName()+"方法开始执行,参数是:"+ Arrays.asList(args));
result = method.invoke(calculator, args);
System.out.println(method.getName()+"方法执行完成,结果是:"+ result);
} catch (Exception e) {
System.out.println(method.getName()+"方法出现异常:"+ e.getMessage());
e.printStackTrace();
}finally {
System.out.println(method.getName()+"方法执行结束了......");
}
return result;
}
};
//JDK中自带的代理类Proxy,需要三个参数 类加载器,被代理对象接口,方法执行器
Object proxy = Proxy.newProxyInstance(classLoader, interfaces, handler);
return (Calculator) proxy;
}
}
–测试结果(用代理可以使得代码更加简单便捷,减少冗余)
AOP底层实现就是基于动态代理的,有俩种实现方式,一种是CGLIB动态代理,另一种就是上面这种,基于JDK基本实现的动态代理。但是JDK中实现动态代理有个缺陷,就是需要代理目标的接口,如果没有就无法创建代理对象。在Spring中就不需要写上面这么麻烦的代码了。只需要利用AOP,就可以实现上述功能。
**AOP的核心概念及术语
- 切面(Aspect): 指关注点模块化,这个关注点可能会横切多个对象。事务管理是企业级Java应用中有关横切关注点的例子。 在Spring AOP中,切面可以使用通用类基于模式的方式(schema-based approach)或者在普通类中以
@Aspect
注解(@AspectJ 注解方式)来实现。 - 连接点(Join point): 在程序执行过程中某个特定的点,例如某个方法调用的时间点或者处理异常的时间点。在Spring AOP中,一个连接点总是代表一个方法的执行。
- 通知(Advice): 在切面的某个特定的连接点上执行的动作。通知有多种类型,包括“around”, “before” and “after”等等。通知的类型将在后面的章节进行讨论。 许多AOP框架,包括Spring在内,都是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。
- 切点(Pointcut): 匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行(例如,当执行某个特定名称的方法时)。切点表达式如何和连接点匹配是AOP的核心:Spring默认使用AspectJ切点语义。
- 引入(Introduction): 声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被通知的对象上。例如,可以使用引入来使bean实现
IsModified
接口, 以便简化缓存机制(在AspectJ社区,引入也被称为内部类型声明(inter))。 - 目标对象(Target object): 被一个或者多个切面所通知的对象。也被称作被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,那么这个对象永远是一个被代理(proxied)的对象。
- AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。
- 织入(Weaving): 把切面连接到其它的应用程序类型或者对象上,并创建一个被被通知的对象的过程。这个过程可以在编译时(例如使用AspectJ编译器)、类加载时或运行时中完成。 Spring和其他纯Java AOP框架一样,是在运行时完成织入的。
AOP的通知类型
- 前置通知(Before advice): 在连接点之前运行但无法阻止执行流程进入连接点的通知(除非它引发异常)。
- 后置返回通知(After returning advice):在连接点正常完成后执行的通知(例如,当方法没有抛出任何异常并正常返回时)。
- 后置异常通知(After throwing advice): 在方法抛出异常退出时执行的通知。
- 后置通知(总会执行)(After (finally) advice): 当连接点退出的时候执行的通知(无论是正常返回还是异常退出)。
- 环绕通知(Around Advice):环绕连接点的通知,例如方法调用。这是最强大的一种通知类型,。环绕通知可以在方法调用前后完成自定义的行为。它可以选择是否继续执行连接点或直接返回自定义的返回值又或抛出异常将执行结束。
–此时进行SpringAOP的简单配置,进行实现刚刚上述的代码,这里你就会发现AOP的巧妙与开发简洁
@Aspect
@Component
public class LogUtil {
/*
* 设置好切面类中的方法在哪里执行,如果很多连接点是相同的,那么也可以设置出公共的@Pointcut
* 使得代码看起来简洁
* 注意点:若是在切面类中加入了形参,就会出现异常信息
* */
@Pointcut("execution(public Integer com.kaisi.service.impl.MyCalculator.*(Integer,Integer))")
public void myPointCut(){}
@Pointcut("execution(* *(..))")
public void myPointCut1(){}
@Before("execution(public Integer com.kaisi.service.impl.MyCalculator.*(Integer,Integer))")
public static void start(){
System.out.println("开始执行,参数是:");
}
@After("execution(public Integer com.kaisi.service.impl.MyCalculator.*(Integer,Integer))")
public static void stop(){
System.out.println("开始结束,结果是:");
}
}
上述代码中就可以体现出@Aspect把整个类当做了切面类。而@Before或@After就是通知了,后面的参数就是对应的切点位置。把额外的功能动态的织入到切点代码之中。下面是结果
这里也有一个注意的点:spring AOP的动态代理方式是jdk自带的方式,容器中保存的代理对象是com.sun.proxy.$Proxy
还有一种动态代理是基于cglib实现的,效果如何实现呢
package com.kaisi.service.impl;
import com.kaisi.service.Calculator;
import org.springframework.stereotype.Service;
@Service
public class MyCalculator2 {
public Integer add(Integer i, Integer j) {
int result = i+j;
return result;
}
public Integer sub(Integer i, Integer j) {
int result = i-j;
return result;
}
public Integer mul(Integer i, Integer j) {
int result = i*j;
return result;
}
public Integer div(Integer i, Integer j) {
int result = i/j;
return result;
}
}
从上述的结果可以看出:在Spirng中,如果是有接口的话,那么会使用jdk自带的动态代理,如果没有接口的话,就会使用cglib的动态代理。动态代理的原理后续的文章会进行讲解