Spring_AOP

文章目录

1.Spring_AOP铺垫

1创建新项目

file=>project=>spring Initializr=>Spring Web=>finish

2AOP项目代码铺垫

2.1业务说明

**事务特性:**原子性/一致性/隔离性/持久性

**业务说明:**在增删改的操作过程中添加事务控制

Demo代码的演示:

image-20211224100347470

结论:

  1. 如果按照上述的代码进行编辑,则所有的增删改操作的代码都必须按照上述的规则,代码冗余
  2. UserService与事务控制代码紧紧的耦合在一起,不方便以后扩展,应该尽可能保证业务的纯粹性

2.2代理模式说明

说明:在**业务层我们不方便,但又不得不做的事情,**可以放到代理对象中,通过这样的设计,我们就可以解决业务层耦合的问题,代理对象看起来和真的对象一摸一样,所以用户使用不会察觉

image-20211224100928301

2.3 动态代理-JDK动态代理

package com.jt.demo1.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKProxy {
    /**
     * 获取代理对象
     * 参数说明:
     *  1. ClassLoader loader 类加载器 读取真实的类数据
     *  2. Class<?>[] interfaces, 要求传递接口信息
     *  3. InvocationHandler h  当代理对象执行方法时 执行
     * 注意事项: JDK代理必须要求 "被代理者"要么有接口(本身就是接口),要么实现接口(实现类).
     */

    public static Object getProxy(Object target){
        //1.获取类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();
        //2.获取接口
        Class[] interfaces = target.getClass().getInterfaces();
        return Proxy.newProxyInstance(classLoader,interfaces,getInvocationHandler(target));
    }

    //代理对象执行方法时调用
    public static InvocationHandler getInvocationHandler(Object target){
        //这些代码都是写死的!!!!!!
        return new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("事务开始");
                //执行真实的业务方法
                Object result = method.invoke(target,args);
                System.out.println("事务提交");
                return result;
            }
        };
    }
}


2.4 业务代码测试

package com.jt.demo1;

import com.jt.demo1.config.SpringConfig;
import com.jt.demo1.proxy.JDKProxy;
import com.jt.demo1.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringTx {

    public static void main(String[] args) {
        ApplicationContext context = new   AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = context.getBean(UserService.class);
        System.out.println(userService.getClass());
        //获取代理对象
        UserService proxy = (UserService) JDKProxy.getProxy(userService);
        System.out.println(proxy.getClass());
        //基于代理对象,执行业务操作 实现方法扩展
        proxy.addUser();
        proxy.deleteUser();
    }
}


2代理模式

2.1JDK的动态代理特点

  • 类型的名称: class com.sun.proxy.$Proxy9
  • 要求 要求被代理者,必须是接口或是实现类
  • JDK代理是java原生提供的API无需导包
  • JDK动态代理在框架的源码经常使用

2.2动态代理机制-CGLIB

2.2.1Cglib代理特点说明
  • **历史原因:**JDK动态代理要求必须要有接口参与,但是某些类没有接口,所以无法使用JDK代理生成代理对象,所以为了填补空缺,则引入Cglib代理

  • 说明:Cglib动态代理要求有无接口都可以创建对象,但如何保证和被代理者"相同"?

    • 答案:通过cglib动态代理继承被代理者==>即代理对象是被代理者的子类
2.2.2动态代理的作用

说明1:一般我们将业务层中耦合性高的代码,采用动态代理的方式进行解耦,使得程序具有扩展性

说明2:Spring专门针对动态代理的规则,封装了一套API==>起名AOP

IOC+DI==>

动态代理===>业务逻辑解耦

3 Spring AOP

3.1AOP介绍

  • 面向切面编程:通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术

  • 作用: 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

总结: Spring中的AOP 利用代理对象在不修改源代码的条件下,对方法进行扩展.

3.2AOP中/专业术语(难点)

  1. 连接点: 用户可以被扩展的方法
  2. 切入点: 用户实际扩展的方法
  3. 通知: 扩展方法的具体体现
  4. 切面: 将通知应用到切入点的过程

3.3 AOP的入门案例

