睁眼写BUG,闭眼改BUG。

Spring_AOP

2020.03.02

上一篇 >> Spring_入门

下一篇 >> Spring_理论

fighting!fighting!

AOP

  • 面向切面编程(Aspect Oriented Programming)

  • 正常程序执行流程都是纵向执行流程

    • 又叫 面向切面编程,在原有的纵向执行流程中添加横切面
    • 不需要修改原有程序的代码(体现程序的高扩展性)
      • 高扩展性
      • 原有功能 相当于释放了部分逻辑,让职责更加明确
  • 面向切面编程是什么?

    • 在程序原有纵向执行流程中,针对某一个或某一些方法添加通知,形成横切面过程就叫面向切面编程
  • 常用概念

    • 原有功能:切点,pointcut
    • 前置通知:在切点之前执行的功能,before advice
    • 后置通知:在切点之后执行的功能,after advice
    • 如果切点执行过程中出现异常,会触发异常通知,throws advice
    • 所有功能总称叫做切面
    • 织入:把切面嵌入到原有功能的过程叫做织入
  • spring提供了 2种 AOP实现方式

    • Schema-based

      • 每个通知都需要实现接口后类
      • 配置spring配置文件时在 aop:config 配置
    • AspectJ

      • 每个通知不需要实现接口或类

      • 配置spring配置文件是在aop:config的子标签aop:aspect中配置

Schema-based 实现步骤

  • 导入jar

  • 新建通知类

    • 新建前置通知类

      public class MyBeforeAdvice implements MethodBeforeAdvice{
      
      	@Override
      	public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
      		// TODO Auto-generated method stub
      		System.out.println("执行前置通知");
      	}
      
      }
      
      
    • 新建后置通知类

      • arg0:切点方法的返回值
      • arg1:切点方法的对象
      • arg2:切点方法的参数
      • arg3:切点方法所在类的对象
      public class MyAfterAdvice implements AfterReturningAdvice{
      
      	@Override
      	public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
      		// TODO Auto-generated method stub
      		System.out.println("执行后置通知");
      	}
      
      }
      
    • 配置spring配置文件

      • 引入 aop 命名空间
      • 配置通知类的
      • 配置切面
      • *表示通配符,它可以配置任意方法名,任意类名,任意一级包名
      • 如果希望匹配任意方法参数 (..)
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="https://www.springframework.org/schema/beans"
          xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
          xmlns:aop="https://www.springframework.org/schema/aop"
          xsi:schemaLocation="https://www.springframework.org/schema/beans 
          	https://www.springframework.org/schema/beans/spring-beans.xsd
          	https://www.springframework.org/schema/aop
          	https://www.springframework.org/schema/aop/spring-aop.xsd
          	">
          <!-- 配置通知类对象,在切面中引入 -->
          <bean id="mybefore" class="work.idler.advice.MyBeforeAdvice"></bean>
          <bean id="myafter" class="work.idler.advice.MyAfterAdvice"></bean>
      
          <!-- 配置切面 -->
          <aop:config>
          	<!-- 配置切点 -->
          	<aop:pointcut expression="execution(* work.idler.test.Demo.demo2())" id="mypoint"/>
          	<!-- 通知 -->
          	<aop:advisor advice-ref="mybefore" pointcut-ref="mypoint"/>
          	<aop:advisor advice-ref="myafter" pointcut-ref="mypoint"/>
          </aop:config>
          <!-- 配置demo类,测试用 -->
          <bean id="demo" class="work.idler.test.Demo"></bean>
      </beans>
      
    • 编写测试代码

      public class Test {
      	public static void main(String[] args) {
      //		Demo demo = new Demo();
      //		demo.demo1();
      //		demo.demo2();
      //		demo.demo3();
      		ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
      		Demo demo = ac.getBean("demo",Demo.class);
      		demo.demo1();
      		demo.demo2();
      		demo.demo3();
      	}
      }
      
    • 运行结果

      demo1
      执行前置通知
      demo2
      执行后置通知
      demo3
      

