目录
5. 创建spring的配置文件 : 声明对象, 把对象交给容器统一管理
6. 创建测试类, 从spring的容器中获得代理对象(实际上是目标对象)
切面三个关键的要素
1. 切面的功能代码:切面是干什么的
2. 切面的执行时间:使用通知(Advice)表示, 是在目标方法之前还是之后
3. 切面的执行位置:使用切入点(Pointcut)表示切面执行位置
从三要素使用aspectJ框架
1. 切面的功能代码, 这个由开发人员自己定义
2. 切面的执行时间, 在规范中叫Advice(通知, 增强)
注解表示
1. @Before 前置通知
2. @AfterReturning 后置通知
3. @Around 环绕通知
4. @AfterThrowing 异常通知(了解)出现目标方法出现异常时执行
5. @After 最终通知(了解)怎么都执行, 目标方法出现异常也执行
3. 切面的执行位置, 使用切入点表达式
1. AspectJ定义的表达式
原形为:
execution(1.[方法访问限定符] 2.方法返回值类型
3.[方法所在包名和类名]方法名(方法参数内容)
4.[抛出异常信息])
每一个小部分都可以使用通配符
表达式参数由四大部分组成(已经添加序号), 每个部分由空格隔开
[]包裹的代表可以没有
简化execution(访问权限 方法返回值 方法声明(参数) 异常类型)
其中 方法返回值 和 方法声明 是必须的, 实际开发中用这两个部分就足够了
例子:
execution(public void org.example.ba01.SomeService.doSome(String, Integer))
表达式的参数由四大部分组成, 其中第3部分又有3个小部分
每个小部分都可以使用通配符
通配符 意义
----------------------------------------
* 0至多个任意字符
.. 用在方法的参数之中, 表示任意多个参数
用在包名之后, 表示当前包和其子包路径
+ (了解) 用在类名之后, 表示当前类及其子类
用在接口之后, 表示当前接口及其实现类
例如:
execution(public * *(..)) 指定切入点为:任意的公共方法
execution(* set*(..)) 指定切入点为:任何一个以set开头的方法
execution(* com.xyz.service.*.*(..)) 指定切入点为:com.xyz.service中的任意类中的任意方法
2. 使用 @Pointcut 管理切入点
使用aspectj框架实现aop
使用aop:目的是给已经存在的一些类和方法增加额外的功能, 前提是不改变原来的类的代码
使用aspectj的基本步骤
1. 新建maven
2. 加入依赖
1. spring依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
2. aspectj依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
3. junit依赖
3. 创建目标类 : 接口和它的实现类
要做到是给类中的方法增加功能
4. 创建切面类 : 就是一个普通类
1. 在类的上面加入 @Aspect
2. 在类中定义方法表示切面是干啥的(给原来方法新添加的功能)
在方法上加入aspectj中的通知注解, 例如 @Before
还需要指定切入点表达式execution()
5. 创建spring的配置文件 : 声明对象, 把对象交给容器统一管理
1. 声明目标对象
2. 声明切面对象
3. 声明aspectj框架中的自动代理生成器标签
自动代理生成器 : 用来完成代理对象的自动创建功能
6. 创建测试类, 从spring的容器中获得代理对象(实际上是目标对象)
声明自动代理生成器, 是使用aspectj框架内部的功能, 创建目标对象的代理对象
创建代理对象是在内存中完成的, 修改目标对象的内存结构, 创建出代理对象
所以目标对象就是被修改后的代理对象
通过代理执行方法, 实现aop的功能增强
@After前置通知
SomeService接口
package org.example.ba01;
public interface SomeService {
void doSome();
}
接口实现类
package org.example.ba01;
public class SomeServiceImpl implements SomeService{
@Override
public void doSome() {
// 在这里增加一个打印时间的功能
System.out.println("目标方法");
}
}
切面类
package org.example.ba01;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/*
@Aspect
这个注解是aspectj框架中的注解
作用 : 表示当前类是切面类
切面类 : 是用来给业务方法增加功能的类, 这个类中有切面功能代码
位置 : 在类定义的上面
*/
@Aspect
public class MyAspect {
/*
定义切面功能的方法
要求:
1. 方法是公共的方法
2. 方法没有返回值
3. 方法名称自定义
4. 方法可以有参数, 也可以没参数
如果有参数, 参数不是自定义的
*/
/*
@Before : 前置通知注解
属性 : value, value的值为切面表达式
切面表达式中一些部分可以简写
如果根据切面表达式找不到要代理的目标就不会代理
特点 :
1. 在目标方法之前先执行
2. 不会改变目标方法的执行结果
3. 不会影响目标方法的执行
*/
@Before(value = "execution(public void org.example.ba01.SomeService.doSome())")
public void myBefore() {
// 切面功能代码 : 打印时间
System.out.println("切面功能:输出时间" + new Date());
}
}
配置文件
<?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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--把对象交给容器统一管理-->
<!--声明目标对象-->
<bean id="someService" class="org.example.ba01.SomeServiceImpl" />
<!--声明切面类对象-->
<bean id="myAspect" class="org.example.ba01.MyAspect"/>
<!--
声明自动代理生成器, 是使用aspectj框架内部的功能, 创建目标对象的代理对象
创建代理对象是在内存中完成的, 修改目标对象的内存结构, 创建出代理对象
所以目标对象就是被修改后的代理对象
aspectj-autoproxy 它会把spring容器中的所有的目标对象, 一次性都生成代理对象
-->
<!--
扫描配置文件时, 执行到这里还只是由spring创建了对应的对象
执行下面一句时, 会启用aspectj框架中的功能, 扫描当前容器中的对象的类,
判断这些类中是否有相关注解, 找到注解之后根据切入点表达式找到切入点(需要添加功能的方法)
找到切入点后也就意味着找到切入点的类, 之后在根据这个类在容器中找到目标对象
对目标对象进行改造
也就是说, 切面是根据注解找到的, 目标是根据切入点表达式找到的
-->
<aop:aspectj-autoproxy />
</beans>
测试代码
public class MyTest01 {
@Test
public void test01() {
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
SomeService proxy = (SomeService) ac.getBean("someService");
proxy.doSome();
}
}
打印结果如下:
切面功能:输出时间Thu May 20 18:19:49 GMT+08:00 2021
目标方法
JoinPoint作为切面功能方法参数
定义切面功能的方法
要求:
1. 方法是公共的方法
2. 方法没有返回值
3. 方法名称自定义
4. 方法可以有参数, 也可以没参数
如果有参数, 参数不是自定义的
指定通知方法的参数 : JoinPoint
JoinPoint : 代表要加入切面功能的业务方法
作用是 : 可以在切面方法中获取执行方法的信息, 例如方法名称, 实参
如果你的切面功能中需要用到方法的信息, 就加入JoinPoint
这个JoinPoint参数的值是由框架赋予的, 必须是第一个位置的参数
@Aspect
public class MyAspect {
/*
定义切面功能的方法
要求:
1. 方法是公共的方法
2. 方法没有返回值
3. 方法名称自定义
4. 方法可以有参数, 也可以没参数
如果有参数, 参数不是自定义的
指定通知方法的参数 : JoinPoint
JoinPoint : 代表要加入切面功能的业务方法
作用是 : 可以在切面方法中获取执行方法的信息, 例如方法名称, 实参
如果你的切面功能中需要用到方法的信息, 就加入JoinPoint
这个JoinPoint参数的值是由框架赋予的, 必须是第一个位置的参数
*/
@Before(value = "execution(public void org.example.ba01.SomeService.doSome(String, Integer))")
public void myBefore(JoinPoint joinPoint) {
// 获取方法的完整定义
System.out.println("方法的完整签名 = " + joinPoint.getSignature());
System.out.println("方法的名称 = " + joinPoint.getSignature().getName());
// 获取方法的全部参数
Object[] args = joinPoint.getArgs();
for (Object o : args) {
System.out.println("参数 = " + o.toString());
}
// 切面功能代码 : 打印时间
System.out.println("切面功能:输出时间" + new Date());
}
}
打印结果为:
方法的完整签名 = void org.example.ba01.SomeService.doSome(String,Integer)
方法的名称 = doSome
参数 = 张三
参数 = 20
切面功能:输出时间Thu May 20 20:33:00 GMT+08:00 2021
目标方法
@AfterReturning后置通知
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class MyAspect1 {
/*
定义后置通知的方法, 方法是实现切面功能的
方法的定义要求:
1. 公共方法
2. 方法中没有返回值
3. 方法名称自定义
4. 方法中有参数, 推荐是Object, 参数名自定义
*/
/*
@AfterReturning: 后置通知
属性: 1. value 表示切入点表达式
2. returning 自定义的变量, 表示目标方法的返回值
自定义变量名必须和通知方法的形参名一致
位置 : 在方法定义的上面
特点: 1. 在目标方法之后执行
2. 能够获取到目标方法的返回值, 可以根据这个返回值做一些不同的处理功能
3. 可以修改这个返回值
后置通知的执行顺序:
Object res = doSome1();
这个是值传递或者引用传递的
myAfterReturning(res);
所以在myAfterReturning中修改res的内容可能会对结果有影响(引用传递时)
*/
@AfterReturning(value = "execution(public int org.example.ba01.SomeService.doSome1(String, Integer))",
returning = "res")
public void myAfterReturning(JoinPoint joinPoint, Object res) {
System.out.println("后置通知 : 在目标方法执行后执行, 获取的返回值为" + res);
System.out.println("后置通知方法的定义为" + joinPoint.getSignature());
// 修改目标方法执行的返回值
res = 100;
System.out.println("将返回值修改为100");
}
}
@Around环绕通知
在项目开发时候经常做事务, 方法执行前开启事务, 执行结束后关闭事务
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import sun.awt.SunHints;
import java.util.Date;
@Aspect
public class MyAspect2 {
/*
环绕通知方法的定义格式
1. public
2. 必须有一个返回值, 推荐使用Object
3. 方法名称自定义
4. 方法有参数, 固定的参数为 ProceedingJoinPoint
*/
/*
@Around : 环绕通知
属性 : value 切入点表达式
位置 : 方法定义的上方
特点 :
1. 它是功能最强的通知
2. 在目标代码的前和后都能增强功能
3. 能控制你的目标方法是否被调用执行
4. 可以修改原来的目标方法的执行结果, 影响到最后的调用结果
环绕通知就等同于JDK的动态代理
参数 : ProceedingJoinPoint, 等同于JDK动态代理的Method
作用是执行目标方法的
返回值 : 就是目标方法的执行结果, 可以被修改
*/
@Around(value = "execution(String doSome2(String))")
public Object myAround(ProceedingJoinPoint point) throws Throwable {
// 实现环绕通知
Object res = null;
System.out.println("环绕通知:目标方法之前, 打印时间 : " + new Date());
// 1.目标方法调用, 可以根据某些规则控制是否调用这个方法
// 现在的规则是如果参数列表中有"张三"就执行,
// 可以使用ProceedingJoinPoint来获取业务方法信息, 这个类继承了JoinPoint
// JoinPoint中可以获取业务方法的信息
Object[] args = point.getArgs();
if ("张三".equals((String) args[0])) {
// 等于就执行
res = point.proceed(); // 执行目标方法相当于 method.invoke()
}
System.out.println("环绕通知:目标方法之后, 追加事务");
// 2.在目标方法的前后添加功能
// 可以修改执行结果
System.out.println("现在的执行结果为 : " + res.toString());
System.out.println("修改结果为 我不好");
res = "我不好";
// 返回目标方法的执行结果
return res;
}
}
使用 @Pointcut 定义和管理切入点
import org.aspectj.lang.annotation.*;
@Aspect
public class MyAspect3 {
@Before(value = "execution(String doSome3(String))")
public void myBefore() {
System.out.println("前置通知, 在目标方法之前执行");
}
@AfterReturning(value = "execution(String doSome3(String))")
public void myAfterReturning() {
System.out.println("后置通知, 在目标方法之后执行");
}
}
上面这两个切入点表达式表示的都是一个方法, 这样写比较麻烦
可以使用这样的方式管理切入点
@Aspect
public class MyAspect3 {
@Before(value = "myPointcut()")
public void myBefore() {
System.out.println("前置通知, 在目标方法之前执行");
}
@AfterReturning(value = "myPointcut()")
public void myAfterReturning() {
System.out.println("后置通知, 在目标方法之后执行");
}
/*
@Pointcut : 定义和管理切入点的
如果你的表达式有多个切入点表达式是重复的, 可以复用的
就可以使用这个注解
属性 : value 切入点表达式
位置 : 在自定义的方式上面
特点 :
当使用 @Pointcut定义在一个方法上, 此时这个方法的名称就是value属性的切入表达式的别名
其他的通知中, value属性就可以使用这个方法名称代替切入点表达式了
*/
@Pointcut(value = "execution(String doSome3(String))")
public void myPointcut() {
// 无需任何代码, 只是使用这个方法的名字作为别名
// 现在execution(String doSome3(String))就等价于myPointcut(), 注意有括号
}
}
这样统一管理, 如果切入点更新只需要改变 @Pointcut 之后的内容就可以完成所有切入点的更新