Spring框架——入门3(动态代理、AOP、SpringAOP)

一、动态代理

先定义代理类的生成逻辑,在运行时动态生成代理类。

1.特点

在class运行期间,字节码随用随创建,随用随加载。

2.作用

不修改源码的基础上对方法增强。

3.分类
1)基于接口的动态代理(被代理类必须要实现接口)
提供者: JDK官方
涉及的类: Proxy
创建代理对象: 使用Proxy类中的newProxyInstance方法,要求被代理类最少实现一个接口,如果没有则不能使用。
具体实现:
final Producer producer = new Producer();
        IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 作用:执行被代理对象的任何接口方法都会经过该方法
                     * @param proxy
                     * @param method
                     * @param args
                     * @return
                     * @throws Throwable
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        /*TODO:提供增强的代码
                        1.拿到要增强的方法
                        2.实现增强的逻辑
                        3.method.invoke(args)
                        */
                        //例子:
                        Object returnVlue = null;
                        //1.获取方法执行参数
                        //通过下标方式获取
                        Float money = (Float)args[0];
                        //2.判断当前方法是不是需要的方法
                        if("saleProduct".equals(method.getName())){
                        	//找到要增强的方法,写入增强逻辑,调用增强后的方法
                            returnVlue=  method.invoke(producer,money*0.8f);
                        }
                        return returnVlue;
                    }
                });
        proxyProducer.saleProduct(10000f);
解释

newProxyInstance方法的参数有

  1. ClassLoader: 类加载器,它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。(固定写法,代理的是谁就是谁的类加载器)

  2. Class[]: 字节码数组,用于让代理对象和被代理对象有相同的方法。(固定写法,代理谁就写谁的接口就行)

  3. InvocationHandler: 用于提供增强的代码,让我们写如何代理,一般都是写一个该接口的实现类,通常都是匿名内部类。在InvocationHandler中要重写invoke方法

执行被代理对象的任何接口方法都会经过invoke方法 ,通过使用invoke的形参可以对被代理类中的方法进行改造。

invoke方法参数 及 返回值如下

  1. Object proxy: 代理对象的引用。
  2. Method method: 代理对象接口当前执行的方法。
  3. Object[ ] args: 当前执行方法所需的参数,可以通过数组下标获取。与原方法参数位置要一致。
  4. 返回值: 和被代理对象方法有相同的返回值。
2)基于子类的动态代理
提供者: 第三方cglib库(需要导入依赖)
涉及的类: Enhancer
创建代理对象: Enhancer类中的create方法,要求被代理类不能是最终类(最终类:加final关键字,不能被继承的)。
具体实现:

与jdk提供的差不多。

 public static void main(String[] args) {
        final Producer producer = new Producer();
        Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object returnValue = null;
                Float money = (Float) objects[0];

                if ("saleProduct".equals(method.getName())) {
                    returnValue = method.invoke(producer, money * 0.8f);
                }
                return returnValue;
            }
        });
        cglibProducer.saleProduct(122);
    }
解释:

create方法的参数有:

  1. Class: 字节码,用于指定被代理对象的字节码。要想代理谁就写谁的字节码。
  2. Callback: 用于提供增强的代码,我们一般使用该接口的子接口实现类,MethodInterceptor。在该实现类中重写intercept方法。

与proxy类似,执行被代理对象的任何方法都会经过intercept方法

intercept方法参数 及 返回值如下

  1. Object o: 代理对象的引用。
  2. Method method: 代理对象接口当前执行的方法。
  3. Object[ ] objects: 当前执行方法所需的参数,可以通过数组下标获取。与原方法参数位置要一致。
  4. MethodProxy methodProxy: 当前执行方法的代理对象。
  5. 返回值: 和被代理对象方法有相同的返回值。

二、AOP的概念

1.什么是AOP

面向切面编程,通过预编译方法和运行期动态代理实现程序功能统一维护的技术。
就是把程序重复的代码提取出来,在需要执行的时候,使用动态代理技术,在不修改源码的基础上,对已有方法进行增强。

2.优势

减少了重复代码、提高开发效率、维护方便。

3.实现

动态代理技术,基于接口,基于子类都可以。

三、Spring中AOP的相关术语在这里插入图片描述

Spring可以基于配置的方式,实现上述功能。

  1. Joinpoint(连接点)
    指被拦截到的点,在Spring中指的是方法,因为,Spring只支持方法类型的连接点。也就是被代理类中的所有方法。
  2. Pointcut(切入点)
    指要对哪些连接点进行拦截的定义。就是那些被代理类中的被增强的方法。
  3. Advice(通知/增强)
    指拦截到连接点后要做的事情。也就是动态代理中增强的逻辑。分为前置通知,后置通知,异常通知,最终通知,环绕通知。以invoke()方法为节点分类各种通知。
  4. Target(目标对象)
    指被代理对象。
  5. Waving(织入)
    指把增强应用到目标对象创建新对象的过程。返回代理对象的过程
  6. Proxy(代理)
    一个类AOP织入增强后,产生一个结果代理类。返回的代理对象
  7. Aspect(切面)
    是切入点和通知的结合。

四、Spring中基于XML和注解的AOP配置

