• 再谈Spring AOP


    1、AOP的基本概念

    在进行AOP开发前,先熟悉几个概念:

    连接点(Jointpoint):表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等,Spring只支持方法执行连接点。程序执行过程中明确的点,一般是方法的调用。AOP中表示为“在哪里干”

    切入点(Pointcut):选择一组相关连接点的模式,即可以认为连接点的集合。就是带有通知的连接点,在程序中主要体现为书写切入点表达式。Spring支持perl5正则表达式和AspectJ切入点模式,Spring默认使用AspectJ语法,AOP中表示为“在哪里干的集合”

    通知(Advice):在连接点上执行的行为,通知提供了在AOP中需要在切入点所选择的连接点处进行扩展现有行为的手段;包括前置通知(before advice)、后置通知(after advice)、环绕通知(around advice)。AOP在特定的切入点上执行的增强处理。在Spring中通过代理模式实现AOP,并通过拦截器模式以环绕连接点的拦截器链织入通知;AOP中表示为“干什么”;

    方面/切面(Aspect):横切关注点的模块化,比如上边提到的日志组件。通常是一个类,里面可以定义切入点和通知。可以认为是通知、引入和切入点的组合;在Spring中可以使用Schema和@AspectJ方式进行组织实现;AOP中表示为“在哪干和干什么集合”;

    引入(inter-type declaration):也称为内部类型声明,为已有的类添加额外新的字段或方法,Spring允许引入新的接口(必须对应一个实现)到所有被代理对象(目标对象), 在AOP中表示为“干什么(引入什么)”

    目标对象(Target Object):需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被通知的对象,从而也可称为“被通知对象”;由于Spring AOP 通过代理模式实现,从而这个对象永远是被代理对象,AOP中表示为“对谁干”

    AOP代理(AOP Proxy):AOP框架使用代理模式创建的对象,从而实现在连接点处插入通知(即应用切面),就是通过代理来对目标对象应用切面。在Spring中,AOP代理可以用JDK动态代理或CGLIB代理实现,而通过拦截器模型应用切面。AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类

    织入(Weaving):织入是一个过程,是将切面应用到目标对象从而创建出AOP代理对象的过程,织入可以在编译期、类装载期、运行期进行。

    在AOP中,通过切入点选择目标对象的连接点,然后在目标对象的相应连接点处织入通知,而切入点和通知就是切面(横切关注点),而在目标对象连接点处应用切面的实现方式是通过AOP代理对象,如下图所示。

    接下来再让我们具体看看Spring有哪些通知类型:

    1. 前置通知(Before Advice):在切入点选择的连接点处的方法之前执行的通知,该通知不影响正常程序执行流程(除非该通知抛出异常,该异常将中断当前方法链的执行而返回)。

    2. 后置通知(After Advice):在切入点选择的连接点处的方法之后执行的通知,包括如下类型的后置通知

      2.1 后置返回通知(After returning Advice):在切入点选择的连接点处的方法正常执行完毕时执行的通知,必须是连接点处的方法没抛出任何异常正常返回时才调用后置通知

           2.2 后置异常通知(After throwing Advice): 在切入点选择的连接点处的方法抛出异常返回时执行的通知,必须是连接点处的方法抛出任何异常返回时才调用异常通知

           2.3 后置最终通知(After finally Advice): 在切入点选择的连接点处的方法返回时执行的通知,不管抛没抛出异常都执行,类似于Java中的finally块。

    3. 环绕通知(Around Advices):环绕着在切入点选择的连接点处的方法所执行的通知,环绕通知可以在方法调用之前和之后自定义任何行为,并且可以决定是否执行连接点处的方法、替换返回值、抛出异常等等。

    2、基于注解的AOP配置方式

    2.1 启用@AsjectJ支持

    在applicationContext.xml中配置下面一句:

    <aop:aspectj-autoproxy />

    2.2 通知类型介绍

    (1)Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可

    (2)AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值

    (3)AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象

    (4)After:在目标方法完成之后做增强,无论目标方法时候成功完成。@After可以指定一个切入点表达式

    (5)Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint

     2.3 例子:

    (1)Operator.java --> 切面类

    @Component
    @Aspect
    public class Operator {
        
        @Pointcut("execution(* com.aijava.springcode.service..*.*(..))")
        public void pointCut(){}
        
        @Before("pointCut()")
        public void doBefore(JoinPoint joinPoint){
            System.out.println("AOP Before Advice...");
        }
        
        @After("pointCut()")
        public void doAfter(JoinPoint joinPoint){
            System.out.println("AOP After Advice...");
        }
        
        @AfterReturning(pointcut="pointCut()",returning="returnVal")
        public void afterReturn(JoinPoint joinPoint,Object returnVal){
            System.out.println("AOP AfterReturning Advice:" + returnVal);
        }
        
        @AfterThrowing(pointcut="pointCut()",throwing="error")
        public void afterThrowing(JoinPoint joinPoint,Throwable error){
            System.out.println("AOP AfterThrowing Advice..." + error);
            System.out.println("AfterThrowing...");
        }
        
        @Around("pointCut()")
        public void around(ProceedingJoinPoint pjp){
            System.out.println("AOP Aronud before...");
            try {
                pjp.proceed();
            } catch (Throwable e) {
                e.printStackTrace();
            }
            System.out.println("AOP Aronud after...");
        }
        
    }

    (2)UserService.java --> 定义一些目标方法

    @Service
    public class UserService {
        
        public void add(){
            System.out.println("UserService add()");
        }
        
        public boolean delete(){
            System.out.println("UserService delete()");
            return true;
        }
        
        public void edit(){
            System.out.println("UserService edit()");
            int i = 5/0;
        }
          
    }

    (3).applicationContext.xml

    <context:component-scan base-package="com.aijava.springcode"/>
        
    <aop:aspectj-autoproxy />

    (4).Test.java

    public class Test {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
            UserService userService = (UserService) ctx.getBean("userService");
            userService.add();
        }
    }

    上面是一个比较简单的测试,基本涵盖了各种增强定义。注意:做环绕通知的时候,调用ProceedingJoinPoint的proceed()方法才会执行目标方法。

    2.4 通知执行的优先级

    进入目标方法时,先织入Around,再织入Before,退出目标方法时,先织入Around,再织入AfterReturning,最后才织入After。

    注意:Spring AOP的环绕通知会影响到AfterThrowing通知的运行,不要同时使用!同时使用也没啥意义。

    2.5 切入点的定义和表达式

    切入点表达式的定义算是整个AOP中的核心,有一套自己的规范

    Spring AOP支持的切入点指示符:

    execution:用来匹配执行方法的连接点

    A:@Pointcut("execution(* com.aijava.springcode.service..*.*(..))")

    第一个*表示匹配任意的方法返回值,..(两个点)表示零个或多个,上面的第一个..表示service包及其子包,第二个*表示所有类,第三个*表示所有方法,第二个..表示方法的任意参数个数

    B:@Pointcut("within(com.aijava.springcode.service.*)")

    within限定匹配方法的连接点,上面的就是表示匹配service包下的任意连接点

    C:@Pointcut("this(com.aijava.springcode.service.UserService)")

    this用来限定AOP代理必须是指定类型的实例,如上,指定了一个特定的实例,就是UserService

    D:@Pointcut("bean(userService)")

    bean也是非常常用的,bean可以指定IOC容器中的bean的名称

    2.6 基于XML形式的配置方式

    开发中如果选用XML配置方式,通常就是POJO+XML来开发AOP,大同小异,无非就是在XML文件中写切入点表达式和通知类型

    (1)Log.java

    public class Log {
    
        private Integer id;
    
        //操作名称,方法名
        private String operName;
    
        //操作人
        private String operator;
    
        //操作参数
        private String operParams;
    
        //操作结果 成功/失败
        private String operResult;
    
        //结果消息
        private String resultMsg;
    
        //操作时间
        private Date operTime = new Date();
    
        setter,getter
    
    }

    (2)Logger.java

    /**
     * 日志记录器 (AOP日志通知)
     */
    public class Logger {
        
        @Resource
        private LogService logService;
        
        public Object record(ProceedingJoinPoint pjp){
            
            Log log = new Log();
            try {
                log.setOperator("admin");
                String mname = pjp.getSignature().getName();
                log.setOperName(mname);
                
                //方法参数,本例中是User user
                Object[] args = pjp.getArgs();
                log.setOperParams(Arrays.toString(args));
                
                //执行目标方法,返回的是目标方法的返回值,本例中 void
                Object obj = pjp.proceed();
                if(obj != null){
                    log.setResultMsg(obj.toString());
                }else{
                    log.setResultMsg(null);
                }
                
                log.setOperResult("success");
                log.setOperTime(new Date());
                
                return obj;
            } catch (Throwable e) {
                log.setOperResult("failure");
                log.setResultMsg(e.getMessage());
            } finally{
                logService.saveLog(log);
            }
            return null;
        }
    }

    (3).applicationContext.xml

    <aop:config>
            <aop:aspect id="loggerAspect" ref="logger">
                <aop:around method="record" pointcut="(execution(* com.aijava.distributed.ssh.service..*.add*(..))
                                                  or   execution(* com.aijava.distributed.ssh.service..*.update*(..))
                                                  or   execution(* com.aijava.distributed.ssh.service..*.delete*(..)))
                                                  and  !bean(logService)"/>
            </aop:aspect>
    </aop:config>

    2.7 JDK动态代理介绍

    例子:

    (1)UserService.java

    public interface UserService {
        public void add();
    }

    (2)UserServiceImpl.java

    public class UserServiceImpl implements UserService{
        public void add() {
            System.out.println("User add()...");
        }
    }

    (3)ProxyUtils.java

    public class ProxyUtils implements InvocationHandler{
        
        private Object target;
        
        public ProxyUtils(Object target){
            this.target = target;
        }
        
        public Object getTarget() {
            return target;
        }
        
        public void setTarget(Object target) {
            this.target = target;
        }
    
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("do sth before...");
            method.invoke(target, args);
            System.out.println("do sth after...");
            return null;
        }
    
    }

    (4)Test.java

    public class Test {
        public static void main(String[] args) {
            UserService userService = new UserServiceImpl();
            ProxyUtils proxyUtils = new ProxyUtils(userService);
            UserService proxyObject = (UserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),UserServiceImpl.class.getInterfaces(), proxyUtils);
            proxyObject.add();
        }
    }

    JDK动态代理核心还是一个InvocationHandler,记住这个就行了。

  • 相关阅读:
    企业微信自建应用使用审批流程引擎
    unicode和utf8互转【转】
    禅道迅捷版,人人都可用的项目管理工具!
    《2021年IT行业项目管理调查报告》重磅发布!
    wxpython窗体之间传递参数
    go Print 和 反射
    go 接口学习笔记
    设计模式学习笔记
    go 变量逃逸分析
    Go ASM 学习笔记之 ppt 版
  • 原文地址:https://www.cnblogs.com/songshu120/p/7930314.html
Copyright © 2020-2023  润新知