一. AOP 概述:
- AOP(Aspect Oriented Programming 面向切面编程) : AOP 是对面向对象编程的一个补充, 在运行时,动态的将代码切入到类的指定方法,指定位置上的编程思想就是面向切面编程。将不同方法的同一位置抽象成一个切面对象,对该切面对象进行编程就是 AOP 。
- 优点:1>. 降低模块之间的耦合度
2>. 使系统容易扩展
3>. 更好的代码复用
4>. 非业务代码更加集中, 不分散,便于统一管理
5>. 业务代码更简洁纯粹,没有其他代码的影响
6>. 将复杂的需求分解除不同的方面,将散布在系统中的公共功能集中解决
如何使用
- 创建Maven ,pom.xml 添加(如果还未下载 Maven 相关配置:请跳转至: Spring IoC 全套资源!!,里面含有Maven框架的下载以及配置)
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.0.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.1.11</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.12</version>
</dependency>
</dependencies>
- 创建一个计算器接口 Cal,定义四个方法:
public interface Cal {
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);
}
- 创建接口的实现类:
public class Calimpl implements Cal {
@Override
public int add(int num1, int num2) {
System.out.println("add 方法的参数是[" + num1 + ", " + num2 + "]");
int res = num1 + num2;
System.out.println("add 方法的结果是:" + res);
return res;
}
@Override
public int sub(int num1, int num2) {
System.out.println("sub 方法的参数是[" + num1 + ", " + num2 + "]");
int res = num1 - num2;
System.out.println("sub 方法的结果是:" + res);
return res;
}
@Override
public int mul(int num1, int num2) {
System.out.println("mul 方法的参数是[" + num1 + ", " + num2 + "]");
int res = num1 * num2;
System.out.println("mul 方法的结果是:" + res);
return res;
}
@Override
public int div(int num1, int num2) {
System.out.println("div 方法的参数是[" + num1 + ", " + num2 + "]");
int res = num1 / num2;
System.out.println("div 方法的结果是:" + res);
return res;
}
}
在上述代码中我们可以看到,在计算过程中都用到了打印参数和结果的语句,因此日志信息和业务逻辑的耦合度太高,不利于系统的维护,使用 AOP 可以进行优化,如何实现 AOP 呢? 使用动态代理的方式来实现。
给业务代码找一个代理, 打印日志信息的工作交给代理来做,这样的话业务代码就只需要关注自身的业务即可:
public class MyInvocationHandler implements InvocationHandler {
// 接受委托对象
private Object object = null;
// 返回代理对象
public Object bind(Object object){
this.object = object;
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "方法的参数是: " + Arrays.toString(args));
Object res = method.invoke(this.object, args);
System.out.println(method.getName() + "方法的结果是:" + res);
return res;
}
}
以上是通过动态代理实现 AOP 的过程,比较复杂,不好理解,Spring 框架对 AOP 进行了封装,使用了 Spring
框架可以用面向对象的思想来实现 AOP 。 Spring 框架中不需要创建 InvocationHander, 只需要创建一个切面对象,
将所有的非业务代码在切面对象中完成即可,Spring 框架底层会自动根据切面类以及目标类生成一个代理对象。
LoggerAspect
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect // 使类成为一个切面对象
@Component // 让IoC管理类
public class LoggerAspect {
@Before("execution(public int com.spring.utils.impl.Calimpl.*(..))")
public void before(JoinPoint joinPoint){
// 获取方法名
String name = joinPoint.getSignature().getName();
// 获取参数
String args = Arrays.toString(joinPoint.getArgs());
System.out.println(name + " 方法的参数是: " + args);
}
@After(value = "execution(public int com.spring.utils.impl.Calimpl.*(..))")
public void afterRuning(JoinPoint joinPoint){
// 获取方法名
String name = joinPoint.getSignature().getName();
System.out.println(name + " 方法执行完毕!");
}
@AfterReturning(value = "execution(public int com.spring.utils.impl.Calimpl.*(..))", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result){
// 获取方法名
String name = joinPoint.getSignature().getName();
System.out.println(name + " 方法执行的结果是:" + result);
}
@AfterThrowing(value = "execution(public int com.spring.utils.impl.Calimpl.*(..))", throwing = "exception")
public void afterThrowing(JoinPoint joinPoint, Exception exception){
// 获取方法名
String name = joinPoint.getSignature().getName();
System.out.println(name + " 方法抛出的异常是:" + exception);
}
}
LoggerAspect 类定义处添加的两个注解:
@Aspect
: 表示该类是切面类;@Component
:将该类的对象注入到 IoC 容器中。 用于标注在类上,表明该类是一个Spring组件(Bean),这样Spring容器就会自动扫描到这些类,并将其实例化、组装,然后管理起来。@Component注解本身并没有特别的业务含义,它仅仅是一个标记,告诉Spring这是一个需要被管理的Bean。- 具体方法处添加的注解:
@Before
:表示方法助兴的具体位置和时机。 Calimpel 也需要添加@Component
,交给IoC 容器来管理。
import com.spring.utils.Cal;
import org.springframework.stereotype.Component;
@Component("test2")
public class Calimpl implements Cal {
@Override
public int add(int num1, int num2) {
int res = num1 + num2;
return res;
}
@Override
public int sub(int num1, int num2) {
int res = num1 - num2;
return res;
}
@Override
public int mul(int num1, int num2) {
int res = num1 * num2;
return res;
}
@Override
public int div(int num1, int num2) {
int res = num1 / num2;
return res;
}
}
Spring.xml 中配置 AOP:
<?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: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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 自动扫描 -->
<context:component-scan base-package="com.spring"/>
<!-- 使Aspect注解生效,为目标类自动生成代理对象 -->
<aop:aspectj-autoproxy/>
</beans>
获取并执行:
import com.spring.utils.Cal;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test2 {
public static void main(String[] args) {
// 加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("Spring-AOP.xml");
// 获取代理对象
Cal proxy = (Cal) applicationContext.getBean("test2");
proxy.add(1, 1);
proxy.sub(2, 1);
proxy.mul(3, 2);
proxy.div(6, 0);
}
}
注意:在执行的时候applicationContext
的 getBean
方法获取对象的名字是通过在 Calimpl
类中添加注解 @Component("test2")
实现的,如果只添加 @Component
不添加名称,则默认是 Calimpl
类的名称首字母小写,即 calimpl
。这里的获取方法相当于 IoC 在spring.xml 中获取 id 为test2
一样,只不过在 Spring.xml 中配置是手动添加参数,而使用 @Component
注解之后 Spring 会自动将其处理。 如:
context:component-scan
:将com.spring
包中的所有类进行扫描,如果该类同时添加了@Component
,则将该类扫描到 IoC 容器中,即 IoC 管理它的对象。aop:aspectj-autoproxy
:让Spring 框架结合切面类和目标类自动生成动态代理对象。
- 切面:横切关注点被模块化的抽象对象。
- 通知:切面对象完成的工作。(如上
LoggerAspect
中的函数)- 目标:被通知的对象,即被混合之后的对象。
- 代理:切面,通知,目标混合之后的对象。
- 连接点:通知要插入业务代码的具体位置。(
LoggerAspect
中的JoinPoint joinPoint
)- 切点:AOP 通过切点定位到连接点。