一、概述
1、什么是AOP
AOP(Aspect Oriented Programming):即面向切面编程,它是面向对象编程(OOP)的一种补充,可以在程序在运行期间动态的将某段代码切入到指定方法指定位置进行运行的操作。如:性能监控、日志记录、事务管理、权限控制等,通过AOP解决代码耦合问题,不但提高了开发的效率,而且增强了代码的可维护性。
二、Aop术语
1、Aspect(切面):
在实际应用中,切面通常是指封装的用于横向插入系统功能(如事务、日志、安全等)的类
2、Joinpoint(连接点):
连接点的最小单位称之为方法,每一个方法称之为连接点,如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。在Spring Aop 中,连接点就是指方法的调用。
3、Pointcut(切入点):
切点就是定义了通知被应用的位置 (配合通知的方位信息,可以确定具体连接点)
4、Advice(通知):
AOP框架在特定的切入点执行的增强处理,即在定义好的切入点所要执行的程序代码。可以将其理解为切面类中的方法,它是切面的具体实现,Spring切面可以应用5种类型的通知:
前置通知(Before): 在目标方法执行前实施增强,可以应用于权限管理等功能。
后置通知 (After): 在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能。
返回通知 (After-returning): 在目标方法成功执行之后调用通知。
异常通知 (After-throwing): 在目标方法抛出异常后调用通知,可以应用于处理异常记录日志等功能。
环绕通知(Around): 在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。
5、目标对象(Target):
指的是被增强的对象,也就是被通知的对象,也就是真正的业务逻辑。
6、代理(Proxy):
将通知应用到目标对象之后,被动态创建的对象
7、织入(Weaving):
将切面代码插入到目标对象上,从而生成代理对象的过程
三、Spring实现AOP
1、 Spring API 实现
(1)、导入依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.3.RELEASE</version>
</dependency>
<!-- -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
</dependencies>
(2)编写业务层接口和实现类
public interface UserService {
public void search();
}
public class UserServiceImpl implements UserService {
public void search() {
System.out.println("进行查询用户");
}
}
(3) 编写增强类 前置和后置
//前置
public class BeforeLog implements MethodBeforeAdvice {
//method : 要执行的目标对象的方法 //objects : 被调用的方法的参数 //Object : 目标对象
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
}
}
(4)在resources目录下新建application.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<!--注册bean-->
<!-- 1 目标类-->
<bean id="userService" class="com.service.impl.UserServiceImpl"/>
<!-- 2 切面-->
<bean id="beforeLog" class="com.aspect.BeforeLog"/>
<bean id="afterLog" class="com.aspect.AfterLog"/>
<!-- 3 aop的配置 -->
<aop:config>
<!-- 3.1 配置切入点,通知最后增强那些方法 -->
<aop:pointcut id="pointcut" expression="execution(* com.service.impl.UserServiceImpl.*(..))" />
<!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
(5)测试
import com.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserService userService = (UserService) context.getBean("userService");
userService.search();
}
}
//com.service.impl.UserServiceImpl的search方法被执行了
//进行查询用户
//执行了com.service.impl.UserServiceImpl的search方法,返回值:null
2、自定义类来实现Aop
(1)目标业务类不变依旧是userServiceImpl
(2)自定义切面类
package com.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
//前置通知
public void before(JoinPoint joinPoint){
System.out.println("前置通知:模拟执行权限检查....");
System.out.println("目标类是:"+joinPoint.getTarget());
System.out.println("被植入增强处理的目标方法为:"+joinPoint.getSignature().getName()+"\n");
}
//后置通知
public void after(JoinPoint joinPoint){
System.out.println("后置通知:模拟记录日志....");
System.out.println("被植入增强处理的目标方法为:"+joinPoint.getSignature().getName()+"\n");
}
/**
* 环绕通知
* ProceedingJoinPoint 是joinPoint子接口
* 必须是Object类型的返回值
* 必须接受一个参数 类型为 ProceedingJoinPoint
* @return
*/
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
//开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务。。");
//执行当前目标方法
Object obj =proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务"+"\n");
return obj;
}
//异常通知
public void afterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知"+"出错了"+e.getMessage()+"\n");
}
//最终通知
public void end(){
System.out.println("最终通知:"+"模拟方法结束后的释放资源...");
}
}
(3)去application.xml配置
<!-- 自定义 注册bean-->
<bean id="userService" class="com.service.impl.UserServiceImpl"/>
<!-- 1 目标类-->
<bean id="myAspect" class="com.aspect.MyAspect"/>
<!-- 3 aop的配置 -->
<aop:config>
<!-- 配置切面-->
<aop:aspect id="aspect" ref="myAspect">
<!-- 配置切入点-->
<aop:pointcut id="aspectPointcut" expression="execution(* com.service.impl.UserServiceImpl.*(..))" />
<!-- 配置通知-->
<!-- 前置通知-->
<aop:before method="before" pointcut-ref="aspectPointcut"/>
<!--后置通知 在方法返回后执行,可以获得返回值-->
<aop:after-returning method="after" pointcut-ref="aspectPointcut" />
<!--环绕通知-->
<aop:around method="around" pointcut-ref="aspectPointcut"/>
<!--异常通知 如果程序没有异常,将不会执行增强-->
<aop:after-throwing method="afterThrowing" pointcut-ref="aspectPointcut" throwing="e"/>
<!--最终通知 无论程序发生什么都会执行-->
<aop:after method="end" pointcut-ref="aspectPointcut"/>
</aop:aspect>
</aop:config>
(4)测试
import com.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserService userService = (UserService) context.getBean("userService");
userService.search();
}
}
前置通知:模拟执行权限检查....目标类是:com.service.impl.UserServiceImpl@75f32542
被织入增强处理的目标方法为:search
环绕开始:执行目标方法之前,模拟开启事务。。
进行查询用户
最终通知:模拟方法结束后的释放资源...
环绕结束:执行目标方法之后,模拟关闭事务
后置通知:模拟记录日志....被织入增强处理的目标方法为:search
3、使用注解实现
(1)编写一个注解实现的增强类
package com.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
//@Component注解表明一个类会作为组件类,并告知Spring要为这个类创建bean。
//定义切面
public class MyAspect2 {
//定义切入点表达式
@Pointcut("execution(* com.service.impl.UserServiceImpl.*(..))")
//使用一个返回值为void 方法体为空的方法来命名切入点
private void myPointcut(){}
//前置通知
@Before("myPointcut()")
public void before(JoinPoint joinPoint){
System.out.print("前置通知:模拟执行权限检查....");
System.out.println("目标类是:"+joinPoint.getTarget());
System.out.println("被织入增强处理的目标方法为:"+joinPoint.getSignature().getName()+"\n");
}
//后置通知
@AfterReturning("myPointcut()")
public void after(JoinPoint joinPoint){
System.out.print("后置通知:模拟记录日志....");
System.out.println("被织入增强处理的目标方法为:"+joinPoint.getSignature().getName()+"\n");
}
/**
* 环绕通知
* ProceedingJoinPoint 是joinPoint子接口
* 必须是Object类型的返回值
* 必须接受一个参数 类型为 ProceedingJoinPoint
* @return
*/
@Around("myPointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
//开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务。。。");
//执行当前目标方法
Object obj =proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务"+"\n");
return obj;
}
//异常通知
@AfterThrowing(value = "myPointcut()",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知"+"出错了"+e.getMessage()+"\n");
}
//最终通知
@After("myPointcut()")
public void end(){
System.out.println("最终通知:"+"模拟方法结束后的释放资源...");
}
}
(2)去application.xml配置
<!--第三种方式:注解实现-->
<bean id="userService" class="com.service.impl.UserServiceImpl"/>
<context:component-scan base-package="com"/>
<aop:aspectj-autoproxy/>
<!-- 有两种代理机制,一种是基于JDK的动态代理,另一种是基于CGLib的动态代理,bean没有接口时使用 CGLib 代理,bean有接口则使用 JDK 代理。 通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面 的bean创建代理,织入切面。当然,spring 在内部依旧采用 AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被 <aop:aspectj-autoproxy />隐藏起来了 <aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态 代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用 CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接 口,则spring将自动使用CGLib动态代理。-->