草庐IT

【Spring】一文带你吃透AOP面向切面编程技术(下篇)

几分醉意. 2023-04-05 原文

个人主页: 几分醉意的CSDN博客_传送门

上节我们介绍了什么是AOP、Aspectj框架的前置通知@Before传送门,这篇文章将继续详解Aspectj框架的其它注解。

文章目录

💖Aspectj框架介绍

AOP技术思想的实现:使用框架实现AOP。实现AOP的框架有很多。有名的两个

1. Spring:Spring框架实现AOP思想中的部分功能。Spring框架实现AOP的操作比较繁琐,笨重。
2. Aspectj:独立的框架,专门做AOp的,功能最强大的。属于Eclipse。

而我下面主要介绍的就是Aspectj框架来实现Aop,Aspectj框架可以使用注解和xml配置文件两种方式实现AOP。

✨JoinPoint通知方法的参数

切面类中的通知方法,可以有参数,但是必须是JoinPoint。
JoinPoint: 表示正在执行的业务方法。 相当于反射中 Method

使用要求:必须是参数列表的第一个
作用:获取方法执行时的信息,例如方法名称, 方法的参数集合

下面我们直接实战,注意下面用的是上一节的前置通知的业务接口和实现类。

切面类

@Aspect
public class MyAspect {
	@Before(value = "execution(* *..SomeServiceImpl.do*(..) )")
	    public void myBefore2(JoinPoint jp){
	        //获取方法的定义
	        System.out.println("前置通知中,获取目标方法的定义:"+ jp.getSignature());
	        System.out.println("前置通知中,获取方法的名称"+jp.getSignature().getName());
	        //获取方法执行时的参数
	        Object[] args = jp.getArgs(); //返回的是一个数组 里面存放的是所有参数
	        for (Object arg : args) {
	            System.out.println("前置通知,获取方法的参数是"+arg);
	        }
	
	        //切面的代码。
	        System.out.println("===前置通知,切面的功能,在目标方法之前先执行==:"+new Date());
	        System.out.println("");
	    }
	}

测试

@Test
    public void test(){
        //如果没有加入代理的处理:
        // 1)目标方法执行时,没有切面功能的。
        // 2) service对象没有被改变

        //加入代理的处理:
        // 1)目标方法执行时,有切面功能的。
        // 2) service对象是改变后的 代理对象 com.sun.proxy.$Proxy8
        String s = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(s);
        SomeService service = (SomeService)ctx.getBean("someService");
        service.doSome("ll" , 22);

    }
	//JoinPoint:哪个目标对象方法执行时,它就代表哪个方法
	//例如这里doSome执行时,它就代表doSome,然后可以获取这个方法的信息

执行结果:

拓展:

✨后置通知@AfterReturning


     * @AfterReturning:后置通知
     *    属性:value 切入点表达式
     *          returning 自定义的变量,表示目标方法的返回值的。
     *                    自定义变量名称必须和通知方法的形参名一样
     *    位置:在方法的上面
     *
     * 特点:
     *  1.在目标方法之后,执行的。
     *  2.能获取到目标方法的执行结果。
     *  3.不会影响目标方法的执行
     *
     * 方法的参数:
     *   Object res: 表示目标方法的返回值,使用res接收doOther的调用结果。
     *   Object res= doOther();
     *
     *  后置通知的执行顺序
     *  Object res = SomeServiceImpl.doOther(..);  先执行业务方法
     *  myAfterReturning(res); 在执行后置通知
     *
     *  思考:
     *    1 doOther方法返回是String ,Integer ,Long等基本类型,
     *      在后置通知中,修改返回值, 是不会影响目标方法的最后调用结果的。
     *    2 doOther返回的结果是对象类型,例如Student。
     *      在后置通知方法中,修改这个Student对象的属性值,会不会影响最后调用结果?
     *

下面通过举例的方式,带大家理解后置通知。

首先业务接口添加doOther方法,然后实现它的实现类

public interface SomeService {
    void doSome(String name,Integer age);
    String  doOther(String name,Integer age);
}
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome(String name, Integer age) {
        System.out.println("业务方法doSome(),创建商品订单");
    }

    @Override
    public String doOther(String name, Integer age) {
        System.out.println("执行业务方法doOther,处理库存");
        return "abcd";
    }
}

创建切面类

@Aspect
public class MyAspect {
    //定义方法,表示切面的具体功能
    /*
       后置通知方法的定义
       1)方法是public
       2)方法是void
       3)方法名称自定义
       4)方法有参数,推荐使用Object类型
     */
    
