SSM框架基础|spring的AOP思想(动态代理)

1、spring的AOP面向切面编程的思想
核心思想:在不改变原有代码的基础上,添加其他的功能;即把原来的代码调过来,然后再原来代码的基础上,在其前面或者后面添加额外的功能;
场景:权限控制、缓存、日志处理、事务控制;
拦截器也是aop思想的一种利用;
利用aop的代码中,一般分为两部分核心与非核心;核心部分一般就是原来的代码,非核心就是通过切入点,额外加入的功能;
spring的AOP 本质是一种动态代理,分为JDK动态代理(实现了接口) 和CGlab动态代理两种;

2、核心概念:横切、通知、连接点、切入点、切面
SSM框架基础|spring的AOP思想(动态代理)
文章图片

advice通知的类型:
  • @Before前置通知
    • 在执行目标方法之前运行
  • @After后置通知
    • 在目标方法运行结束之后
  • @AfterReturning返回通知
    • 在目标方法正常返回值后运行
  • @AfterThrowing异常通知
    • 在目标方法出现异常后运行
  • @Around环绕通知
    • 在目标方法完成前、后做增强处理 ,环绕通知是最重要的通知类型 ,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint,需要手动执行 joinPoint.procced()
3、aop的示例
整体而言,要使用aop,首先需要定义一个切面类,在切面类里面定义出切入点和通知,其中切入点就是在什么地方插入,通知就是插入之后在这个位置做什么事情;
切入点需要通过切入点表达式来定义,是一个正则表达式;
通知即前面提到的 before、after等;
示例:在VideoOrderService类中利用AOP编程;
【SSM框架基础|spring的AOP思想(动态代理)】目标类:
VideoOrderService{ //新增订单 addOrder(){ } //查询订单 findOrderById(){} //删除订单 delOrder(){} //更新订单 updateOrder(){} }

切面编程
//权限切面类 = 切入点+通知 PermissionAspects{//切入点定义了什么地方 @Pointcut("execution(public int net.xdclass.sp.service.VideoOrderService.*(..))") public void pointCut(){ }//before 通知 表示在目标方法执行前切入, 并指定在哪个方法前切入 //什么时候,做什么事情 @Before("pointCut()") public void permissionCheck(){System.out.println("在 xxx 之前执行权限校验"); } .... }

4、切入点表达式
切入点表达式类似于一个正则表达式;
访问修饰符返回值类型(必填)包和类方法(必填) execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(parampattern)throws-pattern?)

  • @Pointcut("execution(public int net.xdclass.sp.service.VideoOrderService.*(..))")
  • 例如上面的;
  • public是访问修饰符(一般省略);
  • int 返回值类型
  • net.xdclass.sp.service.VideoOrderService包名和类名
  • *表示这个类里面的任意一个方法;
  • ..匹配任何数量字符,可以多个,在类型模式中匹配任何数量子包;在方法参数模式中匹配任何数量参数
    () 匹配一个不接受任何参数的方法 (..) 匹配一个接受任意数量参数的方法 (*) 匹配了一个接受一个任何类型的参数的方法 (*,Integer) 匹配了一个接受两个参数的方法,其中第一个参数是任意类型,第二个参数必须是Integer类型

  • 常见的切入点表达式:
SSM框架基础|spring的AOP思想(动态代理)
文章图片

