SpringAOP

SpringAOP拦截

介绍

作用: 在代码的某处 使用动态代理统一动态的为某些方法添加功能或代码进行增强。它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

​ AOP的开发思想(范式):当代码中存在相同的冗余或者要对某些方法进行增强的时候而采用的拦截方式。

​ 弥补OOP(面向对象编程)的不足、可以独立的将某些不确定的,非必要封装的代码进行统一的抽取。运用到多个代码之中。

SpringAOP_第1张图片

上图中:左边是抽取之后的代码效果,右边是抽取之前的效果。

​ 共性功能B 即使只有一句话也是可以被抽取出来作为AOP的,但是没有必要。

Spring特性

  1. ioc 控制反转(容器) --> 反射
  2. AOP 面向切面编程(方法增强)–> 动态代理(动态代理的底层还是使用的反射)

AOP的优势

​ 提高可用性 :抽取出来的代码可以在多个方法中反复使用

​ 简洁 :从代码的方法上看,让编码更简洁。因为去除了大量的冗余代码

​ 维护高效 :如果每个方法都有同样的代码,那么一处修改则处处修改,有了AOP的支持那么只需要在拦截方法中进行统一修改即可

​ 扩展便捷 :由于底层使用的是动态代理,所以在也上中如果要扩展一些功能,就会更加便捷。

AOP操作对象

Aop的操作对象主要是方法。由于在现目前的企业级开发中,还没有做到整个项目的标准化开发,所以在使用AOP的时候会存在某些因为不标准所带来的麻烦(每个人写代码的方式不一致)。因此AOP的使用必须建立在公司对某一个模块功能有一套完整的标准基础上。往往这些功能具备是 统计查询时间,记录日志等统一度高的地方使用。

AOP概念

1)连接点

所有的POJO类中所定义的方法都可以被称为为连接点 ,可以用于AOP的使用。但是要有抽取意义。

2)切入点

表示会被抽取的方法中是有共性功能的,那么被抽取之后所独立出来的新方法叫做切入点(pointCut)(AOP要增强的规则)

3)通知

共性功能(增强的内容) 称为通知 (AOP类中的增强方法)

4)切面

描述切入点和通知的关系 切面

配置哪一个通知按照哪一个切入点的规则对哪些方法进行增强

运行该共性功能的类 叫做目标对象 (规则中筛选出来的方法所在的类)

5)织入

发布运行项目的时候,要还原所有代码。这个动作过程叫织入

6)代理

AOP在底层实际上是使用的动态代理执行的增强

7)引入

在代理中添加自己的增强方法叫引入

AOP入门案例

入门案例以全配置文件进行

1.导入坐标

<dependencies>
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-contextartifactId>
        <version>5.1.9.RELEASEversion>
    dependency>

    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-jdbcartifactId>
        <version>5.1.9.RELEASEversion>
    dependency>

    <dependency>
        <groupId>org.mybatisgroupId>
        <artifactId>mybatisartifactId>
        <version>3.5.3version>
    dependency>
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <version>5.1.47version>
    dependency>

    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>druidartifactId>
        <version>1.1.16version>
    dependency>

    <dependency>
        <groupId>org.mybatisgroupId>
        <artifactId>mybatis-springartifactId>
        <version>1.3.0version>
    dependency>

    <dependency>
        <groupId>org.aspectjgroupId>
        <artifactId>aspectjweaverartifactId>   //AOP包
        <version>1.9.4version>
    dependency>

dependencies>

2.配置文件


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"			
        https://www.springframework.org/schema/aop/spring-aop.xsd">		

    
    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
    
    
    <bean id="myAdvice" class="com.itheima.aop.AOPAdvice"/>

    
    <aop:config>
        
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        
        <aop:aspect ref="myAdvice">
            
            <aop:before method="function" pointcut-ref="pt"/>
        aop:aspect>
    aop:config>

beans>

3.通知类和测试类


//1.制作通知类,在类中定义一个方法用于完成共性功能
public class AOPAdvice {
     

    public void function(){
     
        System.out.println("共性功能");
    }
}

---------------------------以下测试类---------------------------

//实现类   调用该方法进行测试
public class UserServiceImpl implements UserService {
     

    public void save(){
     
        //0.将共性功能抽取出来
        //System.out.println("共性功能");
        System.out.println("user service running...");
    }

}

    
    

配置文件

切点表达式 修饰符,返回值,包路径,方法(参数)

  • *表示任意字符

    … 表示任意多个 任意包层级

    修饰符默认public 可以不写

公共切入点

在外部设置一个切点规则,在其他切面里也可以使用