1.基于xml的AOP配置
1)步骤
  • step1:在xml配置文件中加入aop的约束。
  • step2:把通知的bean交给spring管理。
  • step3:使用aop:config标签表明开始AOP的配置。
  • step4:使用aop:aspect标签表明开始配置切面。id属性给切面提供一个唯一标志,ref属性指定通知类bean的id。
  • step5:在aop:aspect标签的内部使用对应的标签来配置通知的类型,例如:前置通知使用aop:before标签,method属性用于指定通知类中哪个方法是对应通知。
  • step6:使用pointcut属性用于指定切入点表达式,该表达式的含义是指对业务层哪些方法增强 。
  • 切入点表达式写法:execution(表达式)
    表达式:访问修饰符 返回值 包名.类名.方法名(参数列表)
    例如:pointcut = execution(public void com.Yu.Service.saveService())
    在这里插入图片描述
2)切入点表达式

需要导入aspectj依赖,帮助我们解析切入点表达式。

标准写法访问修饰符 返回值 包名.类名.方法名(参数列表)
例如:public void com.Yu.Service.saveService()

全通配写法* *..*.*(..) (慎用)

规则:

  1. 访问修饰符是可以省略的。
  2. 返回值可以使用通配符表示任意返回值。
  3. 包名可以使用通配符表示任意包,但有几级包,就要写几个 *.。包名也可以使用 .. 表示当前包及其子包。
  4. 类名和方法名都可以使用*来实现通配。
  5. 参数列表
    如果是基本数据类型可以直接写直接写数据类型,引用类型写包名.类的方法;
    也可以使用通配符表示任意类型,但必须有参数;
    可以使用..表示有无参数均可,有参数可以是任意类型。

实际开发中要切到业务层实现类下的所有方法

通用切入点表达式
<aop:aspect>标签内可以配置切入点表达式,其中的expression属性用于指定表达式内容,配置完成后,当通知要使用表达式时,只需要给pointcut-ref属性传入配好的id即可。

例子:

<aop:aspect id="???" ref="???">
	<aop:before method="..." pointcut-ref="p1"/>
	<aop:point id="p1" expression=execution(* com.Yu.service.*.*(..))/>
<aop:aspect id="???" ref="???">

注意:如果aop:aspect写在aop:aspect标签外部表示所有切面可用(但必须出现在aop:aspect之前),内部表示当前切面可用。

3)常用的通知类型

在这里插入图片描述

前置通知: 在目标类(切入点)方法执行前执行。

<aop:before method="???" pointcut="excution(??)"></aop:before>

后置通知: 在目标类方法执行之后执行。

<aop:after-returing method="???" pointcut="excution(??)"></aop:before>

注意:它和异常通知永远只能执行一个,不能都执行。

异常通知: 当目标类方法抛出异常时执行,它不会捕获原方法抛出的异常。

<aop:throwing method="" pointcut="excution(??)"></aop:before>

注意: 如果在原方法中对异常使用了try{}catch(){},就不会触发异常通知

④最终通知: 在目标类的方法执行之后,如果程序出现了异常,最终通知也会执行。

<aop:after method="" pointcut="excution(??)"></aop:before>

⑤环绕通知: spring框架提供的一种可以在代码中手动控制增强方法何时执行的方法。
在通知类的环绕通知中,传入参数ProceedingJoinPoint 类型的参数,就可以通过该参数拿到方法执行所需的参数等,再通过proceed方法明确调用业务层方法(切入点方法)。
例子:

public Object aroundPringLog(ProceedingJoinPoint pjp){
            Object rtValue = null;
            try{
                Object[] args = pjp.getArgs();//得到方法执行所需的参数
 
                System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
 
                rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
 
                System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
 
                return rtValue;
            }catch (Throwable t){
                System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
                throw new RuntimeException(t);
            }finally {
                System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
            }
     }

2.基于注解的AOP配置(※※)

** 先在配置文件中写上Spring开启注解AOP的支持,如果想用纯注解,只需要在通知类上加入

<context:aspectj-autoproxy/>
0)@EnableAspectJAutpProxy

作用: 用纯注解实现AOP时,添加在通知类上。

1)@Aspect

作用: 表示当前类是一个切面类
使用对象:

2)@Pointcut("切入点表达式")

使用:表示方法是一个切入点

3)@Before()

作用:方法是前置通知

4)@AfterReturning()

作用:方法是后置通知

5)@AfterThrowing()

作用:方法是异常通知

6)@After()

作用:方法是最终通知

7)@Around()

作用:方法是环绕通知

使用例子:

@Aspect
        public class AnnotationAudienceAround{
            //使用@Pointcut注解声明切入点表达式
            @Pointcut("execution(* com.qin.util.*.*(..))")
            public void pt1(){}

			@Before("pt1()")
			public void beforePrintLog(){sout("前置通知Logger类beforePrintLog方法开始了")} 
			@AfterRetruning("pt1()")
			public void afterRetruningLog(){sout("后置通知Logger类afterRetruningLog方法开始了")} 
			@AfterThrowing("pt1()")
			public void afterThrowing(){sout("异常通知Logger类afterThrowing方法开始了")} 
			@After()("pt1()")
			public void afterPrintLog(){sout("最终通知Logger类afterPrintLog方法开始了")} 

	//※使用注解方法应用AOP时记得放在环绕通知里,因为SPring的bug导致通知执行的过程是乱序的。
			@Around("pt1()")
			public Object aroundPringLog(ProceedingJoinPoint pjp){
            Object rtValue = null;
            try{
                Object[] args = pjp.getArgs();//得到方法执行所需的参数
 
                System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
 
                rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
 
                System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");

                return rtValue;
            }catch (Throwable t){
                System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
                throw new RuntimeException(t);
            }finally {
                System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
            }
       }
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值