Spring-AOP

AOP( 面向切面编程)

1. AOP介绍

1.1 简介

AOP( 面向切面编程 )是一种思想,它的目的就是在不修改源代码的基础上,对原有功能进行增强。
编码时分开编写,运行时植入(weaving)切面有关的代码,这就叫做面向切面编程AOP

SpringAOP是对AOP思想的一种实现,Spring底层同时支持jdk和cglib动态代理。
Spring会根据被代理的类是否有接口自动选择代理方式:

  • 如果有接口,就采用jdk动态代理
  • 如果没接口,就采用cglib的方式

1.2 核心概念

* 目标对象(Target)
	被代理的对象

* 连接点(JoinPoint)
	目标对象中得所有方法

* 切入点(PointCut)
	目标对象中得要进行功能增强那部分方法

* 增强 (Advice 通知)
	一个具体增强功能(增强对象  增强方法)

* 切面 (Aspect)
	切面是一种描述,描述的是: 哪个增强方法   加入到了  哪个切点  的   什么位置
	增强方法和切点方法的执行顺序

2. 入门案例

2.1 创建模块,导入依赖

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--切点表达式解析坐标-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>
        </dependency>

        <!--测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.6</version>
        </dependency>
    </dependencies>

2.2 创建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dept {
    private Integer id;
    private String name;
}

2.3 创建业务层接口和实现类

接口
public interface DeptService {
    //列表查询
    List<Dept> findAll();

    //保存
    void save(Dept dept);

    //主键查询
    Dept findById(Integer id);
}

实现类
@Service
public class DeptServiceImpl implements DeptService {
    @Override
    public List<Dept> findAll() {
        System.out.println("findAll");
        return new ArrayList<>();
    }

    @Override
    public void save(Dept dept) {
        System.out.println("save");
    }

    @Override
    public Dept findById(Integer id) {
        System.out.println("findById");
        return new Dept(1,"开发部");
    }
}

2.4 创建日志类

@Component
public class Logger {
    public void m1(){
        System.out.println("即将进入方法");
    }
}

2.5 配置切面

@Aspect //切面注解,需要标注在增强类上
@Component
public class Logger {

    //切点表达式:作用就是定位出切点(findAll)
    @Pointcut("execution(java.util.List com.itheima.service.DeptService.findAll())")
    public void pt(){}

    @Before("pt()")//指定被标注的方法要在切点之前执行
    public void m1(){
        System.out.println("即将进入方法");
    }
}

2.6 创建配置类

@EnableAspectJAutoProxy //激活切面自动代理
@ComponentScan("com.itheima")
public class SpringConfig {
}

2.7 单元测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class DeptServiceTest {

    //从容器中获取的已经是代理对象
    @Autowired
    private DeptService deptService;

    @Test
    public void test1(){
        deptService.findAll();
    }
}

测试结果:
即将进入方法
findAll

3. 通知类型

3.1 四大通知

四大通知描述的就是增强方法在切点方法的什么位置上执行

- 前置通知(before):增强方法在切点运行之前执行
- 返回后通知(after-returning):增强方法在某切点正常完成后执行的通知,不包括抛出异常的情况
- 异常后通知(after-throwing):增强方法在某切点抛出异常退出时执行的通知
- 后置通知(after):增强方法在某切点退出的时候执行的通知(不论是正常返回还是异常退出)

通知执行的位置

try{
	前置通知(before)
	//切点执行位置
	返回后通知(after-returning)
}catch(Execption e){
	异常后通知(after-throwing)
}finally{
	后置通知(after)
}
3.1.1 添加通知
@Aspect //切面注解,需要标注在增强类上
@Component
public class Logger {

    //切点表达式:作用就是定位出切点(findAll)
    @Pointcut("execution(java.util.List com.itheima.service.DeptService.findAll())")
    public void pt(){}

    @Before("pt()")//指定被标注的方法要在切点之前执行
    public void m1(){
        System.out.println("即将进入方法");
    }
    @AfterReturning("pt()")//指定被标注的方法正常结束后通知
    public void m2(){
        System.out.println("方法正常结束");
    }

    @AfterThrowing("pt()")//指定被标注的方法出现异常后通知
    public void m3(){
        System.out.println("方法出现异常");
    }

    @After("pt()") //指定被标注的方法退出后通知(无论是正常结束还是异常)
    public void m4(){
        System.out.println("方法运行到最后");
    }
}
3.1.2 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class DeptServiceTest {

    //从容器中获取的已经是代理对象
    @Autowired
    private DeptService deptService;

    @Test
    public void test1(){
        deptService.findAll();
    }
}