配置异常通知的步骤(AspectJ方式)

  • 只用当切点报异常才能触发异常通知
  • 在spring中只有AspectJ方式提供了异常通知的办法
    • 如果希望通过 schema-base 实现需要按照特定的要求自己编写方法
  • 实现步骤:
    • 新建类,任意名称的方法
    • spring配置文件中配置
      • acp:aspect的ref 属性表示:方法在那个类中
      • aop:xxxx/表示什么通知
      • method:当触发这个通知时,调用那个方法
      • throwing:异常对象名必须和通知中方法参数名相同

异常通知(Schema-based方式)

环绕通知(Schema-based方式)

  • 把前置和后置通知写到一个通知中,组成了环绕通知

使用注解(基于 Aspect)

  • spring不会自动去寻找注解必须告诉spring那些包下的类可能有注解

    • 引入xmlns:context

          <context:component-scan base-package="work.idler.advice,work.idler.test"></context:component-scan>
      
  • @Component

    • 相当于
    • 如果没有参数,把类名首字母变小写,相当于<bean id="/>
    • @Component("自定义名称")
  • 实现步骤

    • 在spring配置文件中设置注解在哪些包中

        <context:component-scan base-package="work.idler.advice,work.idler.test"></context:component-scan>
      
    • 在Demo类中添加@Componet

      • 在方法上添加@Pointcut("")
      @Component("demo123")
      public class Demo {
      	@Pointcut("execution(* work.idler.test.Demo.demo1())")
      	public void demo1() {
      //		int i=5/0;
      		System.out.println("demo1");
      	}
      }
      
    • 在通知类中配置

      • @Component 类被spring管理
      • @Aspect 相当于 aop:aspect/表示通知方法在当前类中
      @Component
      @Aspect
      public class MyAdvice {
      	@Before("work.idler.test.Demo.demo1()")
      	public void mybefore() {
      		System.out.println("前置");
      	}
      	@After("work.idler.test.Demo.demo1()")
      	public void myafter() {
      		System.out.println("后置");
      	}
      	@AfterThrowing("work.idler.test.Demo.demo1()")
      	public void mythrow() {
      		System.out.println("异常");
      	}
      	@Around("work.idler.test.Demo.demo1()")
      	public Object myarround(ProceedingJoinPoint p) throws Throwable{
      		System.out.println("环绕-前置");
      		Object result = p.proceed();
      		System.out.println("环绕-后置");
      		return result;
      	}
      }
      

代理设计模式

  • 前人总结的一套解决特定问题的代码

  • 优点:

    • 保护真实对象
    • 让真实对象职责更明确
    • 扩展
  • 代理设计模式

    • 真实对象(老总)
    • 代理对象(秘书)
    • 抽象对象(干什么的)

静态代理设计模式

  • 由代理对象代理所有真实对象的功能

    • 自己编写代理类
    • 每个代理的功能需要单独编写
  • 缺点:

    • 当代理功能多时,代理类中方法需要写很多

动态代理

  • 为了解决静态代理频繁编写代理功能缺点

  • 分类:

    • jdk 提供的
    • cglib 动态代理

JDK 动态代理

  • 和cglib 动态代理对比

    • 优点:jdk 自带,不需要频繁导入jar

    • 缺点:

      • 真实对象必须实现接口

      • 利用反射机制,效率不高

cglib 动态代理

  • 优点:

    • 基于字节码,生成真实对象的子类
      • 运行效率高于 JDK 动态代理
    • 不需要实现接口
  • 缺点:

    • 非JDK功能,需要额外导入jar
  • 使用 spring aop时,只要出现Proxy和真实对象转换异常

    • 设置为true 使用 cglib
    • 设置为false 使用 jdk(默认值)
        <context:component-scan base-package="work.idler.advice,work.idler.test"></context:component-scan>