3.3.1引入AOPjar包

 		<!--引入AOPjar包文件-->
        <dependency>
  	          <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

3.3.2切入点表达式

  1. bean(“对象的Id”)
  2. within(“包名.类名”)
  3. execution(返回值类型 包名.类名.方法名(参数列表))
  4. @annotation(注解的路径)

3.3.3定义切面类

知识点1

注解:

  • @Component //将当前类交给Spring容器管理
  • @Aspect //我是一个切面类
  • @Pointcut //定义切面表达式
知识点2
  • 切面=切入点表达式+通知方法
  • 切入点:
    • 理解: 可以理解就是一个if判断
    • 判断条件: 切入点表达式
    • 规则:
      • 如果满足表达式 则判断为true,则执行通知方法
      • 如果满足不表达式 则判断为false,则不执行通知方法
代码
  • package com.jt.demo2.aop;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    import org.springframework.stereotype.Controller;
    import org.springframework.stereotype.Repository;
    import org.springframework.stereotype.Service;
    
    @Component  //将当前类交给Spring容器管理
    @Aspect     //我是一个切面类
    public class SpringAOP {
    
        @Pointcut("bean(userServiceImpl)")
        public void pointcut(){
    
        }
        @Before("pointcut()")
        public void before(){
            System.out.println("你好,我是前置通知");
        }
    }
    

3.3.4让AOP生效

  • **说明: **编辑配置类,添加@EnableAspectJAutoProxy,让AOP机制有效
  • image-20211224183907365

3.3.5编辑测试类

知识点
  • UserService userService = context.getBean(UserService.class);

  • 理论值:根据接口获取实现类对象, 但是与切入点表达式匹配,为了后续扩展方便.为其创建代理对象

  • package com.jt.demo2;
    
    import com.jt.demo2.config.SpringConfig;
    import com.jt.demo2.service.UserService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class Spring_AOP {
    
        public static void main(String[] args) {
            ApplicationContext context =
                    new AnnotationConfigApplicationContext(SpringConfig.class);
            //理论值:根据接口获取实现类对象, 但是与切入点表达式匹配,为了后续扩展方便.为其创建代理对象
            UserService userService = context.getBean(UserService.class);
            //如果是实现类对象,则方法没有被扩展
            //如果是代理对象, 则方法被扩展 aop有效的
            System.out.println(userService.getClass());
            userService.addUser();
        }
    }
    

3.3.6AOP形象化的比喻

  • 说明: AOP是一种抽象的一种概念,看不见/摸不着.
  • AOP就是为了给特定类添加一定的扩展且不动源代码所产生的=>通过四种表达式拦截想要的扩展的对象,从而不去破坏运来程序的结构
  • image-20211224184429418

3.4切入点表达式解析

3.4.1bean(“对象的id”)
  • Pointcut(“bean(userServiceImpl)”) 只匹配ID为userServiceImpl的对象
3.4.2within(“包名.类名”)
  • @Pointcut(“within(com.jt.demo2.service.*)”) 匹配com.jt.demo2.service下面的所有的对象==>"*"号代表通配符
说明:上面两种操作都是粗粒度的,===>按类匹配
3.4.3execution(返回值类型 包名.类名.方法名(参数列表))
  • @Pointcut(“execution(* com.jt.demo2.service**…***.*(…)))”)

    • 匹配任意返回值的 在com.jt.demo2.service包下面的子子孙孙包中的不限参数类型的方法
  • @Pointcut(“execution(* com.jt.demo2.service**.***.*(…)))”)

    • 匹配任意返回值的 在com.jt.demo2.service包下子包中不限参数类型的方法
  • @Pointcut(“execution(* com.jt.demo2.service**…***.add*(…)))”)

    • 匹配任意返回值的 在com.jt.demo2.service包下面的子子孙孙包的中不限参数类型的以add开头的方法
  • "*":任意返回类型
    "..*":子包及其子孙包
    "(..)":任意参数类型
    "add*(..)":不限参数且以add开头的方法
    
