Spring AOP(JDK,CGLIB代理)

AOP简介

我们在做某个业务的时候,可能需要对业务进行拓展,比如说我们做了一个查询业务,
功能上已经可以使用了,但是我们有了新的需求,需要对原有业务的功能进行扩充,比如
我们可能需要判断一下当前是否可以查询此业务(时段考试),我们也可能需要知道当前操作的一些信息,如操作人,操作时间,操作完成情况等来做日志管理。总之,我们需要对类中的方法进行功能扩展。
我们大概有以下几个方法:

  1. 直接在原方法中加入,这个自然是不合适的,先不说重复代码的问题,但是代码的优雅性就有大问题。
  2. 对该类做继承并进行方法的重写,来进行功能的扩展。这个方法自然是可行的。(继承的方式)
  3. 对实现类的接口进行一个新的实现方法去实现功能的扩展。(组合的方式,要求必须有接口)
    第二种方式就是我们的cglib代理,第三种则是spring默认的jdk代理。

JDK代理

jdk代理其实是通过反射实现的,具体的大家可以看代码:

//能否利用一个工厂动态为目标对象创建代理
public class JDKProxyFactory {

    //要求用户传递目标对象
    //关于匿名内部类用法说明:  匿名内部类引用外部参数 要求参数必须final修饰
    public static Object getProxy(final Object target){
        //1.调用java API实现动态代理
        /**
         *  参数分析: 3个参数
         *      1.ClassLoader loader, 类加载器(获取目标对象的Class)
         *      2.类<?>[] interfaces,  JDK代理要求 必须有接口
         *                             java中可以多实现
         *      3.InvocationHandler h  对目标方法进行扩展
         */
        //1.获取类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();
        //2.获取接口数组
        Class[] interfaces = target.getClass().getInterfaces();
        //3.通过动态代理创建对象
        Object proxy = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {

            //invoke方法: 代理对象调用方法时invoke执行,扩展方法的编辑位置
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //proxy: 代理对象本身
                //method: 用户调用的方法对象
                //args:   用户调用方法的参数

                // result 标识目标方法执行的返回值
                Object result = null;
                try {
                    //添加事务的控制
                    System.out.println("事务开始");
                    //执行目标方法
                    // target真实的目标对象,method方法对象,args方法参数
                    result = method.invoke(target,args);
                    System.out.println("事务提交");
                }catch (Exception e){
                    e.printStackTrace();
                    System.out.println("事务回滚");
                }
                return result;
            }
        });

        return proxy;
    }
}

CGLIB代理

这个代理是通过ASM字节码生成的框架,对当前类继承后重写方法实现的。这个网上的源码剖析很多,我懒得导那两个包了就不写了。
我们知道越底层的东西越快,所以CGLIB代理的创建速度是比JDK代理慢的,但不受实现类有没有接口的影响。

总结

1、CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高不少,有研究表明,大概要高10倍;

2、但是CGLib在创建对象的时候所花费的时间却比JDK动态代理要多很多,有研究表明,大概有8倍的差距;

3、因此,对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLib动态代理,反正,则比较适用JDK动态代理。

Spring中AOP的操作

配置:

@Configuration
@ComponentScan("com.study")
@EnableAspectJAutoProxy(proxyTargetClass=false) //启动AOP注解 创建代理对象
                        //默认启用JDK动态代理,
                        //目标对象没有实现接口时,则采用CGLIB
                        //强制使用cglib proxyTargetClass=true
                        //JDK代理创建速度快.运行时稍慢
                        //CGLIB创建时速度较慢,运行时更快
public class SpringConfig {
}

aspect:
面向切面编程,我们要有切入点和切入面,连接点,增强,目标对象,织入的了解。
连接点是我们所有可能会进行该方法的类。(为了便于理解,我们通常加一个注解来表明连接点);
切入点是真正的修饰,例如我们的一个类中的方法加入了连接点,表示该方法需要AOP,但是具体进行那种功能扩展要看他的切入点(在什么地方切入)
增强其实就是要扩展的功能。
切面就是相当于,切点加上增强的部分,面向切面编程指的就是找到切点,并编写增强。
织入是将切面与其他类对象连接的过程。

同时我们应该注意增强的功能不同,切入的时机是不同的,
@Before是调用方法前
@After是调用方法后,无论方法是否发生异常都会执行。
@AfterThrowing是抛出异常后
@AfterReturn是方法返回后
@Arround是以上的整个过程

package com.jt.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

//1.AOP需要被Spring容器管理
//@Component
//2.标识该类为AOP切面
//  Spring容器默认不能识别切面注解,需要手动配置
@Aspect
public class SpringAOP {