5、spring动态代理和静态代理
静态代理 ---接口
动态代理-- 反射实现---JDK动态代理+ CGlab动态代理;
5.1 JDK动态代理的实现:
通过反射机制在程序运行的时候动态实现的,需要目标类实现了一个接口;
public class JdkProxy implements InvocationHandler {//目标类,需要代理的类 private Object targetObject; //获取代理对象将代理类和实际的目标类绑定 public Object newProxyInstance(Object targetObject){ this. targetObject = targetObject; //绑定关系,也就是和具体的哪个实现类关联 returnProxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(),this); }public Object invoke(Object proxy, Method method, Object[] args) {Object result = null; try{ System.out.println("通过JDK动态代理调用 "+method.getName() +", 打印日志 begin"); result = method.invoke(targetObject,args); // 这一句实现的是被代理的方法原来的逻辑,可以在此处对原来返回的结果做进一步的修改System.out.println("通过JDK动态代理调用 "+method.getName() +", 打印日志 end"); }catch (Exception e){e.printStackTrace(); }return result; } }

其核心是实现InvocationHandler这个接口,并且重写里面的invoke方法,这个方法需要传入三个参数,第一个是需要代理的类,第二个是这个类的里面的一个方法,第三个是方法里面的参数。
// 使用上面的动态代理 JdkProxy jdkProxy = new JdkProxy(); // 获取代理类对象 传入的参数就是targetObject ,也就是需要被代理的类 PayService payServiceProxy = (PayService)jdkProxy.newProxyInstance(new PayServiceImpl()); // 调用目标方法 payServiceProxy.callback("aaa"); // 此时调用目标方法后还会执行 result = method.invoke(targetObject,args); 这一句前后的内容

使用Jdk
动态代理的时候,可以套用上面的模板,只需要修改 result = method.invoke(targetObject,args); 这一句前后的内容,加上需要的逻辑,本质跟静态代理一样,都是在目标方法加入一定的逻辑,只是动态代理是通过反射实现
SSM框架基础|spring的AOP思想(动态代理)
文章图片

第二行就是 方法被代理前返回的结果,第一和第三行 是新加入的内容
5.2CGlab动态代理点实现

如果代理类没有实现接口的时候,用CGlab动态代理;
  • CgLib动态代理的原理是对指定的业务类生成一个子类,并覆盖其中的业务方法来实现代理
这个代理需要用了spring的包才可以用,或者单独导入cglab的包;
需要实现springfromwork包下的 MethodInterceptor接口;

public class CglibProxyimplements MethodInterceptor {//目标类 private Object targetObject; //绑定关系 public Object newProxyInstance(Object targetObject){ this.targetObject = targetObject; Enhancer enhancer = new Enhancer(); //设置代理类的父类(目标类) enhancer.setSuperclass(this.targetObject.getClass()); //设置回调函数 enhancer.setCallback(this); //创建子类(代理对象serviceImpl) return enhancer.create(); }/** 通过拦截实现*/public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {Object result = null; try{System.out.println("通过CGLIB动态代理调用 "+method.getName() +", 打印日志 begin"); result = methodProxy.invokeSuper(o,args); // 调用父类,即执行的是父类中的方法System.out.println("通过CGLIB动态代理调用 "+method.getName() +", 打印日志 begin"); }catch (Exception e){ e.printStackTrace(); }return result; }}

CglibProxy cglabProxy = new CglibProxy(); OayService payservice = (PayService) cglibProxy.newProxyInstance(new PayServiceImpl()); // 调用目标方法payservice.save()

  • 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理,解耦和易维护
  • 两种动态代理的区别:
    • JDK动态代理:要求目标对象实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以用CGLib动态代理
    • CGLib动态代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展
    • JDK动态代理是自带的,CGlib需要引入第三方包
    • CGLib动态代理基于继承来实现代理,所以无法对final类、private方法和static方法实现代理
  • Spring AOP中的代理使用的默认策略:
    • 如果目标对象实现了接口,则默认采用JDK动态代理
    • 如果目标对象没有实现接口,则采用CgLib进行动态代理
    • 如果目标对象实现了接扣,程序里面依旧可以指定使用CGlib动态代理
6、利用注解实现AOP面向切面编程
一般是通过单独写一个切面类,然后再切面类里面写入点和通知;
简易模板:
@Component //告诉spring,这个一个切面类,里面可以定义切入点和通知 @Aspect public class LogAdvice {//切入点表达式 @Pointcut("execution(* net.xdclass.sp.service.VideoServiceImpl.*(..))") public void aspect(){}//前置通知 @Before("aspect()") //@Before("execution(* net.xdclass.sp.service.VideoServiceImpl.*(..))") public void beforeLog(JoinPoint joinPoint){ System.out.println("LogAdvicebeforeLog"); }//后置通知 @After("aspect()") public void afterLog(JoinPoint joinPoint){ System.out.println("LogAdviceafterLog"); }/** * 环绕通知 * @param joinPoint */ @Around("aspect()") public void around(JoinPoint joinPoint){Object target = joinPoint.getTarget().getClass().getName(); System.out.println("调用者="+target); //目标方法签名 System.out.println("调用方法="+joinPoint.getSignature()); //通过joinPoint获取参数 Object [] args = joinPoint.getArgs(); System.out.println("参数="+args[0]); long start = System.currentTimeMillis(); System.out.println("环绕通知 环绕前========="); //执行连接点的方法 try { ((ProceedingJoinPoint)joinPoint).proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); }long end = System.currentTimeMillis(); System.out.println("环绕通知 环绕后========="); System.out.println("调用方法总耗时 time = " + (end - start) +" ms"); }}

@EnableAspectJAutoProxy //启动类上开启spring对aspect的支持
可以通过切面来统计目标方法的性能;

    推荐阅读