一、AOP面向切面
1、概念
简单的讲就是: 1、给你的目标类增加功能,就是切面,比如日志,事务 2、他独立于业务逻辑之外,独立使用的,非业务功能
2、怎么理解面向切面
1、分析项目功能时,找出切面 2、合理安排切面的执行时间(在目标方法前,还是后) 3、合理安排切面的位置,在哪个类,哪个方法增加增强功能
3、切面关键三要素
1、切面的功能代码,切面干什么
2、切面执行的位置,使用Pointcut表示切面执行的位置
3、切面执行的时间,使用advice表示时间,在目标方法之前,还是之后执行
4、术语
1、Aspect :切面,表示增强的功能,就是一堆代理 ,完成一个功能,非业务功能,常见的有日志,事务
2、JoinPoint 连接点,连接业务方法和切面的位置,就是某个类中的业务方法
3、Pointcut 切入点,指多个连接点方法的集合,多个方法
4、目标对象:给哪个类的方法增加功能,这个类就是目标对象
5、Advice通知,表示切面功能执行的时间
5、Aop基于动态代理
AOP 是基于动态代理技术的,主要有两种方式 1、JDK动态代理,使用jdk提供的Proxy,method,invocationhandle来创建代理对象, 目标类必须要实现接口 2、CGLIB动态代理,主要原理是通过继承来实现 好处: 1、在不改变目标类源代码情况下,增加概念 2、减少代码的重复 3、专注于业务逻辑代码 4、解耦合,业务功能和日志,事务分离
二、Aop的实现
aop技术实现框架: 1、spring,内部实现了aop规范,
spring主要在事务处理时,使用到了aop,但是spring的aop比较笨重 2、aspectj 一个开源的专门做aop的框架,spring框架中集成了aspectj框架,通过spring就能使用aspectj框架功能 aspectj实现aop的两种方式: (1)使用xml配置 (2)使用注解,aspectj主要有5个注解
三、aspectj框架的使用
1、切面执行的时间,即 Advice ,通知或增强 1)、@Before 2)、@AfterReturning 3)、@Around 4)、@AfterThrowing 5)、@After 2、切面执行的位置,使用切入点表达式,即 : execution(访问权限 方法返回值 方法声明(参数) 异常类型)
注意:表达式中 方法返回值 方法声明(参数)必须要有
四、开发过程
1、步骤 a、创建maven项目 b、加入依赖:spring依赖和aspectj依赖 c、加入juinit单元测试 d、创建目标类:接口和实现类,给他的方法增强功能 e、创建切面类,在类中定义方法,方法就是切面要执行的功能代码, 在方法的上面加入aspectj中的通知注解,例如@Before,有需要指定切入点表达式execution() f、在spring的xml文件中进行配置
创建切面类MyAspect
package com.ba01; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import java.util.Date; /** * 表示当前类为切面类 * 定义在类上面 */ @Aspect public class MyAspect { /** * 定义方法:公共无返回值, */ // @Before(value = "execution(public void com.ba01.SomeServiceImpl.doSome(String,Integer))") @Before(value = "execution(* doSome(..))") public void mybefore(){ System.out.println("前置通知,在目标方法之前输出执行时间:" + new Date()); } }
目标类接口及实现类
package com.ba01; public interface SomeService { public void doSome(String name,Integer age); public void doOther(String name,Integer age); }
package com.ba01; import com.ba01.SomeService; import org.springframework.stereotype.Component; @Component public class SomeServiceImpl implements SomeService{ @Override public void doOther(String name, Integer age) { System.out.println("====doOther方法==========="); } @Override public void doSome(String name, Integer age) { System.out.println("====dosome方法==========="); } }
spring的xml文件配置
<?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"> <!--声明目标对象--> <bean id="someService" class="com.ba01.SomeServiceImpl"/> <!--声明切面类对象--> <bean id="myAspect" class="com.ba01.MyAspect"/> <!--声明自动代理生成器 aspectj-autoproxy:会把spring 容器中所有的目标对象,一次性的生成代理对象 --> <aop:aspectj-autoproxy/> </beans>
测试类泡一下
public class AppTest { @Test public void shouldAnswerWithTrue() { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); SomeService someService = (SomeService) context.getBean("someService"); someService.doOther("jack",20); } }
执行结果
前置通知,在目标方法之前输出执行时间:Mon Sep 26 07:14:25 GMT+08:00 2022 ====dosome方法=========== Process finished with exit code 0
额外小知识点补充: (1)execution表达式的多种写法:
@Before(value = "execution(public void com.ba01.SomeServiceImpl.doSome(String,Integer))")这个为完整写法, 也可以这样 @Before(value = "execution(* doSome(..))"), 或者 @Before(value = "execution(public * doSome(..))") 或者 @Before(value = "execution(* *(..))")都可以的 或者 @Before(value = "execution(void *..SomeServiceImpl.doSome(..))")
(2)执行方法,我们发现程序底层使用的是jdk的动态代理
someService.getClass().getName()//com.sun.proxy.$Proxy11
(3)通过代理生成器,即 aop:aspectj-autoproxy,创建目标对象的代理对象,创建的代理对象是在内存中实现的,修改目标对象在内存中的结构
五、JoinPoint说明
作用是:
JoinPoint: 业务方法,要加入切面功能的业务方法 可以在通知方法中获取方法执行时的信息,例如方法的名称,方法的实参 如果你的切面功能能中需要使用到方法的信息,就加入JoinPoint 这个JoinPoint参数的值是由框架赋予的 JoinPoint在方法参数列表里面,必须位于第一位
@Before(value = "execution(* *(..))") public void mybefore(JoinPoint point){ System.out.println("方法的签名=" +point.getSignature()); System.out.println("方法的名称=" +point.getSignature().getName()); Object[] args = point.getArgs(); for (Object arg : args) { System.out.println("参数=" + arg); } System.out.println("前置通知,在目标方法之前输出执行时间:" + new Date()); }
方法的签名=void com.ba01.SomeService.doSome(String,Integer) 方法的名称=doSome 参数=jack 参数=20 前置通知,在目标方法之前输出执行时间:Tue Sep 27 06:28:05 GMT+08:00 2022 前置通知,在目标方法之前输出执行时间:Tue Sep 27 06:28:05 GMT+08:00 2022 ====dosome方法=========== com.sun.proxy.$Proxy11 Process finished with exit code 0
六、 @AfterReturning后置通知
特点: 1、在目标方法之后执行 2、可以获取到目标方法的返回值,可以根据这个返回值做不同的处理功能 3、可以修改返回值
切面类:
@Aspect public class MyAspect { @AfterReturning(value = "execution(* *(..))",returning = "res") public void myafterReturning(Object res){ //res 就是目标方法的返回值 System.out.println("后置通知:在目标方法之后执行的,获取返回值结果:" + res); } }
测试类:
package com.mr.lee; import com.ba01.SomeService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AppTest { @Test public void shouldAnswerWithTrue() { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); SomeService someService = (SomeService) context.getBean("someService"); someService.doOther("jack",20); System.out.println(someService.getClass().getName());//com.sun.proxy.$Proxy11 } }
执行结果:
====doOther方法=========== 后置通知:在目标方法之后执行的,获取返回值结果:abcd com.sun.proxy.$Proxy11 Process finished with exit code 0
也可以加上joinPoint参数,但是必须位于第一位
@AfterReturning(value = "execution(* *(..))",returning = "res") public void myafterReturning(JoinPoint joinPoint, Object res){ //res 就是目标方法的返回值 System.out.println("后置通知:在目标方法之后执行的,获取返回值结果:" + res); }
七、环绕通知@Around (重点)
格式:
必须有返回值,推荐使用Object 方法名自定义 方法有参数,固定的参数
特点:
1、是功能最强的通知 2、在目标方法前后都能增强功能 3、控制目标方法是否调用执行 4、修改原来目标方法执行的结果
额外补充:
环绕通知 相当于 JDK动态代理的invocationHandler接口 参数 ProceedingJoinPoint ,用来执行目标方法的,相当于JDK动态代理的Method类 环绕通知经常用来做事务的处理 上代码:
package com.ba01; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import java.util.Date; @Aspect public class MyAspect { @Around(value = "execution(* *(..))") public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable { Object object = null; System.out.println("环绕通知,在目标方法之前调用,输出时间," + new Date()); //1、可以控制目标方法的执行 String name = ""; Object[] args = joinPoint.getArgs(); if(args != null && args.length > 1){ name = (String) args[0]; } if(!"zhangshan".equals(name)){ //2、目标方法的调用 object = joinPoint.proceed();//相当于method.invoke(); } System.out.println("环绕通知,在目标方法之后调用,输出时间," + new Date()); //3、可以直接修改方法的返回值 object = "1234werqwe"; return object; } }
测试一下
package com.mr.lee; import com.ba01.SomeService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AppTest { @Test public void shouldAnswerWithTrue() { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); SomeService someService = (SomeService) context.getBean("someService"); String res = someService.doOther("jack",20); System.out.println(res); } }
运行结果:
环绕通知,在目标方法之前调用,输出时间,Tue Sep 27 07:04:47 GMT+08:00 2022 ====doOther方法=========== 环绕通知,在目标方法之后调用,输出时间,Tue Sep 27 07:04:47 GMT+08:00 2022 1234werqwe Process finished with exit code 0
八、aop知识点总结