<aop:config>
    
     <aop:pointcut id="ptc" expression="execution(* *..*(..))"/>
    
    
    <aop:aspect ref="rule">
        
        <aop:before method="showRules" pointcut-ref="ptc"/>
    aop:aspect>
    
aop:config>

局部切入点

把切点规则设置在本切面里面,供本切面的其他类型使用

<aop:config>
    
    <aop:aspect ref="rule">
        
         <aop:pointcut id="ptc" expression="execution(* *..*(..))"/>
        
         <aop:before method="showRules" pointcut-ref="ptc"/>
    aop:aspect>
    
aop:config>

私有切入点

仅自己本类型使用。

<aop:config>
    
    <aop:aspect ref="rule">    
        
         <aop:before method="showRules" pointcut="execution(* *..*(..))"/>
    aop:aspect>
    
aop:config>

五种通知类型

前置类型

<aop:config>
	<aop:aspect ref="rule">
   		 
   		 <aop:before method="showRules" pointcut-ref="ptc"/>
	aop:aspect>
aop:config>

后置类型

<aop:config>
	<aop:aspect ref="rule">
   		 
   		 <aop:after method="showRules" pointcut-ref="ptc"/>
	aop:aspect>
aop:config>

返回值类型

方法运行中如果执行错误那么就不会执行。因为不会返回。如果该方法返回值是void,那么默认返回null且不接收。

<aop:config>
	<aop:aspect ref="rule">
   		 
   		 <aop:returning method="showRules" pointcut-ref="ptc"/>
	aop:aspect>
aop:config>

抛出异常类型

只有在代码中报错且没有被捕获时才会执行的类型。

<aop:config>
	<aop:aspect ref="rule">
   		   
   		   <aop:after-throwing method="showRules" pointcut-ref="ptc"/>
	aop:aspect>
aop:config>

环绕

proceedingJoinPoint这个类调用proceed方法进行分割,proceed也代表原方法

环绕是前四个方法的综合使用。

使用:

(前置) try… (前置)…(设置proceed)(返回)… catch(在catch中抛出异常)…finally(在finally中后置)

<aop:aspect ref="rule">
    
    <aop:around method="showRules" pointcut-ref="ptc"/>  //配置文件中要设置该ptc的切点
aop:aspect>

切点拦截方法

//通知类中的切点方法(该通知类必须在IOC中存在)
public  Object showRules(ProceedingJoinPoint pjp){
       //使用环绕,要使用该类
    System.out.println("前置已执行...");
    Object o = null;
    try {
     
        pjp.proceed();   //环绕的位置从此处开始。 此处以前是前置。 
        int a = 1/0;  // 测试错误代码
        System.out.println("返回已执行...");  //如果报错则不执行,
        return null;
    } catch (Throwable throwable) {
     
        throwable.printStackTrace();  
        System.out.println("异常已执行...");  //只有异常捕获时执行
    }
    System.out.println("后置已执行..."); //如果有finally  那么就要写在finally中
    return null;
}

xml的通知顺序

在配置文件中,如果遇到冲突,运行的先后顺序按照配置文件中的先后顺序执行。

方法不一致时与方法一致时都是按照此逻辑执行。

​ before/around 按照顺序排列执行对应的方法

​ after/returning/around 按照顺序排列执行对应的方法

通知的先后顺序要分为两组。

因为 以proceed进行分离, 只会存在 前置和后置 前置永远执行在前面,后置永远执行在后面。

所以前置的两种情况 才会进行比较,后置的三种情况才会进行比较。

异常类不在其中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kcr3FEit-1602507040381)(D:\Java总结文档\图解\Spring\疑问?为什么分为两组.png)]

通知

获取数据

作用:可以通过JoinPoint类获取该方法传入的各类参数。提供了各项方法。其中getArgs()可以获得该方法的所有形参数组

如果不使用环绕,那么就可以使用该类进行获取

注意:

​ 通知类切点规则中的形参名与配置文件中的参数名字要一致

​ 通过Joinpoint类可以获取方法的元信息 (jp.getSignature()) 如:方法的名字 参数等 jp.getSignature().getName()

//java的测试类
ClassPathXmlApplicationContext c =
new ClassPathXmlApplicationContext("AOPconfig.xml");
Function mybean = (Function) c.getBean("mybean");
mybean.save1(666);

