文章目录
一、AOP概述
AOP(Aspect-Oriented Programming,面向切面编程)。
指的是在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运行
这个已经很形象的将AOP的思想解读出来,白话一点,就是在原本的方法执行前后,可以插入新的方法,不破坏原先代码,但是又能更改或者增加我们想要的功能。说到这里,就不得不提java的一个技术:代理机制。
关于代理机制的详解可以看这篇文章。
主要用于:日志记录,性能统计,安全控制,事务处理,异常处理等等。
二、详细解释面向切面的思想
我们假设我们现在有一个计算器的类,他实现了一个接口
public interface Calculate {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
public class MyCalculate implements Calculate {
public int add(int i, int j) {
return i + j;
}
public int sub(int i, int j) {
return i - j;
}
public int mul(int i, int j) {
return i * j;
}
public int div(int i, int j) {
return i / j;
}
}
平常我们在使用的时候,没有什么问题,但有一天有新需求了,比如在加减乘除的时候需要进行日志输出,如果我们直接在MyCalculate类中直接增加日志输出功能,先不说耦合性变差,就是一个一个敲,遇到错误还得一个个的改,这是一件很麻烦的事。拥有人类的天性的我,肯定是不干的。这里就可以请代理兄隆重登场。
public class JDKProxy {
public static MyCalculate getProxy(final Calculate calculate) {
ClassLoader classLoader = calculate.getClass().getClassLoader();
Class[] interfaces = calculate.getClass().getInterfaces();
return (MyCalculate) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法执行前
Object result = method.invoke(calculate, args);
//方法执行后
return result;
}
});
}
}
我们创建JDKProxy后,可以在两个注释那块进行我们所需要的的操作。这不就实现了,不改变原先的代码,但是动态的增加了功能。
三、AOP专业术语
我们就使用上面的Calculate类进行说明。
Calculate共有四个方法,每一个方法都存在方法执行前,方法返回,方法异常,方法结束这四个位置。
我们横向的与这些方法进行交叉,这些点就叫做**横切关注点,使用的方法就称为通知方法,这些通知方法写在一个类中,这个类就叫做切面类**。
单独的每一个方法的每一个位置就叫做**连接点,但是我们真正使用的过程中,可能只需要在其中的一个连接点上面搞事情,那么这个连接点现在就被叫做切入点**。
而**切入点表达式**就是在众多连接点中选出我们搞事情的地方。
四、AOP的简单配置
1.导包
在进行AOP的使用之前,我们需要导入这些包
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.11</version>
</dependency>
</dependencies>
2.配置
<?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
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan base-package="com.shang"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
1).将目标类和切面类导入bean的工作空间
@Service
public class MyCalculate implements Calculate {
public int add(int i, int j) {
return i + j;
}
public int sub(int i, int j) {
return i - j;
}
public int mul(int i, int j) {
return i * j;
}
public int div(int i, int j) {
return i / j;
}
}
@Aspect//标注是切面类
@Component
public class LogUtils {
public static void logBefore() {
System.out.println("方法执行前");
}
public static void logReturn() {
System.out.println("方法返回");
}
public static void logException() {
System.out.println("方法执行异常");
}
public static void logAfter() {
System.out.println("方法执行结束");
}
}
@Aspect切面类的标识,添加这个注解,Spring就会识别当前的这个类就是切面类
2)、切面类的方法,何时何地运行
AspectJ 支持 5 种类型的通知注解:
@Before:前置通知,在方法执行之前执行
@After:后置通知,在方法执行之后执行
@AfterRunning:返回通知,在方法返回结果之后执行
@AfterThrowing:异常通知,在方法抛出异常之后执行
@Around:环绕通知,围绕着方法执行
3)、切入点表达式
execution([权限修饰符] [返回值类型] [简单类名/全类名 ] [方法名] ([参数列表]) )
例如:@Before(“execution(public int com.shang.impl.MyCalculate.*(int, int))”)
.*代表无论名称,只要参数匹配,就都是切入点。
@Aspect
@Component
public class LogUtils {
@Before("execution(public int com.shang.impl.MyCalculate.*(int, int))")
public static void logBefore() {
System.out.println("方法执行前");
}
@AfterReturning("execution(public int com.shang.impl.MyCalculate.*(int, int))")
public static void logReturn() {
System.out.println("方法返回");
}
@AfterThrowing("execution(public int com.shang.impl.MyCalculate.*(int, int))")
public static void logException() {
System.out.println("方法执行异常");
}
@After("execution(public int com.shang.impl.MyCalculate.*(int, int))")
public static void logAfter() {
System.out.println("方法执行结束");
}
}
3、测试
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
@Test
public void test01(){
Calculate calculate = applicationContext.getBean(Calculate.class);
calculate.add(1,1);
}
执行结果
方法执行前
方法执行结束
方法返回
可以看见,切入完成。