测试结果
即将进入方法
findAll
方法正常结束
方法运行到最后

3.2 环绕通知

它是一种特殊的通知,他允许以编码的形式实现四大通知
@Around环绕通知需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行.
@Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回结果.
当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行。

   //切点表达式:作用就是定位出切点(findAll)
    @Pointcut("execution(java.util.List com.itheima.service.DeptService.findAll())")
    public void pt(){}
    
    //环绕通知
    @Around("pt()")
    public Object m5 (ProceedingJoinPoint pjp){
        Object obj = null ;
        try {
            System.out.println("方法执行前");
            obj = pjp.proceed();//代表切点执行
            System.out.println("方法正常结束");
        } catch (Exception e) {
            System.out.println("方法出现异常");
        }finally {
            System.out.println("方法运行到最后");
            return obj;
        }
    }

测试结果
方法执行前
findAll
方法正常结束
方法运行到最后

3.3 通知顺序:

3.3.1 默认顺序

不同切面类中,默认按照切面类的类名字母排序:
目标方法前的通知方法:字母排名靠前执行
目标方法后的通知方法:字母排名靠前执行

//案例
//这是L方法
@Aspect //切面注解,需要标注在增强类上
@Component
public class Logger {
    //方式2:@annotation(自定义注解类型) 使用一个注解来挑选出切点
    @Pointcut("@annotation(com.itheima.anno.LogAnno)")
    public void pt(){}

    @Before("pt()")//指定被标注的方法要在切点之前执行
    public void m1(){
        System.out.println("即将进入L方法");
    }
    @After("pt()") //指定被标注的方法退出后通知(无论是正常结束还是异常)
    public void m4(){
        System.out.println("方法L运行到最后");
    }
}

//这是M方法
@Aspect
@Component
public class MM {
    @Pointcut("@annotation(com.itheima.anno.LogAnno)")
    public void pt(){}

    @Before("pt()")//指定被标注的方法要在切点之前执行
    public void m1(){
        System.out.println("即将进入M方法");
    }

    @After("pt()") //指定被标注的方法退出后通知(无论是正常结束还是异常)
    public void m4(){
        System.out.println("方法M运行到最后");
    }
}

//测试
    @Test
    public void test1(){
        deptService.findAll();
    }

测试结果:
即将进入L方法
即将进入M方法
findAll
方法M运行到最后
方法L运行到最后

3.3.2 用@Order(数字)加在切面类上来控制顺序

目标方法前的通知方法:数字小执行
目标方法后的通知方法:数字小执行

//案例
//这是L方法
@Aspect //切面注解,需要标注在增强类上
@Component
@Order(1)
public class Logger {
    //方式2:@annotation(自定义注解类型) 使用一个注解来挑选出切点
    @Pointcut("@annotation(com.itheima.anno.LogAnno)")
    public void pt(){}

    @Before("pt()")//指定被标注的方法要在切点之前执行
    public void m1(){
        System.out.println("即将进入L方法");
    }
    @After("pt()") //指定被标注的方法退出后通知(无论是正常结束还是异常)
    public void m4(){
        System.out.println("方法L运行到最后");
    }
}

//这是M方法
@Aspect
@Component
@Order(0)
public class MM {
    @Pointcut("@annotation(com.itheima.anno.LogAnno)")
    public void pt(){}

    @Before("pt()")//指定被标注的方法要在切点之前执行
    public void m1(){
        System.out.println("即将进入M方法");
    }

    @After("pt()") //指定被标注的方法退出后通知(无论是正常结束还是异常)
    public void m4(){
        System.out.println("方法M运行到最后");
    }
}
//测试
    @Test
    public void test1(){
        deptService.findAll();
    }

测试结果:
即将进入M方法
即将进入L方法
findAll
方法L运行到最后
方法M运行到最后

4. 切点表达式

4.1 execution

execution() :指定一组规则来匹配某些类中的方法,匹配中的就是切点

execution(修饰符? 返回值 包名.类名.方法名(参数) throws 异常?)
* 代表一个或多个位置
… 代表0个或多个位置
?表示可以省略

@Aspect //切面注解,需要标注在增强类上
@Component
public class Logger {

    //切点表达式:作用就是定位出切点
    // * 表示一个或多个位置
    // .. 表示0个或多个位置
    //com.itheima.service.impl[包名].*[实现类].*[方法](..)[方法参数]
    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    public void pt(){}