//通知类
    public  void show(JoinPoint jp){
     
        Object[] args = jp.getArgs();
        System.out.println("我是参数"+args[0]);
    }


    <!--配置AOP容器-->
    <aop:config>
        <!--设置需要配置的规则方法-->
        <aop:pointcut id="ptc" expression="execution(* *..*(..))"/>
        <aop:aspect ref="rule">
            <aop:before method="show"
                
         //不起作用???报错:warning no match for this type name: jp
                pointcut="execution(* *..*(..)) && args(jp)"  
                
                 <aop:before method="show" arg-names="jp"
                        pointcut="execution(* *..*(..)) "   //使用这种方式又可以???
         		   />
            />
        </aop:aspect>
    </aop:config>

不指定JoinPoint,直接使用基本类型的方式:

//测试类代码。实现类的方法中已经定义过int参数    save(int a )
 ClassPathXmlApplicationContext c =
 new ClassPathXmlApplicationContext("AOPconfig.xml");
 Function mybean = (Function) c.getBean("mybean");
 mybean.save1(666);



//指定输入参数值  通知类
public  void show(int jp){
     
    System.out.println("我是参数"+jp);
}

//xml文件中的配置
   <!--配置AOP容器-->
    <aop:config>
        <!--设置需要配置的规则方法-->
        <aop:pointcut id="ptc" expression="execution(* *..*(..))"/>
        <aop:aspect ref="rule"> //绑定rule的通知类
            <aop:before method="show" //设置前置类型,执行通知类的show方法
                arg-names="jp" //下面的方式不行就使用这种方式进行定义
                
                 //要使用args定义变量名,与通知类变量名一致。
                //使用&&做逻辑判断,由于&与xml文件有冲突,所以使用转义符也可以使用纯文本标签
                        pointcut="execution(* *..*(..)) && args(jp)"    
            />
        </aop:aspect>
    </aop:config>
       

获取返回值

获取返回值有两种类型的获取: 环绕、返回后类型(after可能得可能不得,故不考虑)

void=null 如果方法的返回值是void则表示没有返回值,那么就是null。

如果通知类中的方法已经有joinpoint存在, 那么其他类型的参数只能在之后写。务必保证joinpoint 类在第一位!否则报错

  • 返回后类型

    after-returning – - - returning属性

注意:在查看是否有返回值的时候,必定是根据规则中的返回值进行过滤。如果返回值与目标函数返回值不一致那么不进行过滤

<aop:pointcut id="one" expression="execution(public void com.AOP..find*(..))"/>  //此处的void变为*表示通配

案例:

xml文件中的配置


<aop:config>
    
    <aop:pointcut id="one" expression="execution(public * com.AOP..find*(..))"/>   //注意此处的通配符
    
    <aop:aspect ref="rule">
        
        <aop:after-returning method="findEnhance" pointcut-ref="one" returning="obj" />
    aop:aspect>
aop:config>

通知类

//    返回值
    public void findEnhance(JoinPoint jp,Object obj){
     
        String name = jp.getSignature().getName();
        System.out.println("time is .."+System.currentTimeMillis()+"  name is "+name+" is parameter?"+obj);
    }

实例类中的方法

    @Override
    public int find4() {
     
        System.out.println("findTest...");
        try {
     
//            int i = 1 / 0;
            return 11;
        }catch (Exception e){
     
            System.out.println("试试");
        }
        return 11;
    }

测试类

  ClassPathXmlApplicationContext c =
  new ClassPathXmlApplicationContext("AOPconfig.xml");
  Function mybean = (Function) c.getBean("mybean");
mybean.find4();
  • 环绕

around – - - returning属性

传参与不传参都可以得到proceed,由此可以得到返回值,或者决定执不执行原拦截方法。

通知类


public  Object showRules(ProceedingJoinPoint pjp) throws Throwable {
     
    System.out.println("此处为前置..");
    Object proceed = pjp.proceed(pjp.getArgs());   //返回的是Object,一般来说都是返回Object,因为不确定会传入什么。
    Object proceed = pjp.proceed();    
    System.out.println("此处为后置.."+proceed);
    return proceed;
}

xml文件中的环绕配置

<aop:config>
	
	<aop:pointcut id="ptc" expression="execution(* *..*(..))"/>
    <aop:aspect ref="rule">
    
  		  <aop:around method="showRules" pointcut-ref="ptc"/>   //引用
	aop:aspect>
aop:config>

测试类

@Override
public int find4() {
     
    System.out.println("findTest...");
    try {
     
        return 11;
    }catch (Exception e){
     
        System.out.println("试试");
    }
    return 11;
}

获取异常

在抛出异常中使用

agter-throwing-- - - throw属性

注意:

通知类中,如果try …catch了 那么就不会显示,反之显示。 在测试类中捕获。依然会进入通知类。

传入的接口是Throwable ,参数名要与配置文件中的名字一致。

配置文件

<aop:aspect ref="rule">
    
    <aop:after-throwing method="findEnhance" pointcut-ref="one" throwing="tw" />
