1.SpringAOP
1.1 AOP介绍
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP主要利用动态代理模式,降低程序的耦合度,拓展业务功能的方法
1.2 名词解释
1).连接点: 用户可以被扩展的方法 joinPoint
2).切入点: 用户实际扩展的方法(判断方法能否进入切面) pointcut 切入点表达式
3).通知: 扩展方法的具体实现 @before…
4).切面: 将通知应用到切入点的过程 方法功能得到扩展全部配置(切面=切入点表达式+通知方法)
(老王饿了去吃饭,路上被雷劈到了天庭,吃了饭吃了蟠桃,人参果,唐僧。)
(整个全过程=切面,连接点=eat()/JoinPoint,切入点=被雷劈/pointcut,通知=进入天庭开始吃)
1.3 通知类型
1. @before: 在目标方法执行之前执行
2.@afterReturning: 在目标方法执行之后返回时执行
3.@afterThrowing: 在目标方法执行之后,抛出异常时执行
4.@after: 无论程序是否执行成功,都要最后执行的通知
5.@around: 在目标方法执行前后 都要执行的通知(完美体现了动态代理模式),功能最为强大 只有环绕通知可以控制目标方法的执行
关于通知方法总结:
1.环绕通知是处理业务的首选. 可以修改程序的执行轨迹(控制目标方法的执行)
2.另外的四大通知一般用来做程序的监控.(监控系统) 只做记录
1.4 切入点表达式
概念:当程序满足切入点表达式,才能进入切面,执行通知方法.
1.bean(“bean的ID”) 根据beanId进行拦截 只能匹配一个
2.within(“包名.类名”) 可以使用通配符 ***** 能匹配多个.
粒度: 上述的切入点表达式 粒度是类级别的. 粗粒度.
3.execution(返回值类型 包名.类名.方法名(参数列表…))
粒度: 控制的是方法参数级别. 所以粒度较细. 最常用的.
4.@annotation(包名.注解名) 只拦截注解.
粒度: 注解是一种标记 根据规则标识某个方法/属性/类 细粒度
1.5 AOP入门案例
1.5.1 导入jar包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring_demo_9_aop</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--Spring核心包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.6</version>
</dependency>
<!--引入SpringBean-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.6</version>
</dependency>
<!--引入context包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.6</version>
</dependency>
<!--引入表达式jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.6</version>
</dependency>
<!--引入日志依赖-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!--引入测试包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--引入AOP包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.6</version>
</dependency>
</dependencies>
</project>
1.5.2 编写UserService/userServiceImpl类
package com.jt.vo.aop;
public interface UserService {
void addUser();
void deleteUser();
}
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("新增用户");
}
@Override
@Cath // 被注解标识
public void deleteUser() {
System.out.println("删除用户");
}
}
1.5.3 创建注解类
package com.jt.vo.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) //控制注解的生命周期
//注解的作用对象, 方法有效 类有效 Type
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD})
public @interface Cath {
}
1.5.4 配置切面类
切面 = 切入点表达式 + 通知方法
package com.jt.vo.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
//1.AOP需要被Spring容器管理
@Component
//2.标识该类为AOP切面
// Spring容器默认不能识别切面注解,需要手动配置
@Aspect
public class SpringAOP {
/**
* 切入点表达式练习
* within:
* 1.within(com.jt.*.DeptServiceImpl) 一级包下的类
* 2.within(com.jt..*.DeptServiceImpl) ..代表多级包下的类
* 3.within(com.jt..*) 包下的所有的类
*
* execution(返回值类型 包名.类名.方法名(参数列表))
* 1.execution(* com.jt..*.DeptServiceImpl.add*())
* 注释: 返回值类型任意的, com.jt下的所有包中的DeptServiceImpl的类
* 的add开头的方法 ,并且没有参数.
*
* 2.execution(* com.jt..*.*(..))
* 注释: 返回值类型任意,com.jt包下的所有包的所有类的所有方法 任意参数.
*
* 3.execution(int com.jt..*.*(int))
* 4.execution(Integer com.jt..*.*(Integer))
* 强调: 在Spring表达式中没有自动拆装箱功能! 注意参数类型
*
* @annotation(包名.注解名)
* @Before("@annotation(com.jt.anno.Cache)")
* 只拦截特定注解的内容.
*/
// 定义切入点表达式
@Pointcut("@annotation(com.jt.vo.aop.Cath)")
public void pointcut() {
}
//1.定义before通知
// @Before("bean(userServiceImpl)")
//@Before("within(com.jt..*)")
//@Before("execution(* com.jt..*.userServiceImpl.add*())")
@Before("pointcut()")
public void before() {
System.out.println("我是userServiceImpl的前置通知");
}
@AfterReturning("pointcut()")
public void AfterReturning() {
System.out.println("我是userServiceImpl的前置通知");
}
@AfterThrowing("pointcut()")
public void afterThrowing(){
System.out.println("我是userServiceImpl异常通知,不报错我不会出来");
}
@After("pointcut()")
public void after(){
System.out.println("我是userServiceImpl后置通知");
}
//
// @Around("pointcut()")
// public void around(){
// System.out.println("我是userServiceImpl环绕通知");
// }
}
1.5.4 编辑配置类
package com.jt.vo;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.jt.vo")
@EnableAspectJAutoProxy(proxyTargetClass=false) //启动AOP注解 创建代理对象
//默认启用JDK动态代理,
//目标对象没有实现接口时,则采用CGLIB
//强制使用cglib proxyTargetClass=true
//JDK代理创建速度快.运行时稍慢
//CGLIB创建时速度较慢,运行时更快
public class SpringConfig {
}
1.5.5编写测试类
package com.jt.vo.aop;
import com.jt.vo.SpringConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestUser {
@Test
public void testUserService(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService= (UserService) context.getBean(UserService.class);
userService.addUser();
userService.deleteUser();
}
}
1.5.6 测试结果
1.6.1 @Before作用
说明: 前置通知,在目标方法执行之前执行
用途: 如果需要记录程序在方法执行前的状态,则使用前置通知.
需求: 1.获取目标对象的类型
2.获取目标方法的名称
3.获取目标方法的参数
/*Spring为了AOP动态获取目标对象及方法中的数据,则通过joinPoint对象
* 进行数据的传递.
* getSignature : 方法签名 获取方法的参数
* */
/*Spring为了AOP动态获取目标对象及方法中的数据,则通过joinPoint对象
* 进行数据的传递.
* getSignature : 方法签名 获取方法的参数
* */
@Before("pointcut()")
public void before(JoinPoint joinPoint){
System.out.println("获取目标对象的类型:"+joinPoint.getTarget().getClass());
System.out.println("获取目标对象类名:"+joinPoint.getSignature().getDeclaringTypeName());
System.out.println("获取目标对象方法名:"+joinPoint.getSignature().getName());
System.out.println("获取方法参数:"+ Arrays.toString(joinPoint.getArgs()));
System.out.println("我是before通知");
}
测试结果:
1.6.2 @AfterReturning
说明: afterReturning,在目标方法执行之之后执行
用途: 用来监控方法的返回值,进行日志的记录
1).在接口中添加测试方法 String after(Integer id)
package com.jt.vo.aop;
public interface UserService {
// aop中的测试方法
String after(Integer id);
2).实现目标方法 添加注解类 将其交由AOP切面
package com.jt.vo.aop;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Override
@Cath
public String after(Integer id) {
return "spring通知的测试" ;
}
}
3).编辑AOP方法测试
package com.jt.vo.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
//1.AOP需要被Spring容器管理
@Component
//2.标识该类为AOP切面
// Spring容器默认不能识别切面注解,需要手动配置
@Aspect
public class SpringAOP {
// 定义切入点表达式
@Pointcut("@annotation(com.jt.vo.aop.Cath)")
public void pointcut() {
}
@AfterReturning(pointcut = "pointcut()",returning = "result")
public void AfterReturning(JoinPoint joinPoint,Object result) {
System.out.println("我是userServiceImpl的运行通知"+joinPoint.getArgs());
System.out.println("用户返回值的结果"+result);
}
4)编辑测试类
package com.jt.vo.aop;
import com.jt.vo.SpringConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestUser {
@Test
public void testUserService2(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService= (UserService) context.getBean(UserService.class);
userService.after(112);
}
5)测试结果
1.6.3 @AfterThrowing
作用: 当目标方法执行时,抛出异常时 可以使用AfterThrowing 进行记录.
1).编辑业务接口
2).编辑业务实现类
3)编辑异常通知类型
4)编辑测试类
5)查看结果
1.6.4 @Around
1.6.4.1 @Around作用
规则: 在目标方法执行前后都要执行
实际作用: 可以控制目标方法是否执行.
1.6.4.2 环绕通知学习
/**
* 关于环绕通知的说明
* 作用: 可以控制目标方法是否执行.
* 参数: ProceedingJoinPoint 通过proceed方法控制目标方法执行.
* 注意事项:
* ProceedingJoinPoint is only supported for around advice
* @return
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint){
Object result = null;
try {
System.out.println("环绕通知开始");
//1.执行下一个通知 2.执行目标方法 3.接收返回值
Long start = System.currentTimeMillis();
result = joinPoint.proceed();
Long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-start));
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("环绕通知结束");
return result;
}
1.7 关于知识点讲解
1.7.1 pointcut
说明:判断方法能否进入切面 IF判断
注解说明: @Pointcut 内部编辑切入点表达式
判断依据:
1.7.2. joinPoint(连接点)
说明: 当用户执行方法时,如果方法满足pointcut表达式,则该方法就是连接点.
如果通知中需要获取方法相关参数 则Spring会将joinPoint对象进行封装.为通知参数进行赋值
通俗理解一点就是执行的原方法中途进入切面后改名并赋值 Joinpoint() = addUser()
1.8 关于通知方法的执行顺序
1.执行around开始
2.执行before
3.执行目标方法
4.执行afterReturning
5.执行afterThrowing
6.执行after
7,执行around通知结束
1.9 关于Order注解说明
说明: 研究如果是多个切面,如何控制切面的执行的顺序.
方法: 通过Order注解实现.