    @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",
            returning = "res")
    public void myAfterReturning(JoinPoint jp , Object res){
        System.out.println("后置通知,在目标方法之后,执行的。能拿到执行结果:"+res);

        //修改目标方法的返回值
        if(res != null){
            res = "HELLO Aspectj";
        }
        System.out.println("后置通知,修改res后"+res);
    }
}

测试:

@Test
    public void test02(){
        String config="applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        SomeService service = (SomeService) ctx.getBean("someService");
        String ret = service.doOther("zhangsan", 20);

        System.out.println("test02中调用目标方法的结果:"+ret);
    }

执行结果:

✨环绕通知@Around

特点介绍:





     * @Around:环绕通知
     *    属性:value 切入点表达式
     *    位置:在方法定义的上面
     *
     * 返回值:Object ,表示调用目标方法希望得到执行结果(不一定是目标方法自己的返回值)
     * 参数:  ProceedingJoinPoint, 相当于反射中 Method。
     *        作用:执行目标方法的,等于Method.invoke()
     *
     *        public interface ProceedingJoinPoint extends JoinPoint {}
     *
     * 特点:
     *  1.在目标方法的前和后都能增强功能
     *  2.控制目标方法是否执行
     *  3.修改目标方法的执行结果。
     *
@Aspect
public class MyAspect {
    //定义方法,表示切面的具体功能
    /*
       环绕置通知方法的定义
       1)方法是public
       2)方法是必须有返回值, 推荐使用Object类型
       3)方法名称自定义
       4)方法必须有ProceedingJoinPoint参数,
     */

    @Around("execution(* *..SomeServiceImpl.doFirst(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        Object methodReturn = null;
        System.out.println("执行了环绕通知,在目标方法之前,输出日志时间=="+ new Date());

        //执行目标方法  ProceedingJoinPoint,表示doFirst
        methodReturn = pjp.proceed();//method.invoke(),表示执行doFirst()方法本身

        if( methodReturn != null){
            methodReturn ="环绕通知中,修改目标方法原来的执行结果";
        }

        System.out.println("环绕通知,在目标方法之后,增加了事务提交功能");

        //return "HelloAround,不是目标方法的执行结果";
        //返回目标方法执行结果。没有修改的。
        return methodReturn;
    }


}

测试:

@Test
    public void test02(){
        String config="applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        SomeService service = (SomeService) ctx.getBean("someService");
        String ret = service.doFirst("zhangsan");
        System.out.println("ret调用目标方法的结果:"+ret);
    }

执行结果:

✨异常通知@AfterTrowing

业务接口和实现类:

public interface SomeService {
    void doSecond(String name);
}
@Service
public class SomeServiceImpl implements SomeService {
    
    @Override
    public void doSecond(String name) {
        System.out.println("执行业务方法doSecond,处理库存"+(10/0));
    }
}

切面类:

@Aspect
public class MyAspect {
    //定义方法,表示切面的具体功能
    /*
       异常通知方法的定义
       1)方法是public
       2)方法是没有返回值。是void
       3)方法名称自定义
       4)方法有参数是Exception
     */

    /**
     * @AfterThrowing:异常通知
     *     属性: value 切入点表达式
     *           throwing 自定义变量,表示目标方法抛出的异常。
     *                    变量名必须和通知方法的形参名一样
     *     位置:在方法的上面
     * 特点:
     *  1. 在目标方法抛出异常后执行的, 没有异常不执行
     *  2. 能获取到目标方法的异常信息。
     *  3. 不是异常处理程序。可以得到发生异常的通知, 可以发送邮件,短信通知开发人员。
     *      看做是目标方法的监控程序。
     *
     *  异常通知的执行
     *  try{
     *      SomeServiceImpl.doSecond(..)
     *  }catch(Exceptoin e){
     *      myAfterThrowing(e);
     *  }
     */
    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex")
    public void myAfterThrowing(Exception ex){
        System.out.println("异常通知,在目标方法抛出异常时执行的,异常原因是:"+ex.getMessage());
        /*
           异常发生可以做:
           1.记录异常的时间,位置,等信息。
           2.发送邮件,短信,通知开发人员
         */
    }

}

测试:

@Test
    public void test01(){
        String config="applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        SomeService service = (SomeService) ctx.getBean("someService");
        service.doSecond("lisi");
    }

执行结果:
✨ 最终通知@After

业务接口和实现类:

public interface SomeService {
    void doThird();
}
@Service
public class SomeServiceImpl implements SomeService {
    @Override
    public void doThird() {
        System.out.println("执行了业务方法doThird()");
    }
}

切面类:

@Aspect
public class MyAspect {
    //定义方法,表示切面的具体功能
    /*
       最终通知方法的定义
       1)方法是public
       2)方法是没有返回值。是void
       3)方法名称自定义
       4)方法没有参数
     */