aop:aspect>

通知类

//    异常信息
public void findEnhance(JoinPoint jp,Throwable tw){
     
        try{
     
    String name = jp.getSignature().getName();
    System.out.println("time is .."+System.currentTimeMillis()+"name is "+name);
    System.out.println("日志错误信息:"+tw);
        }catch (Exception e){
     
            System.out.println("捕获了");
        }
}

实体类的方法(已经捕获,则不会进入到通知类)

@Override
public void find3() {
     
    try {
     
        int i=1/0;
    } catch (Exception e) {
     
        e.printStackTrace();
    }
    System.out.println("findTest...");
}

使用注解

1)半注解半配置文件

在xml配置文件中必须开启AOP的支持,并且扫描对应包。


<aop:aspectj-autoproxy>aop:aspectj-autoproxy>


<context:component-scan base-package="com.annotation"/>
常用的注解
  • @PointCut (规则) 要定义在一个类上,该类本身不具备AOP功能仅仅是一个id,被其他方法所指向。
@Pointcut("execution(* *..*(..))")   //设置规则 切点
public void pt(){
      //此处的pt就是该切点的名字,被其他方法指向。
		//其中的代码不会生效,除非被改类对象调用
}
  • @Aspect 指定切面 ,在类上面直接指定。让该类称为切面。所有满足条件的对象都要执行该切面中指定的方法

@Component //读入该类到容器中
@Aspect   //设置切面
public class rules {
     

    public  void showRules(JoinPoint jp){
     
        Signature signature = jp.getSignature();
        System.out.println("现在的时间是:"+System.currentTimeMillis());
    }

    @Pointcut("execution(* *..*(..))")   //设置规则 切点
    public void pt(){
     

    }

//指定类型。只要满足该规则就执行该方法
//演示前置   
//其他的类型使用都一致 如果是
    @Before("pt()")
    public void findEnhance(){
     
        System.out.println("time is .."+System.currentTimeMillis());
    }


}

下列这些主要的注解中所存在的返回值之类的属性,可以点进去在该注解中去查看具体有哪些,一般名字与xml是一致的。

在使用的时候首先要明确,是需要用到什么功能,再选择相应的类型。类型不同功能大不相同

  • @Before

  • @After

  • @AfterThrow 例如 @AfterThrowing(throwing = “”,value = “”)

  • @AfterReturning

  • @Around() 如果在Around的通知类函数中不存在分割,那么不会执行原方法,仅执行环绕拦截的方法

如果规则太多,可以考虑把所有规则都移入到一个指定的类中存放,使用类名.方法名的方式指定切点

@Before("Kaishi.pt()")   //指定切点的格式
public void findEnhance(){
     
    System.out.println("time is .."+System.currentTimeMillis());
}

存放切点的类

public class Kaishi {
     
    @Pointcut("execution(* *..*(..))")   //设置规则 切点
    public void pt(){
     

    }
}

2)AOP注解驱动(纯注解)

要使用纯注解,那么要在SpringConfig的主映射 类上再额外加上该注解

@EnableAspectJAutoProxy

@Configuration
@ComponentScan(value = "wz",includeFilters = {
     @ComponentScan.Filter(   //此处的扫瞄指定了过滤器。
        type =FilterType.REGEX, classes = {
     Controller.class})
})
@ComponentScan("/wz")    //  正常就是直接扫描指定的包
@Import({
     JDBCConfig.class,MybatisConfig.class})
@PropertySource(value = {
     "classpath:jdbc.properties","classpath:demo.properties"})
@EnableAspectAutoProxy   //使用纯注解的AOP注册驱动
public class SpringConfig {
     

}

案例:监控业务层的性能

通知类

package AOPtest.AopCore;

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

/**
 * 目标:测试查询方法,使用100000次的查询速度是多久
 */
//交给spring管理
    @Component("aopAdvice")
//把该类作为AOP的通知类
    @Aspect
public class AOPFunctionAdvice {
     
    //定义通知类的规则切点
//    @Pointcut("execution(* AOPtest.AopCore.AOP*(..))")   //配置的切点规则如果太大会影响其他类的正常使用
    @Pointcut("execution(* AOPtest.service..*(..))")
    public void pt(){
     }

