spring5之AOP详解

一、AOP概念和作用:

概念:
AOP的全称是Aspect Oriented Programming,即面向切面编程。他是面向对象编程(oop)的一种补充。

oop遇到的问题:
当遇到日志记录,事务处理,权限判断,异常等操作,虽然oop面向对象编程可以解决,但是相同的日志代码会被重复写到各个方法中,如果后期要修改,要修改每一个方法,耦合性大,不利于开发维护。为了解决这一问题,AOP思想出现,AOP采用横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再动态的将这些提取出来的代码应用到需要执行的地方。其底层是动态代理的机制

AOP作用:
基于代理思想,对原来目标对象,创建代理对象,在不修改原对象代码情况下,通过代理对象,调用增强功能的代码,从而对原有业务方法进行增强
减少重复代码
提高开发效率
维护方便

二、AOP术语:

针对方法的:
1.连接点(Joinpoint):(可 连接/增强 的 点/方法)
类里面哪些方法可以被增强,这些方法
2.切入点(Pointcut):实际被真正增强的方法
针对部分代码逻辑的:
3.通知/增强(Advice)
(1)实际增强的逻辑部分称为通知(增强)
(2)5种通知:
前置通知:@Before 在增强的方法前执行
后置通知:@After 在增强的方法后执行
环绕通知:@Around 在增强的方法前后都执行
异常通知:@AfterThowing 当发生异常时候 执行,有异常才执行的。
返回通知:@AfterReturnning 返回值之后执行。有异常不执行。
针对动作:
4.切面(Aspect):切面是增强和切入点的结合。 Aspect 声明类似于 Java 中的类声明,,它既包括了横切逻辑的定义,也包括了连接点的定义。
5.目标(target)
  引入中所提到的目标类,也就是需要被增强的对象,也就是真正的业务逻辑对象,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。
6.代理(proxy)
  一个类被AOP织入增强后,就产生了一个代理类
7.织入(weaving)
  把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时。

在这里插入图片描述
以下代码类似于通知:

//(2)增强逻辑的方法invoke,会将这部分代码横向插入
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("获取增强方法的方法名"+method.getName());
        //obj为真实对象/被代理对象;去调用自己类中的方法即method,args为方法参数。类似于对象.方法(参数)
        Object invoke = method.invoke(obj, args);//让被代理对象的方法执行,obj为被代理对象
        System.out.println("增强方法后的逻辑");
        return invoke;
    }

三、Spring AOP底层实现:

AOP 就是要对目标进行代理对象的创建, Spring AOP是基于动态代理的,分别基于两种动态代理机制: JDK动态代理和CGLIB动态代理。Spring AOP中的动态代理默认是jdk动态代理

方式一:JDK动态代理
JDK动态代理,针对目标对象的接口进行代理 ,创建接口实现类的代理对象。 (必须有接口)

过程要点
1.必须对接口生成代理

2.采用Proxy对象,通过newProxyInstance方法为目标创建代理对象。

该方法接收三个参数 :

(1)目标对象类加载器

(2)目标对象实现的接口

(3)代理后的处理程序InvocationHandler

3.实现InvocationHandler 接口中 invoke方法,在目标对象每个方法调用时,都会执行invoke

方式二:Cglib动态代理
Cglib的引入为了解决类的直接代理问题,不需要接口也可以代理,创建当前类子类的代理对象

代码具体参考此链接:
https://blog.csdn.net/weixin_38568503/article/details/112264811.

AOP过程理解:
spring用代理类包裹切面,把他们织入到Spring管理的bean中。也就是说代理类伪装成目标类,它会截取对目标类中方法的调用,让调用者对目标类的调用都先变成调用伪装类,伪装类中就先执行了切面,再把调用转发给真正的目标bean

四、AOP的应用场景

场景一:记录日志

场景二:监控方法运行时间 (监控性能)

场景三: 权限控制

场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )

场景五: 事务管理 (调用方法前开启事务, 调用方法后提交或者回滚、关闭事务 )

五、AOP在spring中的案例应用:

AOP过程理解:
spring用代理类包裹切面,把他们织入到Spring管理的bean中。也就是说代理类伪装成目标类,它会截取对目标类中方法的调用,让调用者对目标类的调用都先变成调用伪装类,伪装类中就先执行了切面,再把调用转发给真正的目标bean

第一步,引入jar包

  1. 加入 jar 包
  • com.springsource.net.sf.cglib-2.2.0.jar
  • com.springsource.org.aopalliance-1.0.0.jar
  • com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
  • spring-aspects-4.0.0.RELEASE.jar

在这里插入图片描述

AspectJ 框架的说明

1.提供了完整的 AOP 解决方案,是AOP的 Java实现版本定义切面语法以及切面语法的解析机制
2.提供了强大的织入工具
3.AspectJ包含三个 jar 包:
(1)aspectjrt.jar 提供 AOP相关的注解 如: @Aspect @Before 等注解信息
(2)aspectjtools.jar :提供 ajc 编译器
(3)aspectjweaver.jar:对切入点表达式的支持,提供对切面语法的支持,里面还包含 java agent(用于在类加载期间去织入切面—即LTW时织入逻辑) 该 jar 包其实包含了 aspectjrt.jar 的内容

在这里插入图片描述
maven中怎么导入:
maven中需要导入spring-aop和aspectjweaver
在这里插入图片描述

准备知识:知道切入点表达式:
切入点表达式作用:知道对哪个类里面的哪个方法进行增强
语法:execution([权限修饰符][返回类型]【类全路径】【方法名称】([参数列表]))
举例:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
切入点表达式可以参考此链接:
https://blog.csdn.net/qq_42764468/article/details/102767947

