Spring学习笔记5——什么是AOP

七、Spring AOP

AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,能隔离业务逻辑的各个部分,减少重复代码,达到解耦合,专注于核心业务,从而提高了开发效率
切面:新增功能,非业务功能,可独立使用
在写功能前我们要先确定“www”,好目标切面(who),而这个切面在什么时候执行(when),最后要确定好切面执行的位置(where)

AOP 实现原理就是动态代理,动态代理的实现方式有很多种,而AOP是动态代理的一种规范,让开发人员统一使用

目前最流行的 AOP 框架

  • Spring AOP(少用):使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码
  • AspectJ(开源):基于 Java 语言的 AOP 框架,AspectJ 扩展了 Java 语言,提供了强大的切面实现,在spring中有其集成
AOP专业术语:
  • Joinpoint(连接点):链接业务方法和切面的位置,就是某类种的业务方法
    指向业务方法(即目标方法),作用是可以在通知方法中获取执行时的信息,如方法名、方法实参;如果我们的切面功能需要用到这些信息,就加入JoinPoint;JoinPoint参数的值时由框架赋的,必须是第一位的参数

    @Before(value = "execution(public void com.spring.aop.ba01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore2(JoinPoint jp){
    
        //获取方法的完整定义
        System.out.println("方法的签名(定义)="+jp.getSignature());
        System.out.println("方法名称="+jp.getSignature().getName());
        //获取方法的实参
        Object o[]=jp.getArgs();
        for (Object oo: o) {
            System.out.println("参数="+oo);
        }
    
        System.out.println("切面功能-前置通知0:在目标方法前执行"+new Date());
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dwb0U9in-1595767961340)(https://ae01.alicdn.com/kf/H7325a1e6e7314028889aa6de6a7c68f72.jpg)]

    注意:先完成下面的aspectj的实现再加入JoinPoint
  • Pointcut(切入点):连接点的集合,表示切面的位置
    定义和管理切入点,当项目中有多个切入点表达式是重复的时候使用;自定义方法无需加入具体代码,只需加上@Pointcut注解和切入点表达式,其他地方要用到时,直接写自定义方法名即可
    属性:
    value 切入点表达式
    例子:

    @Aspect
    public class MyAspect {
    
    @After(value = "mypt()")
    public void myAfter(){
        System.out.println("最终通知:总是会被执行");
        //一般做资源的清楚工作
    }
    
    @Before(value = "mypt()")
    public void myBefore(){
        System.out.println("前置通知:在目标方法前执行执行");
    }
    
    @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
    private void mypt(){
        //无需具体代码
        }
    }
    
  • Advice(通知):执行切面的时间,表示切面执行的时间

  • Target(目标):指代理的目标对象

  • Weaving(植入):指把增强代码应用到目标上,生成代理对象的过程

  • Proxy(代理):指生成的代理对象

  • Aspect(切面):要增强的功能,常见的功能有日志、事务、统计信息、参数检查和权限验证

AspectJ实现aop

表示切面的执行时间(Advice)的注解(也可以使用xml中的标签):

  • @Before前置通知:下面有具体例子见下

  • @AfterReturning后置通知:
    属性:
    1. value:切入点表达式
    2.returning 自定义变量,表示目标方法的返回值,自定义变量名必须和通知方法的形参名一致
    位置:方法定义上
    功能:在目标方法后执行,获取目标方法的返回值,可以根据这个返回值做不同的处理方法,可以修改这个返回值

    方法有参数,推荐用Object,参数名自定义
    public class SomeServiceImpl implements SomeService {//目标类
    
    @Override
    public String doOther(String name, Integer age) {
        System.out.println("这是目标类SomeService的doOther()...");
        return "abcd";
        }   
    }
    
    @Aspect
    public class MyAspect {//切面类
    
    @AfterReturning(value ="execution(* *..SomeServiceImpl.doOther(..))" ,returning = "res")
    public void myAfterReturning(Object res){
    
        //Object res:是目标方法执行后的返回值,根据返回值我们可以做相应的切面处理
        System.out.println("后置通知,在目标方法后执行,获取的返回值是:"+res);
    
     }
    }
    
    public class MyTest02 {//测试类
    @Test
    public void test01(){
        String con="applicationContext.xml";
        ApplicationContext ac=new ClassPathXmlApplicationContext(con);
        SomeService ss= (SomeService) ac.getBean("someService");
    
        ss.doOther("pp",2);
        //ss.doOther2("0",0);
        }
    }
    

    执行结果

  • @Around
    环绕通知------最强的通知:public、有返回值(推荐用Object)、属性value:切入点表达式;方法名称自定义,方法有参数,固定参数:ProceedingJoinPoint
    功能:
    1、在目标方法前后执行
    2、控制目标方法是否被调用
    3、影响目标方法:修改目标方法的结果

    等同于jdk动态代理的InvocationHandler;参数=Method=执行目标方法;返回值=目标方法的执行结果,可修改
    @Aspect
    public class MyAspect {
    
    @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
    
        Object result=null;
    
        System.out.println("环绕通知-执行前加时间功能=="+new Date());
        //目标方法调用    等同于method.invoke();  Object result=doFirst()
        String nname=null;
    
        Object args[]=pjp.getArgs();
        if(args!=null && args.length>1){
            Object arg=args[0];
            nname= (String) arg;
        }
        //判断什么条件执行业务方法
        if(nname=="环绕小姐"){
            result=pjp.proceed();
        }else {
            System.out.println("你不是环绕小姐!!!");
        }
    
      System.out.println("环绕通知-执行后执行功能");
      //在目标方法前后增强功能
    
      if(result != null){
          result="改变目标方法的返回结果";
      }
      return result;
      }
    

    }

  • @AfterThrowing(少用)
    异常通知:
    属性:1、value切入点表达式
    2、throwing自定义变量,表示目标方法抛出的异常对象,变量名必须和方法的参数一致
    功能:
    1、在目标方法抛出异常时执行
    2、可以做目标方法的监控程序:有异常可以发右键、短信来通知
    参数:本身有一个throwing,也可以再加一个JoinPoint
    执行原理:

    try{
         SomeServiceImpl.doSecond(..)
     }catch{Exception e{
         myAfterThrowing(e)
     }
    

    例子:

    @Aspect
    public class MyAspect {
    
    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex")
    public void myAfterThrowing(Exception ex){
        System.out.println("异常通知:程序出异常啦"+ex.getMessage());
        //可以发邮件、短信...
        }
    }
    
    记得要让业务方法产生异常才能看见效果
  • @After(少用)
    最终通知: public、void、方法名称自定义、无参(可加JoinPoint)
    功能:总是会执行(在目标方法之后),哪怕程序报异常也输出
    实现原理:

    try{
         SomeServiceImpl.doThird(..)
    }catch(Exception e){
    }finally{
        myAfter()
    }
    

    例子:

    @Aspect
    public class MyAspect {
    
    @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public void myAfter(){
        System.out.println("最终通知:总是会被执行");
        //一般做资源的清楚工作
        }
    }
    
    可以试试发生异常时这个方法是否执行~

使用切面表达式来表示切面的位置:

用切面表达式匹配目标方法的方法名,所以execution表达式就是方法的前面,表达式中每个部分用空格隔开,在表达式中可以使用通配符

1、*表示0~n个任意字符
2、…用在方法参数时,表示任意多个参数;用在包名后,表示当前包及其子包path
3、+用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类
例子:

  • execution(public * *(..))表示公共任意方法
  • execution(public * set*(..))表示公共的以“set”开头的方法
  • execution(public * xx.yy.*.*(..))表示xx.yy下任意类的任意方法,不包括xx.yy的子包
  • execution(public * xx.yy.zz.*.*(..))表示xx.yy.zz下任意类的任意方法,包括xx.yy的* 子包
  • execution(public * *..zz*.*(..))表示所有包下任意类的任意方法
实现aspectj步骤(使用@Before):
  1. 创建maven项目
  2. 加入依赖
  • spring依赖
  • aspectj依赖
  • junit依赖
  1. 创建目标类(需要增强方法):接口及其实现类
public class SomeServiceImpl implements SomeService {//目标类
    @Override
    public void doSome(String name, Integer age) {//在方法执行前输出当前时间
        System.out.println("这是目标类SomeService的doSome()...");
        System.out.println(name+"-->"+age);
    }
}
  1. 创建切面类:普通类
  • 在类上加@Aspect
  • 在类中定义方法(要执行的功能代码),在上面加通知注解,如@Before;有需要指定切入点表达式execution()

/**
 * @Aspect:是aspectj框架中的注解
 * 表示当前类为切面类
 * 位于类定义的上面
 */
@Aspect
public class MyAspect {

    /**
     * 定义切面功能:即要增强的功能
     * 要求:
     * 1、public
     * 2、void
     * 3、名称自定义
     * 4、参数有无均可:
     * 有参时:参数非自定义,有几个参数类型可以使用
     */


    /**
     *@Before:前置通知注解
     * 属性value:切入点表达式,表明增强功能的执行位置
     * 位于方法定义上
     * 功能:让增强功能在目标功能前执行并且不影响目标方法的执行
     * 注意:方法中的参数只需写上参数类型,无需写参数名  doSome(String,Integer)
     */
    @Before(value = "execution(public void com.spring.aop.ba01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore(){
        System.out.println("切面功能-前置通知:在目标方法前执行"+new Date());
    }
    
    //极简版,可以有多个增强功能
    @Before(value = "execution(* *..SomeServiceImpl.do*(..))")
    public void myBefore1(){
        System.out.println("切面功能-前置通知1:在目标方法前执行"+new Date());
    }
}
  1. 创建spring配置文件:声明对象,把对象的创建交给容器管理(声明对象可用注解/xml配置文件)
  • 声明目标对象
  • 声明切面类对象
  • 声明aspectj的自动代理生成器标签(完成代理对象的自动创建)
<!--目标对象-->
    <bean id="someService" class="com.spring.aop.ba01.SomeServiceImpl"/>

    <!--切面对象-->
    <bean id="myAspect" class="com.spring.aop.ba01.MyAspect"/>

    <!--
        自动代理生成器:使用aspectj框架内部的功能,创建目标的代理对象
        其过程时在内存中实现的,修改目标对象在内存中的结构,创建为代理对象
        so,目标对象就是被修改后的代理对象

        aspectj-autoProxy会把容器中所有目标对象一次性都生成代理对象
    -->
    <aop:aspectj-autoproxy/>
  1. 创建测试类,从容器中获取目标对象(即代理对象),通过代理执行方法,实现aop功能增强
public class MyTest01 {

    @Test
    public void test01(){
        String con="applicationContext.xml";
        ApplicationContext ac=new ClassPathXmlApplicationContext(con);
        SomeService ss= (SomeService) ac.getBean("someService");

        ss.doSome("tt",1);
    }
}

jdk动态代理

指不改动原先的类,对该类进行增强,实现新的功能;要求目标对象必须实现接口;java通过java.lang.reflect包提供三个类支持代理模式Proxy、Method和InovcationHandler
实现步骤:

  1. 创建目标类
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome() {
        //ServiceTools.doLog();
        System.out.println("执行业务方法doSome()");
    }

    @Override
    public void doOther() {
        //ServiceTools.doLog();
        System.out.println("执行业务方法doOther()");
    }
}
//这是要加入的新功能
public class ServiceTools {
    public static void doLog(){
        System.out.println("现在的时间=="+new Date());
    }

