AOP-面向切面编程思想
1、图解概述
- 简单来说就是在程序运行过程中执行的一种技术,通过预编译与运行期的动态代理实现程序功能的统一维护的技术
- 动态代理:就是,不修改原码的情况下,对我的目标方法,进行一个增强,并且可以完成程序之间的松耦合
2、AOP的作用和优势
2.1、流程图解
2.2、AOP的思想及底层的实现
- 在Spring中,Spring底层通过动态代理的方式,实现类AOP的思想,通过动态代理,动态的生成代理对象,来完成对功能的一个增强
2.3、AOP动态代理技术
常见的动态代理技术有俩种
-
JDK代理:基于接口的动态代理技术(目标对象必须有接口,没有接口无法生成代理对象)
-
-
cglib代理:基于父类的动态代理技术(第三方动态代理小工具,为目标对象找个子类,这个子类的功能比父类强大)
-
3、jdk动态代理技术代码实现
3.1、分析
1、我们以target接口和他的实现类作为我们的目标对象,内部实现了一个save方法
2、而现在我想要使用AOP的思想对我这个接口实现类的功能进行一个增强,我新建了一个类,内部包含两个增强的方法
可以看出来,这三个方法,目前是没有任何联系的,如何将他们串起来?
3、映射,Proxy.newProxyInterface()
内部需要三个参数
- 目标对象的类加载器–xxx.getClass().getClassLoader()//目标对象类加载器
- 与目标对象相同的接口字节码对象数组–xxx.getClass().getInterfaces();
- 为什么是数组,因为目标对象可能不是一个接口,java是单继承多实现的
- 第三个参数是接口,new InvocationHandler
- 内部包含一个invock方法,其实我们在调用代理对象的时候执行的就是这个invock方法
4、图解
5、实操
在原有基础上创建一个类,老师举的那个匿名内部类看的不是很清晰,实际上,第三个参数实际上是一个接口,该接口有一个方法
调用该接口的时候会执行这个invoke方法,而我们只是使用这个方法进行一个相对应的增强
invoke方法需要的参数,一就是我们创建好的那个Object对象proxy,代理对象,第二个参数就是我们需要被增强方法的那个参数的类,第三个参数是,第二个参数,要执行的方法,所携带的参数,所以他是一个数组类型的
我们使用一个类去实现InvocationHandler的接口,并对他的invoke方法进行一个重写,也就是我们所谓的增强
public class MyInvocationHandler implements InvocationHandler {
// 目标对象
private targetImpl target;
public MyInvocationHandler(targetImpl target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(proxy.getClass().getName());
Object invoke = method.invoke(target, args);
System.out.println("后置增强......");
return invoke;
}
}
我发现一个问题,这个返回的invoke是一个空值,也就是说这个返回的invoke没多大的鸟用,我们只需要执行这个方法就行,方法的组装也是在这里完成的
main方法中的代码,增强后的代码进行一个查看
public static void main(String[] args) {
// 目标方法对象
targetImpl target = new targetImpl();
// 增强目标的方法对象
targetEach targetEach = new targetEach();
com.waves.proxyJDK.target proxy = (com.waves.proxyJDK.target) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MyInvocationHandler(target)
);
// 使用代理对象调用我们的增强方法
proxy.save();
}
6、AOP的思想
就是在不改变原先代码的情况,解耦合,并且实现相对应的功能,可以看到,我在target的实现类中完全没有new 我要增强的对象,他们之间并无关系,但是可以相互组合,要学习这个思想,而不是会用就行
4、cglib的动态代理
这个是基于父类实现的,我这有个方法需要被增强,我这个代理对象,是被增强方法的儿子
4.1、cglib动态代理的步骤
-
创建代理对象Enhancer对象
-
设置我们代理对象的父类
-
-
设置回调函数
-
// 设置回调函数 enhancer.setCallback(new MethodInterceptor() { /** * * @param proxy 生成的代理对象 * @param method 需要实现方法 * @param args 参数集合 * @param methodProxy 这个intercept的代理对象 * @return 鸡巴用 * @throws Throwable */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // 增强 targetEach.befor(); //执行目标 Object invoke = method.invoke(target, args); //后续增强 targetEach.afterAdvice(); return invoke; } });
-
-
开始创建我们的代理对象
-
-
实现方法
5、AOP的正式学习
5.1、AOP的专业术语
1、Target(目标对象)
指代我们需要被代理的对象,由这个对象的代理对象来执行,增强我们这个对象当中需要被增强的方法
2、Proxy:代理
一个类被AOP代理,织入增强后,就会产生一个结果的代理类–Proxy
3、Joinpoint:连接点
其实这里的连接点指的就是我们的方法,这些方法被称之为–连接点,因为Spring只支持方法类型的连接点,因为需要对这些方法,拦截,并且增强他们的功能
4、Pointcut:切入点
切入点就是我们需要对哪些连接点进行拦截,并对其增强;切入点属于连接点的一部分,范围肯定没有连接点大
5、Advice:通知-增强
官方翻译为通知,实际上就是我们对连接点的一个增强
6、Aspect:切面
由切入点和增强的结合,就叫做切面
7、Weaving:织入
指的是把增强的功能应用到目标对象来创建新的代理对象的过程(切点和增强结合到一起的过程),这个过程叫做织入;Spring采用的是动态代理的织入,通过配置进行相应的配置
5.2、AOP开发需要注意的事项
1、需要编写的内容
- 编写业务核心代码(目标类的目标方法)
- 编写切面类,切面类中有增强的方法
- 配置文件当中,配置织入关系,哪些切点和哪些通知需要进行相应的结合
2、AOP技术实现的内容
Spring框架监控切入点的方法的执行,而切点这个东西使我们通过配置文件进行配置的,哪些方法,是切点,当执行,调用这个方法时,说明切面类的某个方法被执行了,一执行,Spring就监控到了,监控到了以后,Spring就会启动代理机制,动态的创建你这个切点方法,所在的目标对象的deep对象?
切点就是一堆方法,集合,你在执行这些方法中的某一个方法时,只要一执行,就被Spring监控到了,你这个方法是不是在某个对象中,这个对象就是我们的目标对象,这个Spring就会为当前方法所在的目标对象动态的创建代理对象,是指的这个意思
创建了代理对象之后干嘛呢?他会根据你配置的,增强的类型(前置增强,后置增强,很多种),根据你配置的什么通知,在代理对象对应的位置,对你的目标方法,进行一个相应的增强方法的介入
3、AOP底层到底是使用哪种代理方式的?
jdk和cglib
在Spring中,框架会根据类是否实现了接口来决定采用哪种动态代理的方式
4、知识要点
-
aop:面向切面编程
-
aop底层实现:基于JDK和Cglib的动态代理
-
aop的重点概念:
- Pointcut:切点:被增强的方法
- Advice:增强:封装增强业务逻辑的方法
- Aspect:切面:切点+通知
- Weaving:织入:将切点和增强结合的这一过程
-
开发明确事项:
- 谁是切点:要被增强的方法,切点表达式配置
- 谁是通知:切面类中的增强方法
- 将切点和通知进行织入的配置
6、基于XML方式进行AOP的开发
6.1、快速入门
图解
1、导入AOP的相关坐标
其实Spring内部包含了AOP的接口,在Spring-context中,但是光有这个还不行,因为实际上这个时候底层实现的代理是Aspect的方式进行的,所以还是要导入相关的坐标。
导入aspectjweaver,这个第三方的资源比spring更轻量级,并且官方文档也注明去使用aspectjweaver来进行AOP的相关操作
<!-- 导入aspectjweaver配置 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9</version>
</dependency>
2、创建目标接口和目标类(内部有切点)
3、创建切面类
// 切面类
public class MyAspect {
// 内含一些增强方法
public void befor(){
System.out.println("前置增强.....");
}
public void afterfor(){
System.out.println("后置增强.....");
}
}
4、将目标类和切面类的对象创建交给Spring
为了更好的控制这些对象,将控制权限交给Spring容器
<!-- 目标对象 -->
<bean id="target" class="com.waves.aop.target.impl.targetImpl"></bean>
<!-- 切面类 -->
<bean id="ascept" class="com.waves.aop.aspect.MyAspect"></bean>
5、在applicationContext.xml中配置织入关系
告诉Spring容器是那些切点,哪些目标方法,需要被增强,被增强的是哪一种?
-
引入aop命名空间
-
-
告诉Spring,MyAspect是一个切面,通过配置告诉他
- 因为现在Spring还不知道他是切面,目前而言他只是一个普通的bean,只是我们赋予了他这个切面的意义
-
<aop:config> <!-- 告诉Spring这是一个切面 --> <aop:aspect ref="aspect"></aop:aspect> </aop:config>
-
配置切面—切点+通知(增强)
- 在内部配置你需要增强的类型,然后指定是哪个方法需要被增强
-
<aop:config> <!-- 告诉Spring这是一个切面 --> <aop:aspect ref="aspect"> <!-- 什么是切面?切点+通知 --> <!-- 前置增强,method:哪些方法是前置增强,增强的是哪些方法? --> <aop:before method="befor" pointcut="execution(public void com.waves.aop.target.impl.targetImpl.save())"/> <!-- 上面配置的是一个切点,内部是一个切点表达式 --> </aop:aspect> </aop:config>
-
目光汇聚在内部,aop:before,是我的一个增强类型,内部装载的是我切面的一个方法-befor(),这个方法用来进行前置增强,后面配置一个切点表达式,也就是说,当我访问这个接口的实现类的save方法的时候,进行一个前置增强,增强的方法为befor()
6、测试代码
总结,通过AOP这种方式我们实现了解耦合,想要就要不想要就拆掉
6.2、切点表达式的方式
<aop:before method="befor"
pointcut="execution(public void com.waves.aop.target.impl.targetImpl.save())"/>
1、aop:before:这是一种增强类型,在目标对象方法被增强前执行,配置好的增强方法
2、pointcut:切点表达式
其实上面看的出来,我们实际上就是对接口中的save方法进行一个增强,但是,只对save()方法进行增强显然不合适,将来可能有一对方法,来让其进行修饰
表达式的语法
注意事项
- 访问修饰符可以省略
- 返回值的类型、包名、类名、方法名可以使用*号代表任意
- 包名和类名之间的一个**.**代表当前包下的类,两个…代表当前包及其子包下的类
- 参数列表**(save(…))**,可以使用两个点,代表任意个数、任意类型的参数
3、直接看老子的切点配置精华图
6.3、通知的种类
除了前置通知还有哪些通知?
1、环绕通知–增强
一般前后置方法执行的效果和环绕通知差求不多,所以采用环绕会稍微好点
-
设计方法
-
// Proceeding JoinPoint 正在执行 的切点 public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕增强(前置增强)......."); Object proceed = pjp.proceed(); System.out.println("环绕增强(后置增强)......."); return proceed; }
-
参数详解
- ProceedingJoinPoint–正在执行的切入点
- 通过这个参数的proceed来执行我们目标对象的方法
- 有可能这个方法会有返回值,如果所以我们要返回一下
-
配置文件配置
-
返回类型任意,com.waves.aop.target包及其子包(impl)的所有方法被增强,参数类型任意,个数任意
-
<aop:around method="around" pointcut="execution(*com.waves.aop.target..*.*(..))"/>
2、异常抛出增强
-
方法设计
-
抛出异常执行这个方法
-
// 异常抛出通知 public void afterThrowing(){ System.out.println("异常抛出异常....."); }
-
-
配置设计
-
<aop:after-throwing method="afterThrowing" pointcut="execution(* com.waves.aop.target..*.*(..))"/>
-
-
设计一个自杀式异常(save方法)
-
打印完save running…之后抛出一个除零异常
-
@Override public void save() { System.out.println("save running......"); int i = 1/0; }
-
-
测试–效果展示
-
3、最终增强
不管有没得异常,老子必须执行
-
设计方法
-
不管有没得异常,老子必须执行
-
// 最终异常 public void after(){ System.out.println("最终增强......."); }
-
-
配置文件配置
-
<aop:after method="after" pointcut="execution(* com.waves.aop.target..*.*(..))"/>
-
-
测试–效果
- 依旧是除0异常
6.4、切点表达式的抽取
当多个切点表达式的作用域相同的时候,可以使用pointcut-ref属性来代替pointcut
-
我这三个增强的切点表达式是不是都一样
-
<aop:around method="around" pointcut="execution(* com.waves.aop.target..*.*(..))"/> <!-- 异常抛出异常 --> <aop:after-throwing method="afterThrowing" pointcut="execution(* com.waves.aop.target..*.*(..))"/> <aop:after method="after" pointcut="execution(* com.waves.aop.target..*.*(..))"/>
-
1、使用pointcut-ref代替
-
规划我们的切点表达式抽
-
<!-- 规划我们的切点表达式抽取 --> <aop:pointcut id="mypointcut" expression="execution(* com.waves.aop.target..*.*(..))" /> <!-- 环绕增强 --> <aop:around method="around" pointcut-ref="mypointcut"/> <!-- 异常抛出增强 --> <aop:after-throwing method="afterThrowing" pointcut-ref="mypointcut"/> <!-- 最终增强 --> <aop:after method="afterThrowing" pointcut-ref="mypointcut"/>
-
2、最终效果
6.5、知识要点
7、基于注解的AOP开发
7.1、图解
7.2、快速入门
1、创建目标接口和目标类(内部有切点)
2、创建切面类(内部有增强方法)
3、将目标类和切面类的对象权交给Spring
4、在切面类中使用注解织入关系
- 配置织入的关系,使用注解之后,我们只需要配置切点表达式即可
- 配置切点表达式的目的,让spring知道是哪些切点需要被什么通知进行一个什么样的增强
4.1、注解配置
1、环绕通知注解–@Around(“切点表达式”)
2、异常抛出通知注解–@AfterThrowing
3、最终通知注解–@After
5、在配置文件中开启组件扫描和AOP自动代理
6、测试
新建测试包,读取我们新配置的xml配置文件
7.3、注解配置AOP开发
1、图解
2、切点表达式的抽取
3、实现–@Pointcut(),他必须要在一个空方法上面,因为需要一个宿主
-
内部的参数放我们相同的切点表达式
-
调用抽取的切点表达式的方式
-
直接调用切点表达式的那个方法体
-
对象.切点表达式的方法体–的方式
-