3.3.4@annotation(注解的路径)
  1. 定义注解类
    • @Target(ElementType.METHOD)             //注解对方法有效
      @Retention(RetentionPolicy.RUNTIME)     //运行期有效
      public @interface cgb2110 {             //注解起标记作用
      }
      
    2.切点表达式写法
    • @Pointcut("@annotation(com.jt.demo2.anno.CGB2110)")
    • 拦截有该注解的对象
    • image-20211224191118544
    3. 注解解释
    • 元注解: @Target: 注解用在哪里:类上、方法上、属性上等等

    • ElementType.TYPE 应用于类的元素
      ElementType.METHOD 应用于方法级
      ElementType.FIELD 应用于字段或属性(成员变量)
      ElementType.ANNOTATION_TYPE 应用于注解类型
      ElementType.CONSTRUCTOR 应用于构造函数
      ElementType.LOCAL_VARIABLE 应用于局部变量
      ElementType.PACKAGE 应用于包声明
      ElementType.PARAMETER 应用于方法的参数
      
    • 元注解: @Retention 注解的生命周期:源文件中、字节码文件中、运行中

      SOURCE 在源文件中有效(即源文件保留)
      CLASS 在class文件中有效(即class保留)
      RUNTIME 在运行时有效(即运行时保留
      

3.5动态获取注解参数

3.5.1 定义注解

知识点
  • @target 表示注释用在哪里

  • @Retention 表示注解生命周期

  • @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Find {
        int id() default 0;
    }
    
使用注解
  • image-20211224192440424

3.5.2 需求

利用前置通知,打印注解中的Id值!!!

2.5.3 编辑切面类

知识点
  • /**
        * 知识点:
        *      1.如果切入点表达式只对当前通知有效.则可以按照如下方式编辑
        * 要求: 动态的拦截Find注解,并且要获取Find注解中的参数Id
        * 难点: 动态获取注解的对象!!
        * 代码解释:
        *    1.@annotation(find) 拦截find变量名称对应类型的注解
        *    2.当匹配该注解之后,将注解对象当做参数传递给find
        *    优势: 可以一步到位获取注解的内容.避免了反射的代码
        */
       @Before("@annotation(find)")
       public void before2(Find find){
           System.out.println("ID的值为:"+find.id());
       }
     
    

3.6 通知方法

3.6.1关于通知方法解析

  1. 前置通知 在目标方法执行之前执行
  2. 后置通知 在目标方法执行之后执行.
  3. 异常通知 在目标方法执行之后抛出异常时执行.
  4. 最终通知 都要执行的通知
说明: 上述的四大通知一般用于记录程序的运行状态.只做记录.

5.环绕通知 在目标方法执行前后都要执行的通知

3.6.2前置通知案例

@Before("pointCut()")
    public void Before(JoinPoint joinPoint) {
        //1.获取目标对象的类型
        Class tagetClass = joinPoint.getTarget().getClass();
        //2.获取目标对象的路径
        String path = joinPoint.getSignature().getDeclaringTypeName();
        //3.目标对象的方法名
        String methodName = joinPoint.getSignature().getName();
        //4.目标对象的方法参数
        Object[] args = joinPoint.getArgs();
        System.out.println("对象的类型" + tagetClass);
        System.out.println("对象的路径" + path);
        System.out.println("对象的方法名" + methodName);
        System.out.println("对象的方法参数" + Arrays.toString(args));
    }

3.6.3后置通知案例

3.6.3.1AOP设置
//注意: 如果多个参数,joinPoint必须位于第一位
    @AfterReturning(value = "pointCut()", returning = "result")
    public void afterReturn(JoinPoint joinPoint, Object result) {
        //如果获取当前方法信息,则通过joinPoint获取
        System.out.println("我是后置通知,返回值是:" + result);
    }
3.6.3.2接口设置

image-20211224201410987

3.6.3.3编辑实现类

image-20211224201508456

3.6.3.4编辑测试案例
 public static void main(String[] args) {
        ApplicationContext context =
                new AnnotationConfigApplicationContext(SpringConfig.class);
        //理论值:根据接口获取实现类对象, 但是与切入点表达式匹配,为了后续扩展方便.为其创建代理对象
        UserService userService = context.getBean(UserService.class);
        //如果是实现类对象,则方法没有被扩展
        //如果是代理对象, 则方法被扩展 aop有效的
        System.out.println(userService.getClass());
        userService.addUser();
        userService.findCount(); //测试带返回值的方法
    }

3.6.4异常通知案例

 //后置通知与异常通知互斥,只能存在一个
    @AfterThrowing(value = "pointCut()", throwing = "exception")
    public void afterThrow(JoinPoint joinPoint, Exception exception) {
        //打印异常
//        exception.printStackTrace();
        System.out.println("异常信息:" + exception.getMessage());
    }

3.6.5最终通知

说明:不管方法是否有误,都会执行该通知

注解:@After

3.6.6环绕通知

注解:@Round
特点:
  • 方法执行前后,通知都要执行.
  • 环绕通知可以控制目标方法是否执行
  • 环绕通知必须添加返回值
proceed():
  • 作用1: 如果有下一个通知,则执行下一个通知
  • 作用2: 如果没有下一个通知,则执行目标方法
代码演示
   @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知开始!!!");
        Object result = joinPoint.proceed();
        System.out.println("环绕通知结束!!!");
        return result;
    }