    public static void doLove(){
        System.out.println("收尾功能--");
    }
}
  1. 创建InvocationHandler接口的实现类,在这个类中实现目标方法(增加功能)
public class MyInvocationHandler implements InvocationHandler {
    //目标对象
    private Object target;
    //要写构造方法,不然调用时,没有目标对象传进来
    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object res=null;
        //主方法前的显示时间方法

        System.out.println("method的名称:"+method.getName());

        //要求doSome()才有新增的方法
        String methodName=method.getName();
        if("doSome".equals(methodName)){
            ServiceTools.doLog();
            //通过Mehod类来执行目标类的方法:doSome()、doOther
            res=method.invoke(target,args);
            //主方法后的收尾方法
            ServiceTools.doLove();
        }else{
            res=method.invoke(target,args);
        }
        return res;
    }
}
  1. 使用jsk的Proxy类,创建代理对象,实现创建对象的能力
public class MyApp {
    public static void main(String[] args) {

        //创建目标对象
        SomeService target=new SomeServiceImpl();
        //创建InvocationHandler对象,注意要传入目标对象
        InvocationHandler ih=new MyInvocationHandler(target);
        //参数  类加载器、实现接口、目标代理类
        SomeService proxy= (SomeService) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),ih);
        //调用方法
        proxy.doSome();
        System.out.println("===================");
        proxy.doOther();

    }
}

CGLIB动态代理

要求目标类能被继承,即不能是final类

关于代理
  1. 当目标类是实现接口时,spring默认用的是jdk的动态代理
  2. 当目标类没有实现接口时,spring会自动转换成cglib框架进行动态代理
  3. 当目标类实现了接口,但是又想让项目用cglib框架时,只需把xml配置文件中的<aop:aspectj-autoproxy />改成<aop:aspectj-autoproxy proxy-target-class="true"/>即可
查看代理对象:xx.getClass().getName();

条条:该学习笔记是记录了我的学习过程,学习自动力节点c语言中文网,有不对的地方欢迎指出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值