    //面向切面编程 = 切入点表达式(IF判断) + 通知方法
    /**
     * 切入点表达式练习
     * within:
     *  1.within(com.jt.*.DeptServiceImpl)   一级包下的类
     *  2.within(com.jt..*.DeptServiceImpl)  ..代表多级包下的类
     *  3.within(com.jt..*)  包下的所有的类
     *
     * execution(返回值类型 包名.类名.方法名(参数列表))
     *  1.execution(* com.jt..*.DeptServiceImpl.add*())
     *  注释: 返回值类型任意的, com.jt下的所有包中的DeptServiceImpl的类
     *        的add开头的方法 ,并且没有参数.
     *
     *  2.execution(* com.study..*.*(..))
     *  注释: 返回值类型任意,com.jt包下的所有包的所有类的所有方法 任意参数.
     *
     *  3.execution(int com.study..*.*(int))
     *  4.execution(Integer com.study..*.*(Integer))
     *  强调: 在Spring表达式中没有自动拆装箱功能! 注意参数类型
     *
     * @annotation(包名.注解名)
     *     @Before("@annotation(com.study.anno.Cache)")
     *    只拦截特定注解的内容.
     */


    //1.定义before通知
    //@Before("bean(deptServiceImpl)")
    //@Before("within(com.study..*)")
    //@Before("execution(* com.study..*.DeptServiceImpl.add*())")
    //@Before("@annotation(com.study.anno.Cache)")

    //1.定义切入点表达式  if判断
    @Pointcut("@annotation(com.study.anno.Cache)")
    public void pointcut(){

    }

    /*Spring为了AOP动态获取目标对象及方法中的数据,则通过joinPoint对象
    * 进行数据的传递.
    * getSignature : 方法签名  获取方法的参数
    * */
    @Before("pointcut()")
    public void before(JoinPoint joinPoint){
        System.out.println("获取目标对象的类型:"+joinPoint.getTarget().getClass());
        System.out.println("获取目标对象类名:"+joinPoint.getSignature().getDeclaringTypeName());
        System.out.println("获取目标对象方法名:"+joinPoint.getSignature().getName());
        System.out.println("获取方法参数:"+ Arrays.toString(joinPoint.getArgs()));
        System.out.println("我是before通知");
    }

    /**
     * 记录方法的方法返回值!!!
     * pointcut:  关联的切入点表达式
     * returning: 将方法的返回值,通过形参result进行传递
     * @AfterReturning(pointcut = "pointcut()",returning = "result")
     * 注意事项:
     *      如果参数中需要添加joinPoint 对象时,参数必须位于第一位.
     *      Spring在进行参数赋值时,采用index[0] 下标的方式赋值
     * 报错提示: ::0xxxx
     */
    @AfterReturning(pointcut = "pointcut()",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
        System.out.println(Arrays.toString(joinPoint.getArgs()));
        System.out.println("用户的返回值结果:"+result);
        System.out.println("我是afterReturning通知");
    }

    /*
    * throwing = "e" 动态接收程序运行时的报错信息,
    * 利用异常通知进行记录
    * */
    @AfterThrowing(pointcut = "pointcut()",throwing = "e")
    public void afterThrowing(Exception e){
        System.out.println("获取异常信息:"+e.getMessage());
        System.out.println("获取异常的类型:"+e.getClass());
        System.out.println("我是afterThrowing");

    }

    @After("pointcut()")
    public void after(){
        System.out.println("我是after通知");
    }

    /**
     * 关于环绕通知的说明
     * 作用: 可以控制目标方法是否执行.
     * 参数: ProceedingJoinPoint 通过proceed方法控制目标方法执行.
     * 注意事项:
     *      ProceedingJoinPoint 只能适用环绕通知
     * @return
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint){
        Object result = null;
        try {
            System.out.println("环绕通知开始");
            //1.执行下一个通知  2.执行目标方法 3.接收返回值
            Long start = System.currentTimeMillis();
            result = joinPoint.proceed();
            Long end = System.currentTimeMillis();
            System.out.println("耗时:"+(end-start));
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("环绕通知结束");
        return result;
    }
}

连接点:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//自定义注解   包括元注解
//注解的作用:  配合AOP进行注解类型 案例的训练   标识
//控制注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
//注解的作用对象  方法有效  类有效 TYPE
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD})
public @interface Cache {

}

切面:

@Service
public class DeptServiceImpl implements DeptService{

    @Override
    public void addDept() {
        System.out.println("添加部门信息");
    }

    @Override
    @Cache      //被注解标识
    public void updateDept() {
        System.out.println("更新部门");
    }

    @Override
    @Cache  //标识该方法需要执行切面
    public String after(Integer id) {

        return "Spring通知的测试";
    }

    //让该方法执行时 抛出异常
    @Override
    @Cache
    public void afterThrow() {
        System.out.println("用户执行目标方法");
        //手动抛出算数异常
        int a  = 1/0;
    }

    @Override
    @Cache  //标识执行AOP中的方法
    public void doAround() {
        System.out.println("实现用户数据的入库操作");
    }

    @Override
    @Cache
    public void doOrder() {
        System.out.println("测试程序执行的顺序");
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值