    //提供方法获取执行100000次的查询速度
    //定义该方法的切面类型
    @Around("pt()")
    public void getSpeed(ProceedingJoinPoint pjp) throws Throwable {
     
     //获得该方法的签名
        Signature signature = pjp.getSignature();
        //获取该调用方法的名字
        String name = signature.getName();
        //获得该方法的接口名
        String declaringTypeName = signature.getDeclaringTypeName();
        //测试该方法是什么
        Class declaringType = signature.getDeclaringType();

        System.out.println("getDeclaringType方法是:"+declaringType);

        //定义开始时间
        long timeStart=System.currentTimeMillis();

        //进行万次循环查询
        for(int i = 0 ;i < 10000;i++){
     
            pjp.proceed();
        }

        //定义结束时间
        long timeEnd = System.currentTimeMillis();
        //获得时间差
        long sum = timeEnd- timeStart;
        //打印信息
        System.out.println(declaringTypeName+":"+name+"   (万次)run:"+sum+"ms");
//        return sum;     // 通知类不可进行返回。只做增强处理
    }

}

业务层

public Account findById(Integer id) {
     
    return accountDao.findById(id);
}

测试类

此处使用的是全注解。
public class App {
     
    public static void main(String[] args) {
     
        AnnotationConfigApplicationContext m = new AnnotationConfigApplicationContext(SpringConfig.class);
        AccountService accountService = (AccountService) m.getBean("accountService");
        Account byId = accountService.findById(1);
        System.out.println(byId);
    }
}

测试数据库查询中存在一级缓存 查询id 9ms

											查询所有 900ms

但是在查询10000次的时候 消耗的时间又差不多了 为什么?

因为存在一级缓存 第一次查询的时候 该类型的查询就已经存在于内存中

之后的每一次查询都是从内存中进行读取,所以最终结果会差不多

比如在内存中每次获取都只需要1ms,那么不管是查询id还是查询全部读是1ms

注解的通知先后顺序

排序规则用Asc码表进行编译

​ 1.如果在一个类中 以方法作为基准,顺序以类的字母数字定义

​ 2.多个通知类 先以类名排 然后用方法名字排序

​ 3.再或者使用order注解进行排序

AOP的底层原理实现方式

静态代理

-----装饰者模式

动态代理

-----在使用AOP的时候,由于底层使用的是动态代理。

​ 当是在使用原生的JDK动态代理实现,那么返回的接收必须是接口类型

CGlib

---- 继承

Code生成类库

Spring 的织入时机

什么是织入?

​ 织入就是指对方法的切面操作。

​ 织入时机就是在Spring的底层中最终是在哪个时期进行织入的。 ----运行期织入

SpringAOP_第2张图片

运行期织入

​ Spring可以在这三种时期对其进行织入,但是最终选择了运行速度最慢的运行期织入。

​ 主要原因是因为足够灵活,每次程序被加载成字节码对象的时候都已经存在所有的方法,spring可以通过值的判定来执行方法,方法又由规则来确定是否被AOP拦截,这样就为其选择了不同的方法执行。虽然速度慢,但是足够灵活。不会在编译器或者加载器就已经锁定住一个方法等待执行。

ervice.findById(1);
System.out.println(byId);
}
}




测试数据库查询中存在一级缓存 查询id  9ms

  												查询所有 900ms

但是在查询10000次的时候 消耗的时间又差不多了  为什么?

因为存在一级缓存 第一次查询的时候 该类型的查询就已经存在于内存中

之后的每一次查询都是从内存中进行读取,所以最终结果会差不多

比如在内存中每次获取都只需要1ms,那么不管是查询id还是查询全部读是1ms





## 注解的通知先后顺序

排序规则用Asc码表进行编译

​	1.如果在一个类中  以方法作为基准,顺序以类的字母数字定义

​	2.多个通知类  先以类名排  然后用方法名字排序

​	3.再或者使用order注解进行排序



# AOP的底层原理实现方式

### 静态代理

 -----装饰者模式



### 动态代理

-----在使用AOP的时候,由于底层使用的是动态代理。

​	  当是在使用原生的JDK动态代理实现,那么返回的接收必须是接口类型

### CGlib

 ---- 继承

Code生成类库



# Spring 的织入时机     

### 什么是织入?

​	织入就是指对方法的切面操作。

​	织入时机就是在Spring的底层中最终是在哪个时期进行织入的。 ----运行期织入

[外链图片转存中...(img-OVmdlik9-1602507040384)]



### 运行期织入

​		Spring可以在这三种时期对其进行织入,但是最终选择了运行速度最慢的运行期织入。

​		主要原因是因为足够灵活,每次程序被加载成字节码对象的时候都已经存在所有的方法,spring可以通过值的判定来执行方法,方法又由规则来确定是否被AOP拦截,这样就为其选择了不同的方法执行。虽然速度慢,但是足够灵活。不会在编译器或者加载器就已经锁定住一个方法等待执行。

你可能感兴趣的:(SpringAOP)