zoukankan      html  css  js  c++  java
  • spring aop

    约定编程~Spring AOP

    1. 约定编程

    1.1 约定

    我们先准备一下测试的环境

    先写一个服务接口和服务实现

    public interface HelloService {
        void sayHello(String name);
    }
    
    public class HelloServiceImpl implements HelloService{
        @Override
        public void sayHello(String name) {
            if (name == null || name.trim().equals("")){
                throw new RuntimeException("the name is empty");
            }
            System.out.println("hello " + name +" !!!");
        }
    }
    

    这个服务很简单,就是,名字不为null和名字不为空时打印hello + name 否则抛出异常

    定义一个拦截器

    /**
     * 拦截器接口
     */
    public interface Interceptor {
        //事前方法
        boolean before();
    
        //事后方法
        void after();
        /**
         * 取代原有的事件方法
         * @param invocation --回调参数,可以通过它来回调原有事件的方法
         * @return 原有事件返回的对象
         * @throws InvocationTargetException
         * @throws IllegalAccessException
         */
        Object around(Invocation invocation) throws InvocationTargetException,IllegalAccessException;
    
        //是否返回方法,事件没有发生异常的情况下调用
        void afterReturning();
    
        //事后异常方法,当事件发生异常后执行
        void afterThrow();
    
        //是否调用around方法取代原有方法
        boolean useAround();
    }
    

    接口的定义也完成了,后面的工作就是把这些方法织入流程,这就需要约定了。

    先给出around(Invocation invocation)的源码

    public class Invocation {
        private Object[] params;
        private Method method;
        private Object target;//被代理的对象
    
        public Invocation(Object[] params, Method method, Object target) {
            this.params = params;
            this.method = method;
            this.target = target;
        }
        
        public Object proceed() throws InvocationTargetException,IllegalAccessException{
            return method.invoke(target,params);
        }
    	/*get set ...*/
    }
    

    通过反射去执行被代理对象的方法

    实现这个拦截器

    public class MyInterceptor implements Interceptor{
    
        @Override
        public boolean before() {
            System.out.println("before .... ");
            return useAround();
        }
    
        @Override
        public boolean useAround() {
            return true;
        }
        
        @Override
        public void after() {
            System.out.println("after .... ");
        }
    
        @Override
        public Object around(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
            System.out.println("around before.... ");
            Object proceed = invocation.proceed();
            System.out.println("around after.... ");
            return proceed;
        }
    
        @Override
        public void afterReturning() {
            System.out.println("afterReturning.... ");
        }
    
        @Override
        public void afterThrow() {
            System.out.println("afterThrow.... ");
        }
    }
    

    我们需要一个代理对象-ProxyBean来执行这些流程,ProxyBean怎么实现后面再说,实现之后我们需要他能执行以下流程

    1. 使用 proxy调用方法时会先执行拦截器的 before方法
    2. 如果拦截器的 useAround方法返回true,则执行拦截器的 around方法,而不调用 target,对象对应的方法, around方法的参数Invocation对象存在一个 proceed方法,它可以调用 target对象对应的方法;如果 useAround方法返回 false,则直接调用 target对象的事件方法
    3. 无论怎么样,在完成之前的事情后,都会执行拦截器的 after方法
    4. 在执行 around方法或者回调 target I的事件方法时,可能发生异常,也可能不发生异常。如果发生异常,就执行拦截器的 afterThrowing方法,否则就执行 afterReturning方法。

    下图是整个约定流程

    image-20201103100829869

    这边的服务和拦截器都已经定义好了,那么问题就是怎么把这些拦截器里面的方法织入约定的流程呢,也就是ProxyBean是怎么实现的?

    1.2 ProxyBean的实现

    JDK提供了一个可以创造代理对象的方法,方法如下

    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws ...
    
    • ClassLoader loader 类加载器

    • Class<?>[] interfaces 把代理对象绑定在那些接口下

    • InvocationHandler 类中有一个invoke方法可以实现代理对象的逻辑

      /**
      * Processes a method invocation on a proxy instance and returns
      * the result.  This method will be invoked on an invocation handler
      * when a method is invoked on a proxy instance that it is
      * associated with.
      *proxy 代理对象
      *method 被代理的方法
      *args 被代理方法的参数
      */
      public Object invoke(Object proxy, Method method, Object[] args)
      

    那么ProxyBean使用一个方法获取到proxy的实例

     public static Object getProxyBean(Object target, Interceptor interceptor)
    

    该方法就有两个约定

    1. target对象必须存在接口
    2. 返回一个proxy对象,可以根据target实现的接口对他进行强制转换

    ProxyBean就需要有如下两个条件

    1. 需要有参数target(必须实现接口)和Interceptor(定义了流程)
    2. 同时要继承InvocationHandler实现invoke方法,这样当我们获取到代理对象执行方法是就可以在Invoke中实现约定的流程

    ProxyBean的具体实现

    public class MyProxyBean implements InvocationHandler {
        private Object target;//保存被代理的对象
        private Interceptor interceptor;//保存需要织入的流程
    
        /**
         * 绑定代理对象
         * @param target 被代理的对象
         * @param interceptor 拦截器
         * @return 代理对象
         */
        public static Object getProxyBean(Object target, Interceptor interceptor){
            MyProxyBean myProxyBean = new MyProxyBean();
            myProxyBean.target = target;
            myProxyBean.interceptor = interceptor;
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),myProxyBean);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            boolean hasException = false;//异常标志位
            Object returnObj = null;
            Invocation invocation  = new Invocation(args,method,target);//可以通过它来实现被代理对象的方法的调用
            try {
                if (this.interceptor.before()){//需要
                    returnObj = this.interceptor.around(invocation);//执行around取代原有的方法
                }else {
                    returnObj = method.invoke(target,method);//直接调用方法
                }
            }catch (Exception e){
                hasException = true;
            }
            this.interceptor.after();
            if (hasException){
                this.interceptor.afterThrow();//方法执行后存在异常时调用
            }else {
                this.interceptor.afterReturning();//方法执行后存在异常时调用
                return returnObj;
            }
            return null;
        }
    }
    

    测试代码(测试代码中就是按照之前getProxyBean的约定编程的,只要按照约定编程就能将代码织入事先约定的流程)

    @Test
    void proxyTest(){
        HelloService helloService = new HelloServiceImpl();
        MyInterceptor myInterceptor = new MyInterceptor();
        HelloService proxyHelloService = (HelloService)MyProxyBean.getProxyBean(helloService, myInterceptor);
        proxyHelloService.sayHello("yogurt");
        System.out.println("====================name is null================================");
        proxyHelloService.sayHello(null);
    }
    

    测试结果

    image-20201103110112619

    1.3 总结

    测试的结果告诉我们,提供一定的约定规则后,只要按照约定编程就能实现某些功能。spring Aop也是如此,它能通过与我们的约定,把对应的方法通过动态代理技术织入约定的流程。所以掌握spring Aop的根本就是掌握其对我们的约定规则。

    2.AOP概念

    2.1 为什么使用AOP

    使用AOP主要是将一些与业务逻辑无关的代码复用,开发者可以集中关注于业务逻辑的开发

    比如说,操作日志、安全检测、事务处理等等。

    2.2 AOP术语以及流程

    AOP术语

    1. 连接点 joint point

      指被拦截的对象,但是spring只支持方法,这里指的是特定的方法,比如HelloService的sayHello()就是一个连接点,Aop通过动态代理将它织入对应的流程

    2. 切点 point cut

      由于我们的切面不单单指一个方法,可能需要织入流程的方法有很多,所以可以切点可以通过正则表达式去匹配这些方法

    3. 通知 advice

      就是按照约定的流程下的方法,分为前置通知( before advice)、后置通知( afteradvice)、环绕通知( around advice)、事后返回通知(after Returning advice)和异常通知(after Throwing advice),它会根据约定织入流程中,需要弄明白它们在流程中的顺序和运行 的条件

    4. 目标对象 target

      被代理的对象,例如HelloServiceImpl

    5. 引入 introduction

      指引入新的类和其方法,增强现有Bean的功能。

    6. 织入 weaving

      它是一个过程,过程就是通过动态代理技术为原有的服务生成代理对象,将与切点匹配的连接点拦截,并按照约定将各类通知织入流程

    7. 切面 aspect

      它是一个可以定义切点,通知和引入的内容,spring AOP可以通过它来增强bean的功能或者将对应的方法织入流程

    下图解释了spring aop的流程约定

    image-20201103113710470

    3.AOP的使用

    3.1 切面、切点、通知

    需求:UserServiceImpl的printUser方法织入流程

    UserService和UserServiceImpl

    public interface UserService {
        void printUser(User user);
    }
    
    @Service
    public class UserServiceImpl implements UserService{
        @Override
        public void printUser(User user) {
            System.out.println(user.toString());
        }
    }
    

    定义切面、切点、通知

    @Aspect
    @Component
    public class MyAspect {
        @Pointcut("execution(* com.yogurt.chapter4.aop.service.UserServiceImpl.printUser(..))")
        public void pointCut(){
    
        }
        @Before("pointCut()")
        public void before(){
            System.out.println("before ....");
        }
        @After("pointCut()")
        public void after(){
            System.out.println("after ....");
        }
    
        @Around("pointCut()")
        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            System.out.println("around before ....");
            Object result = pjp.proceed();
            System.out.println("around after ....");
            return result;
        }
        @AfterReturning("pointCut()")
        public void afterReturn(){
            System.out.println("afterReturn ....");
        }
    
        @AfterThrowing("pointCut()")
        public void afterThrowing(){
            System.out.println("afterThrowing ....");
        }
    }
    

    使用@PointCut来定义切点他的配置值是一个正则式

    execution(* com.yogurt.chapter4.aop.service.UserServiceImpl.printUser(..))

    1. execution表示在执行的时候拦截正则匹配的方法
    2. *表示返回任意类型
    3. com.yogurt.chapter4.aop.service.UserServiceImpl类的全限定名
    4. printUser(..)表示任意参数的printUser方法

    对于这个正则式而言还可以使用AspectJ指示器

    image-20201103202458522

    上面的正则式也可以这样表达

    @Pointcut("execution(* com.yogurt.chapter4.*.*.*.printUser(..)) && bean(userServiceImpl)")
    

    bean中的内容表示对Spring Bean的限定

    3.2 环绕通知

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before ....");
        Object result = pjp.proceed();
        System.out.println("around after ....");
        return result;
    }
    

    环绕通知非常强大,他的参数ProceedingJoinPoint pjp是一个被spring封装过的对象,他的proceed方法可以回调原有目标对象的方法,断点调试可以知道这个对象持有tagart、method、param,这样可以通过反射来调用目标对象的方法了。

    3.3 引入

    前面的例子只要传入的user为null会抛出异常,执行afterThrowing方法,那么我现在的需求是引入一个接口增强UserService使传过来的user为空时不执行printUser方法。

    定义一个UserValidator接口和对应的实现类

    public interface UserService {
        void printUser(User user);
    }
    
    public class UserValidatorImpl implements UserValidator{
        @Override
        public boolean validate(User user) {
            return user != null;
        }
    }
    

    将接口加入切面

    public class MyAspect {
    
        @DeclareParents(value = "com.yogurt.chapter4.aop.service.UserServiceImpl+"
                        ,defaultImpl = UserValidatorImpl.class)
        private UserValidator userValidator;
    

    这里使用 @DeclareParents引入新的接口来增强服务有两个配置项

    • value 指向你要增强的目标对象,全限定名加+号
    • defaultImpl 指向引入接口的默认实现类

    测试

    @Test
    void test2() {
        User user = new User();
        user.setUserName("yogurt");
        user.setNote("always");
        //强制转换
        UserValidator userValidator = (UserValidator) userService;
        //判断user是否为空
        if (userValidator.validate(user)) {
            userService.printUser(user);
        }
    }
    

    userService能强制转换为UserValidator实例的原理:

    jdk的proxy类的newProxyInstance方法可以传入多个接口

    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
    

    让该代理对象挂在这两个接口下,且这些接口代理类能自由切换并使用他们的方法

  • 相关阅读:
    C# 利用Log4Net进行日志记录
    驰骋工作流引擎JFlow与activiti的对比之2种结构化模式
    驰骋工作流引擎JFlow与activiti的对比之4种高级分支同步模式
    工作流引擎JFlow与activiti 对比分析(一)5种基本控制流模式的对比
    "整数"组件:<int> —— 快应用组件库H-UI
    "短整数"组件:<short> —— 快应用组件库H-UI
    "字节型整数"组件:<byte> —— 快应用组件库H-UI
    "图片验证码"组件:<vcode> —— 快应用组件库H-UI
    "手机验证码"组件:<smscode> —— 快应用组件库H-UI
    "邮政编码"组件:<postcode> —— 快应用组件库H-UI
  • 原文地址:https://www.cnblogs.com/iandf/p/13922533.html
Copyright © 2011-2022 走看看