开始代码演示:

第二步:创建被代理类,在类中定义方法,加@Component注解

package com.fan.aop;

import org.springframework.stereotype.Component;

//(1)需要增强的类/被代理类,类中定义方法
@Component
public class User {
    public void add(){
        System.out.println("add方法执行...");
    }
}

第三步.创建增强类(编写增强的逻辑),加@Component和@Aspect注解,增强方法上写切入点表达式

package com.fan.aop;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

//(2)增强类(编写增强逻辑)
@Component
@Aspect//此注解是生成代理对象
public class UserProxy {
    //以下是增强逻辑,要编织到方法中
    //前置通知
    @Before("execution(* com.fan.aop.User.add())")
    public void before(){
        System.out.println("方法执行前-before");
    }
    //后置通知
    @After("execution(* com.fan.aop.User.add())")
    public void after(){
        System.out.println("方法执行后-After");
    }

    //环绕通知
    @Around("execution(* com.fan.aop.User.add())")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕执行前-before");
        proceedingJoinPoint.proceed();
        System.out.println("环绕执行后-After");
    }
}

在这里插入图片描述

第四步:在 Spring 的配置文件中加入 aop和 context的命名空间,和开启自动扫描和自动代理配置

4.1在 Spring 的配置文件中加入 aop和 context的命名空间。
4.2在配置文件中配置自动扫描的包: <context:component-scan >和自动代理配置aop:aspectj-autoproxy

<?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 http://www.springframework.org/schema/context/spring-context.xsd
                          http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

   <!--开启注解扫描-->
   <context:component-scan base-package="com.fan.aop"></context:component-scan>
   <!--开启Aspect生成代理对象-->
   <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

•要在 Spring IOC 容器中启用 AspectJ 注解支持, 只要在 Bean 配置文件中定义一个空的 XML 元素 aop:aspectj-autoproxy

当 Spring IOC 容器侦测到 Bean 配置文件中的 aop:aspectj-autoproxy 元素时, 会自动为与 AspectJ 匹配的类自动生成动态代理对象.

在这里插入图片描述

在这里插入图片描述
第五步:测试aop:

package testaop;

import com.fan.aop.User;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestAop {
    @Test
    public void test01(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        User user = context.getBean("user", User.class);
        user.add();

    }
}

在这里插入图片描述
测试结果:
环绕执行前-before
方法执行前-before
add方法执行…
方法执行后-After
环绕执行后-After

环绕通知与其他通知的不同:

在这里插入图片描述

六、合并切入点表达式

相同的切入点抽取:
当多个切入点表达式相同时,我们可以合并成一个方法。如:

@Pointcut(value = "execution(* com.fan.aop.User.add())")
    public void pointAll(){

    }
    //以下是增强逻辑,要编织到方法中
    //前置通知
    @Before("pointAll()")//进行方法的调用,重复利用切入点
    public void before(){
        System.out.println("方法执行前-before");
    }

七、设置增强类的优先级:

有多个增强类对同一个被代理类的方法进行增强,需要设置优先级
(1)在增强类上面添加@Order(数字类型值),数字类型越小优先级越高
PersonProxy 增强类代码如下:

package com.fan.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Order(1)
public class PersonProxy {
    @Pointcut(value = "execution(* com.fan.aop.User.add())")
    public void all(){

    }

    @Before(value = "all()")
    public void beafor(){
        System.out.println("person的前置增强---");
    }
}

八、xml形式的aop使用(了解)

第一步:创建两个类,一个是增强类,一个是被代理类:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

九、全注解的形式的AOP:

不使用任何xml文件,写一个配置类:

package com.fan.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration//标志成配置类,代替配置文件
@ComponentScan(basePackages = {"com.fan"})//代替xml中组件扫描
@EnableAspectJAutoProxy(proxyTargetClass = true)//默认不写proxyTargetClass就是true
public class configAop {
}

@RunWith(SpringJUnit4ClassRunner.class)//使用spring测试环境
@ContextConfiguration(classes=configAop.class)在这里插入图片描述
@RunWith(SpringJUnit4ClassRunner.class)//使用spring测试环境
@ContextConfiguration(classes=configAop.class)
用法参考:
https://www.cnblogs.com/bihanghang/p/10023759.html

@RunWith作用
@RunWith 就是一个运行器

@RunWith(JUnit4.class) 就是指用JUnit4来运行

@RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境

@RunWith(Suite.class) 的话就是一套测试集合,

@ContextConfiguration Spring整合JUnit4测试时,使用注解引入多个配置文件

单个文件
@ContextConfiguration(Locations=“classpath:applicationContext.xml”)
@ContextConfiguration(classes = SimpleConfiguration.class)
多个文件时,可用{}
@ContextConfiguration(locations = { “classpath:spring1.xml”, “classpath:spring2.xml” })
@RunWith(SpringJunit4ClassRunner.class) 使用说明

Spring的Demo测试写法:

package testaop;

import com.fan.aop.User;
import com.fan.config.configAop;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)//使用spring测试环境
@ContextConfiguration(classes=configAop.class)//方式二:使用注解加载配置类或者配置文件
public class TestAop {

    @Resource(name = "user")
    private User user;

    /*@Test//方式一:加载配置文件的方式,麻烦
    public void test01(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        User user = context.getBean("user", User.class);
        user.add();

    }*/

    @Test//测试注解形式
    public void test02(){
        System.out.println(user);
        user.add();

    }
    
}

总结:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_38568503/article/details/114703572
今日推荐