    //环绕通知
    @Around("pt()")
    public Object m5 (ProceedingJoinPoint pjp){
        Object obj = null ;
        try {
            System.out.println("方法执行前");
            obj = pjp.proceed();
            System.out.println("方法正常结束");
        } catch (Exception e) {
            System.out.println("方法出现异常");
        }finally {
            System.out.println("方法运行到最后");
            return obj;
        }
    }
}

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class DeptServiceTest {

    //从容器中获取的已经是代理对象
    @Autowired
    private DeptService deptService;

    @Test
    public void test1(){
        deptService.findAll();
        System.out.println("============");
        deptService.save(new Dept());
        System.out.println("============");
        deptService.findById(1);
    }
}

测试结果:
方法执行前
findAll
方法正常结束
方法运行到最后
============
方法执行前
save
方法正常结束
方法运行到最后
============
方法执行前
findById
方法正常结束
方法运行到最后

4.2 @annotation

@annotation:指定一个注解,凡是标有此注解的方法都是切点

4.2.1 自定义注解
//自定义注解
public @interface LogAnno {
}
4.2.2 在作为切点的方法上添加注解
@Service
public class DeptServiceImpl implements DeptService {
    @Override
    @LogAnno
    public List<Dept> findAll() {
        System.out.println("findAll");
        return new ArrayList<>();
    }

    @Override
    @LogAnno
    public void save(Dept dept) {
        System.out.println("save");
    }

    @Override
    @LogAnno
    public Dept findById(Integer id) {
        System.out.println("findById");
        return new Dept(1,"开发部");
    }
}
4.2.3 设置切点表达式

在增强类上设置

@Aspect //切面注解,需要标注在增强类上
@Component
public class Logger {

    //方式2:@annotation(自定义注解类型) 使用一个注解来挑选出切点
    @Pointcut("@annotation(com.itheima.anno.LogAnno)")
    public void pt(){}


    //环绕通知
    @Around("pt()")
    public Object m5 (ProceedingJoinPoint pjp){
        Object obj = null ;
        try {
            System.out.println("方法执行前");
            obj = pjp.proceed();
            System.out.println("方法正常结束");
        } catch (Exception e) {
            System.out.println("方法出现异常");
        }finally {
            System.out.println("方法运行到最后");
            return obj;
        }
    }
}
4.2.4 测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class DeptServiceTest {

    //从容器中获取的已经是代理对象
    @Autowired
    private DeptService deptService;

    @Test
    public void test1(){
        deptService.findAll();
        System.out.println("============");
        deptService.save(new Dept());
        System.out.println("============");
        deptService.findById(1);
    }
}

测试结果
findAll
方法正常结束
方法运行到最后
============
方法执行前
save
方法正常结束
方法运行到最后
============
方法执行前
findById
方法正常结束
方法运行到最后

5. 日志记录详情

日志:类名,方法名,参数,返回结果,报错,耗时时间

    //环绕通知
    @Around("pt()")
    public Object m5 (ProceedingJoinPoint pjp) throws Throwable{
        Object obj = null ;
        StringBuilder sb = new StringBuilder();
        //获取类名
        sb.append("类名"+ pjp.getTarget().getClass().getName());
        //获取方法名
        MethodSignature methodSignature =(MethodSignature) pjp.getSignature();
        sb.append("方法名"+methodSignature.getMethod().getName());
        //获取方法参数
        sb.append("参数"+ Arrays.toString(pjp.getArgs()));
        long begin = System.currentTimeMillis();//开始时间
        try {
            obj = pjp.proceed();
            //获取返回结果[] 为无返回值
            sb.append("返回值"+obj);
        } catch (Exception e) {
        //返回错误信息
            sb.append("错误信息"+e.getMessage());
        }finally {
        long end = System.currentTimeMillis();//结束时间
            sb.append("耗时时间"+(end-begin));//耗时
        //输出日志信息
            System.out.println("日志结果"+sb.toString());
        }
        return obj;
    }
}
测试类
   @Test
    public void test1(){
        deptService.findAll();
        System.out.println("============");
        deptService.save(new Dept());
        System.out.println("============");
        deptService.findById(1);
    }

测试结果
findAll
日志结果类名com.itheima.service.impl.DeptServiceImpl方法名findAll参数[]返回值[]耗时时间1
============
save
日志结果类名com.itheima.service.impl.DeptServiceImpl方法名save参数[Dept(id=null, name=null)]返回值null耗时时间0
============
findById
日志结果类名com.itheima.service.impl.DeptServiceImpl方法名findById参数[1]返回值Dept(id=1, name=开发部)耗时时间1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值