0.引言
复习一下:IoC是用来解决代码耦合度过高所带来的修改难、维护难问题,即将创建对象的职责转移到了外部的IoC容器。我们写的对象都交给IoC容器管理,他们在容器中统称为Bean,需要的时候就从Bean中拿出来使用。
IoC的使用范围很广,本章节介绍一下IoC是如何实现AOP的
1.什么是AOP?为什么要用AOP?
大家都知道面向对象编程 ( OOP ),尤其是使用Java语言的开发者和学习者对于这个概念都很熟悉了。诚然面向对象有很多优势,但他也有着一些缺陷,其中很重要的一点就是:当需要为多个不具有继承关系的对象引入同一个公共行为时(例如日志、安全检测)我们只有在每个对象里单独引用公共行为,产生了大量的重复代码,程序不便于维护。
好像这句话有点抽象,举个简单的例子来说明一下:
我们在开发中很多时候都需要打印日志,比如说一个简单的加减乘除计算器类,就会有四个对应的方法,执行对应方法的前后都需要打印日志,如下:
public int add(int num1, int num2) {
System.out.println("add方法的参数是[" + num1 + " , " + num2 "]");
result = num1 + num2;
System.out.println("add方法的结果是" + result);
return result;
}
如上只是一个 加 方法,减乘除三个方法同样也需要写这样的日志输出,如果有更多的方法就需要写更多的日志输出。
细想一下就很恐怖了,如果以后需要修改计算方法或者日志输出的格式,那不是要修改非常多的类似的代码?实际上这些代码的逻辑都是一样的,那么有没有一种方法可以避免这个问题呢?这就是为什么我们要用到AOP的原因
有一张非常经典的图,很好地解释了AOP横向合并OOP纵向流程的逻辑:
把我们刚刚举例的计算器套入上面,那么这个横向的AOP流程就是日志输出,因为每一个方法都需要输出日志,只是参数不同而已
面向切面编程 ( AOP )关注横向,区别于 OOP 的纵向,是OOP的一种补充。开发者主要关心的是横切逻辑的编写,只需要很少的代码编写确定横切点有哪些,而不需要去为每个横切点添加横切逻辑,不然就是面向对象编程了
2.IoC与AOP的关系
讲了这么多AOP,这根我们的标题IoC有什么关系呢?实际上AOP是基于IoC的,他可以说是IoC在实际应用中的体现。
AOP的核心就是解耦,而解耦就是通过IoC来实现的
具体来说,我们使用代理(IoC容器)去管理我们需要实现AOP的类,在刚刚的例子里这个类是计算器。也就是说我们写输出日志的代码的时候,不可能还去new一个计算器对象然后对他进行操作,还是会产生重复代码,而且你怎么保证用户new对象的时候命名一致呢?这里我们就要用到IoC,反正是IoC容器创建的对象,我不需要关系他内部的逻辑是什么,只需要用get方法去获取这个对象里面的参数就可以了。
如果还理解不了,没关系我们后面写到实现步骤的时候还会说道。
3.利用IoC实现AOP编程
AOP包含了三个重要过程:
1.找到横切点:首要目标确定在程序的哪个位置进行横切逻辑
2.横切逻辑(业务代码):横切逻辑代码,这个就是横切业务代码,与aop无关
3.织入(weaving):将横切逻辑织入到横切点
3.1 找到横切点
还是用计算器的例子,很明显很切点就是执行计算方法的前后输出日志
3.2 横切逻辑(业务代码)
横切逻辑就是输出的这个日志,那么我们怎么利用IoC去编写这个逻辑代码呢?让我们先从计算器类的创建开始
1.首先我们定义一个接口,是基本的加减乘除操作;AOP实现必须定义接口!!!因为横切逻辑是绑定在基于接口的实现类上的
public interface CalAPI {
public int add(int num1, int num2);
public int sub(int num1, int num2);
public int mul(int num1, int num2);
public int div(int num1, int num2);
}
2.然后我们去写一个接口的实现类,非常简单的加减乘除,加上@C0omponent把这个类注入到IoC容器作为Bean来管理
package wy.springboot01.domain;
import org.springframework.stereotype.Component;
import wy.springboot01.Interface.CalAPI;
@Component
public class CalMachine implements CalAPI {
@Override
public int add(int num1, int num2) {
return num1 + num2;
}
@Override
public int sub(int num1, int num2) {
return num1 - num2;
}
@Override
public int mul(int num1, int num2) {
return num1 * num2;
}
@Override
public int div(int num1, int num2) {
return num1 / num2;
}
}
3.在工程的pom.xml里面引入aspect依赖,即切面依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
3.3 织入
1.重点重点:创建一个切面类,切面类也要交给spring管理(即Component),这样通过before和after注解才可以把代码逻辑织入计算器类
@Component
@Aspect //声明它是一个切面对象
public class LoggerAspect {
//在Callculator中所有方法(*)执行之前都去执行beforecal
@Before("execution(public int wy.springboot01.domain.Callculator.*(..))")
public void beforecal(JoinPoint joinPoint) {
//获取方法名
String name = joinPoint.getSignature().getName();
//getArgs获取参数
System.out.println(name + "方法的参数是" + Arrays.toString(joinPoint.getArgs()));
}
//在Callculator中所有方法(*)执行之后都去执行aftercal,且返回值映射到result
@AfterReturning(value = "execution(public int wy.springboot01.domain.Callculator.*(..))", returning = "result")
public void aftercal(JoinPoint joinPoint, Object result){
//获取方法名
String name = joinPoint.getSignature().getName();
//getArgs获取参数
System.out.println(name + "方法的结果是" + result);
}
}
这里就有一些需要注意的点;
a.可以看到切面对象所有方法引入的参数都是JoinPoint(切入点),这个JoinPoint实际上就是利用IoC创建的一个计算器对象,所以我们才可以使用getName、getArgs等方法来获取它的各种属性,方法之前的注解定义了切入点的具体位置(before、after)
b.这里是在类的所有方法前后都要输出日志,如果只是某些方法,可以使用:@Before("execution(public int wy.springboot01.domain.CalMachine.add())")
2.配置IoC,这里我们不使用上一篇文章中的扫包+注解的办法,而是用xml配置,因为这样在实现AOP会比较便利并且更加灵活。
我们这几就创建一个IoC.xml配置文件,加入如下代码,非常简单,只需要引入相关的beans、配置扫包的范围、开启AOP自动代理即可
<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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置扫包范围-->
<context:component-scan base-package="wy.springboot01">
</context:component-scan>
<!--开启AOP自动代理-->
<aop:aspectj-autoproxy>
</aop:aspectj-autoproxy>
</beans>
3.最后就是写一个测试代码,我们依然叫做IoCTest,如下(注意这里创建context的方法和上一篇文章中不一样,我们调用的是xml的配置):
public class IoCTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("IoC.xml");
//context.getBean(User.class)利用IoC生成对象
CalMachine calMachine = context.getBean(CalMachine.class);
calMachine.add(2,3);
calMachine.sub(2,3);
calMachine.mul(2,3);
calMachine.div(2,3);
}
}
可以看到我们还顺便写了计算器四个方法的测试代码,看下会不会有日志输出
4.测试,结果如下,可以看到日志都输出了:
我们今天的例子就到这里了,是一个非常基础但是使用的IoC实现AOP的逻辑,顺便也介绍了一些IoC的xml实现方法,后续会继续更新IoC这个spring中非常重要的核心概念的其他使用操作