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 实现用户接口```javapublic class UserDaoImpl implements UserDao {@Overridepublic int add(int a, int b) {System.out.println("add...执行了");return a + b;}@Overridepublic 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() {// 每次执行方法前后调用提示内容@Overridepublic 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;@Componentpublic class UserImpl implements User {@Overridepublic 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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://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 ...addafterReturning add ...nullafter 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 ...addafterReturning add ...nullafter 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 测试```javaimport 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;@Componentpublic class Boy implements IBuy {@Overridepublic 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;@Componentpublic class Girl implements IBuy {@Overridepublic 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@Componentpublic 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元,赠送一双袜子男孩买到了:游戏机女孩买到了:衣服和袜子