3.6.7Spring中通知总结

第一类:程序状态的记录

  1. 前置通知

  2. 后置通知: 记录目标方法的返回值

  3. 异常通知: 记录目标方法执行之后,抛出异常信息

  4. 最终通知: 记录程序最后的终后状态

第二类:程序控制

  1. 环绕通知 : 控制目标方法是否执行,环绕通知是未来使用最多的,功能最为强大的

3.7Spring中AOP案例

需求1: 需要对执行方法的时间进行监控

通知选择: 环绕通知

需求2:利用AOP可以实现缓存控制

业务思路:

  1. 用户直接查询缓存
    1. 如果缓存中没有数据,表示第一次查询,让目标方法执行
    2. 如果缓存中有数据,表示这是第n次查询,不执行目标方法,从缓存中获取即可
需求3: 利用AOP控制事务

通知类型: 环绕通知

需求4: 利用AOP方法的权限

通知类型 :环绕通知

业务思路:

  1. 在AOP环绕通知中,判断当前用户是否有该权限
    1. 有权限: 可以执行目标方法 获取数据
    2. 没有权限 : 不能控制目标方法,无法获取数据并通知

说明:

  • 关于AOP现状,一般工作中很少的直接编辑AOP底层源码,一般都是用AOP的高级API

3.8AOP执行的循序

3.8.1编辑第二个AOP测试类

注解:@Component //将当前类,交给Spring容器管理

​ @Aspect //标识AOP

package com.jt.demo2.aop;

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

@Component  //将当前类.交给Spring容器管理
@Aspect     //标识AOP
public class SpringAOP2 {

    @Around("@annotation(com.jt.demo2.anno.CGB2110)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知开始2");
        Object result = joinPoint.proceed();
        System.out.println("环绕通知结束2");
        return result;
    }
}


3.8.2AOP执行顺序说明

说明:如果有多个环绕通知,其中执行的顺序是嵌套关系

默认从上向下执行,若要手动排序则需要添加@Order注解

@Order(值):值为123456789==>数字越小越先执行;当输入的数字一样时,按默认执行

嵌套关系

image-20211227151109864

4.Spring总结

知识总结:

  1. Spring的作用:Spring可以整合其他的第三方框架!从架构的角度,实现了代码的松耦合
  2. Spring-IOC 控制反转/数据结构Map集合< id ,反射实例化对象>/xml文件写法/注解写法
  3. Spring-DI:依赖注入 类型注入\名称注入\一般接口实现都为单实现
  4. Spring-AOP 在不修改源码的条件下对方法进行扩展!!!(动态代理)
  5. 动态代理 JDK动态/Cglib动态代理 method.invoke()
  6. 切面 = 切入点表达式 + 通知方法
  7. AOP中因为切面较多,每个切面都完成特定的功能,所以一般不会研究顺序. @Order注解 可有控制顺序.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值