1 介绍
1.1 什么是AOP
AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
1.2 为什么需要AOP
想象下面的场景,开发中在多个模块间有某段重复的代码,我们通常是怎么处理的?显然,没有人会靠“复制粘贴”吧。在传统的面向过程编程中,我们也会将这段代码,抽象成一个方法,然后在需要的地方分别调用这个方法,这样当这段代码需要修改时,我们只需要改变这个方法就可以了。然而需求总是变化的,有一天,新增了一个需求,需要再多出做修改,我们需要再抽象出一个方法,然后再在需要的地方分别调用这个方法,又或者我们不需要这个方法了,我们还是得删除掉每一处调用该方法的地方。实际上涉及到多个地方具有相同的修改的问题我们都可以通过 AOP 来解决。
2 API说明
2.1 AOP术语
通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
- 连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
- 切点(PointCut): 可以插入增强处理的连接点。
- 切面(Aspect): 切面是通知和切点的结合。
- 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
- 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。
2.2 AspectJ 切点指示器
| AspectJ切点指示器 | 描述 | | —- | —- | | arg() | 限制连接点匹配参数为指定类型的执行方法 | | @args() | 限制连接点匹配参数由指定注解标注的执行方法 | | execution() | 用于匹配是连接点的执行方法 | | this() | 限制连接点匹配AOP代理的Bean引用为指定类型的类 | | target() | 限制连接点匹配目标对象为指定类型的类 | | @target() | 限制连接点匹配特定的执行对象,这些对象对应的类药具备指定类型的注解 | | within() | 限制连接点匹配指定的类型 | | @within() | 限制连接点匹配指定注解锁标注的类型 | | @annotation | 限制匹配带有指定注解连接点 |
2.3 通知类型
注解 | 通知 |
---|---|
@Before | 通知方法会在目标方法调用之前执行 |
@After | 通知方法会在目标方法返回或异常后调用 |
@AfterReturning | 通知方法会在目标方法返回后调用 |
@AfterThrowing | 通知方法会在目标方法抛出异常后调用 |
@Around | 通知方法会将目标方法封装起来 |
3 AOP底层原理
3.1 概述
AOP的底层原理:
}
<a name="du4gy"></a>
### 3.2.1.2 实现用户接口
```java
public class UserDaoImpl implements UserDao {
@Override
public int add(int a, int b) {
System.out.println("add...执行了");
return a + b;
}
@Override
public String update(String id) {
System.out.println("update...执行了");
return id;
}
}
3.2.1.3 JDK动态代理测试
public class SpringTest {
public static void main(String[] args) {
UserDaoImpl userDao = new UserDaoImpl();
// 代理用户类,
UserDao proxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), new InvocationHandler() {
// 每次执行方法前后调用提示内容
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法执行前的提示内容
System.out.println("在" + method.getName() + "之前执行");
// 主方法调用
Object result = method.invoke(userDao, args);
// 方法执行后的提示内容
System.out.println("在" + method.getName() + "之后执行");
return result;
}
});
// 调用代理后的方法
int result = proxy.add(1, 2);
System.out.println("result = " + result);
String id = proxy.update("1");
System.out.println("id = " + id);
}
}
3.2.1.4 结果
在add之前执行
add...执行了
在add之后执行
result = 3
在update之前执行
update...执行了
在update之后执行
id = 1
4 入门案例
4.1 注解方式
4.1.1 依赖引入
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
4.1.2 创建用户的增加方法
package com.hikktn.service;
public interface User {
void add();
}
4.1.3 实现用户的增加方法
package com.hikktn.service.impl;
import com.hikktn.service.User;
import org.springframework.stereotype.Component;
@Component
public class UserImpl implements User {
@Override
public void add() {
System.out.println("add");
}
}
4.1.4 切面类实现
package com.hikktn.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 增强的类
*/
@Component
@Aspect //生成代理对象
@Order(1) //在多个增强类对同一个方法进行增强,设置增强类的优先级,@Order中的value属性值越小优先级越高
public class UserAspect {
/**
* 切入点表达式
*/
@Pointcut("execution(* com.hikktn.service.impl.UserImpl.add(..))")
public void pointcut() {
}
/**
* 前置通知
*/
@Before(value = "pointcut()")
public void beforeAdd() {
System.out.println("before add ...");
}
/**
* 后置通知
*/
@AfterReturning(value = "pointcut()", returning = "obj")
public void afterReturningAdd(Object obj) {
System.out.println("afterReturning add ..." + obj);
}
/**
* 环绕通知
*/
@Around(value = "pointcut()")
public void aroundAdd(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前");
proceedingJoinPoint.proceed();
System.out.println("环绕之后");
}
/**
* 异常通知
*/
@AfterThrowing(value = "pointcut()", throwing = "ex")
public void afterThrowingAdd(Exception ex) {
System.out.println("afterThrowing add ..." + ex);
}
/**
* 最终通知
*/
@After(value = "pointcut()")
public void afterAdd() {
System.out.println("after add ...");
}
}
4.1.5 XML配置
<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.hikktn"></context:component-scan>
<!-- 开启aspectj生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
4.1.6 测试
public class SpringTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean(User.class);
user.add();
}
}
4.1.7 结果
环绕之前
before add ...
add
afterReturning add ...null
after add ...
环绕之后
4.1.8 全注解方式
package com.hikktn.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(value = "com.hikktn")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}
4.1.9 测试
public class SpringTest {
public static void main(String[] args) {
// ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
User user = context.getBean(User.class);
user.add();
}
}
4.1.10 结果
环绕之前
before add ...
add
afterReturning add ...null
after add ...
环绕之后
4.2 XML方式
4.2.1 依赖引入
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
4.2.2 创建用户的增加方法
package com.hikktn.service;
public interface User {
void add();
}
4.2.3 实现用户的增加方法
package com.hikktn.service;
public interface User {
void add();
}
4.2.4 切面类
package com.hikktn.config;
/**
* 增强的类
*/
public class UserAspect {
/**
* 前置通知
*/
public void beforeAdd() {
System.out.println("before add ...");
}
}
4.2.5 XML配置
applicationContext.xml ```xml <?xml version=”1.0” encoding=”UTF-8”?>
<!-- 配置切入点 -->
<aop:pointcut id="pointcut" expression="execution(* com.hikktn.service.User.add(..)))"/>
<!-- 配置切面 -->
<aop:aspect ref="userAspect">
<!-- 配置增强作用在具体的方法上 -->
<aop:before method="beforeAdd" pointcut-ref="pointcut"></aop:before>
</aop:aspect>
<a name="gsFH9"></a>
### 4.2.6 测试
```java
import com.hikktn.service.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean(User.class);
user.add();
}
}
4.2.7 结果
before add ...
add
4.3 注解多参数方式
4.3.1 依赖引入
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
4.3.2 创建购买接口方法
package com.hikktn.service;
public interface IBuy {
String buy(double price);
}
4.3.3 实现购买方法-男孩
package com.hikktn.service.impl;
import com.hikktn.service.IBuy;
import org.springframework.stereotype.Component;
@Component
public class Boy implements IBuy {
@Override
public String buy(double price) {
System.out.println(String.format("男孩花了%s元买了一个游戏机", price));
return "游戏机";
}
}
4.3.4 实现购买方法-女孩
package com.hikktn.service.impl;
import com.hikktn.service.IBuy;
import org.springframework.stereotype.Component;
@Component
public class Girl implements IBuy {
@Override
public String buy(double price) {
System.out.println(String.format("女孩花了%s元买了一件漂亮的衣服", price));
return "衣服";
}
}
4.3.5 切面类
package com.hikktn.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BuyAspectJ {
/**
* 该切面方法只会在Girl类调用起作用
*/
@Before("execution(* com.hikktn.service.IBuy.buy(..)) && within(com.hikktn.service.impl.*) && bean(girl)")
public void hehe(){
System.out.println("女孩超级喜欢的东西");
}
@Pointcut("execution(String com.hikktn.service.IBuy.buy(double)) && args(price) && bean(girl)")
public void gif(double price) {
}
@Around("gif(price)")
public String hehe(ProceedingJoinPoint pj, double price){
try {
pj.proceed();
if (price > 68) {
System.out.println("女孩买衣服超过了68元,赠送一双袜子");
return "衣服和袜子";
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return "衣服";
}
}
4.3.6 配置类
package com.hikktn.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = "com.hikktn")
// 选用CGLib代理
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}
4.3.7 测试
import com.hikktn.config.AppConfig;
import com.hikktn.service.impl.Boy;
import com.hikktn.service.impl.Girl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AppTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Boy boy = context.getBean("boy", Boy.class);
Girl girl = (Girl) context.getBean("girl");
String boyBought = boy.buy(35);
String girlBought = girl.buy(99.8);
System.out.println("男孩买到了:" + boyBought);
System.out.println("女孩买到了:" + girlBought);
}
}
4.3.8 结果
男孩花了35.0元买了一个游戏机
女孩超级喜欢的东西
女孩花了99.8元买了一件漂亮的衣服
女孩买衣服超过了68元,赠送一双袜子
男孩买到了:游戏机
女孩买到了:衣服和袜子