    /**
     * @After:最终通知
     *    属性: value 切入点表达式
     *    位置: 在方法的上面
     * 特点:
     *  1. 在目标方法之后执行的。
     *  2. 总是会被执行。
     *  3. 可以用来做程序最后的收尾工作。例如清除临时数据,变量。 清理内存
     *
     *  最终通知
     *  try{
     *      SomeServiceImpl.doThird(..)
     *  }finally{
     *      myAfter()
     *  }
     */
    @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public void myAfter(){
        System.out.println("最终通知,总是会被执行的");
    }


}

测试:

    @Test
    public void test01(){
        String config="applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        SomeService service = (SomeService) ctx.getBean("someService");
        
        service.doThird();
    }

执行结果:

✨@Pointcut定义和管理切入点注解

@Aspect
public class MyAspect {

    @Before(value = "mypt()")
    public void myBefore(){
        System.out.println("前置通知,在目标方法之前先执行的");
    }

    @After(value = "mypt()")
    public void myAfter(){
        System.out.println("最终通知,总是会被执行的");
    }

    /**
     * @Pointcut: 定义和管理切入点,不是通知注解。
     *     属性: value 切入点表达式
     *     位置: 在一个自定义方法的上面, 这个方法看做是切入点表达式的别名。
     *           其他的通知注解中,可以使用方法名称,就表示使用这个切入点表达式了
     */
    @Pointcut("execution(* *..SomeServiceImpl.doThird(..))")
    private void mypt(){
        //无需代码
    }
}

✨总结

AOP是一种动态的技术思想,目的是实现业务功能和非业务功能的解耦合。业务功能是独立的模块,其他功能也是独立的模块。例如事务功能,日志等等。让这些事务,日志功能是可以被复用的。
当目标方法需要一些功能时,可以在不修改,不能修改源代码的情况下,使用aop技术在程序执行期间,生成代理对象,通过代理执行业务方法,同时增加功能。

💖投票传送门(欢迎伙伴们投票)

有关【Spring】一文带你吃透AOP面向切面编程技术(下篇)的更多相关文章

  1. ruby-on-rails - 带 Spring 锁的 Rails 4 控制台 - 2

    我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.

  2. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  3. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  4. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

  5. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  6. spring.profiles.active和spring.profiles.include的使用及区别说明 - 2

    转自:spring.profiles.active和spring.profiles.include的使用及区别说明下文笔者讲述spring.profiles.active和spring.profiles.include的区别简介说明,如下所示我们都知道,在日常开发中,开发|测试|生产环境都拥有不同的配置信息如:jdbc地址、ip、端口等此时为了避免每次都修改全部信息,我们则可以采用以上的属性处理此类异常spring.profiles.active属性例:配置文件,可使用以下方式定义application-${profile}.properties开发环境配置文件:application-dev

  7. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  8. ruby - 如何以编程方式删除实例上的 "singleton information"以使其编码(marshal)? - 2

    我创建了一个由于“在运行时执行的单例元类定义”而无法编码的对象(这段代码的描述是否正确?)。这是通过以下代码执行的:#defineclassXthatmyusesingletonclassmetaprogrammingfeatures#throughcallofmethod:break_marshalling!classXdefbreak_marshalling!meta_class=class我该怎么做才能使对象编码正确?是否可以从对象instance_of_x的classX中“移除”单例组件?我真的需要一个建议,因为我们的一些对象需要通过Marshal.dump序列化机制进行缓存。

  9. Ruby 元编程问题 - 2

    我正在查看Ruby日志记录库Logging.logger方法并从sourceatgithub提出问题与这段代码有关:logger=::Logging::Logger.new(name)logger.add_appendersappenderlogger.additive=falseclass我知道类 最佳答案 这实际上删除了方法(当它实际被执行时)。这是确保close不会被调用两次的保障措施。看起来好像有嵌套的“class 关于Ruby元编程问题,我们在StackOverflow上找到一

  10. ruby - Paperclip:以编程方式分配图像并设置其名称 - 2

    使用Paperclip,我想从这样的URL抓取图像:require'open-uri'user.photo=open(url)问题是我最后得到一个像“open-uri20110915-4852-1o7k5uw”这样的文件名。有什么方法可以更改user.photo上的文件名?作为一个额外的变化,Paperclip将我的文件存储在S3上,所以如果我可以在初始分配中设置我想要的文件名就更好了,这样图像就会上传到正确的S3key。像这样:user.photo=open(url),:filename=>URI.parse(url).path 最佳答案

随机推荐