zoukankan      html  css  js  c++  java
  • Spring笔记:AOP基础

    Spring笔记:AOP基础

    AOP

    引入AOP

      面向对象的开发过程中,我们对软件开发进行抽象、分割成各个模块或对象。例如,我们对API抽象成三个模块,Controller、Service、Command,这很好地解决了业务级别的开发,但是对于系统级别的开发我们很难聚集。比如每一个模块需要打印日志、代码监控、异常检测等。我们只能将日志代码嵌套在各个对象上,无法关注日志本身

      为了更好地将系统系统级别的代码抽离出来,去掉和对象的耦合,就产生了AOP(面向切面)。如下图,OOP是一种横向扩展,AOP是一种纵向扩展。AOP依赖OOP,进一步将系统级别的代码抽象出来,进行纵向排列,实现低耦合

     

    AOP的家庭成员

    •  PointCut::即在哪个地方进行切入,它可以指定一个点,也可以指定多个点。
    •  Advice:连接点,在切入点(PointCut)干什么,比如打印日志、执行缓存、处理异常等。
    •  Advisor/Aspect:PointCut与Advice形成了切面Aspect,这个概念本身即代表切面的所有元素,Proxy技术会将切面植入到代码中。
    •  Proxy:代理,相当于一个管理部分,它管理了AOP如何融入OOP。

    NOTE:Aspect虽然是面向切面核心思想的重要组成部分,但是其思想的践行者是Proxy,也是实现AOP的难点与核心所在。

    技术实现Proxy

    静态代理

      设计模式中讲过代理模式,此处不在赘述。

      之所以称为静态dialing,是因为静态与动态是有代理产生的时间来决定,静态代理产生于代码编译阶段,即一旦代码运行就不变了

      

      举个例子,我们实现一个简单的日志管理系统:

    public interface IPerson {
        public void doSomething();
    }
    public class Person implements IPerson {
        public void doSomething(){
            System.out.println("I want wo sell this house");
        }
    }
    public class PersonProxy {
        private IPerson iPerson;
        private final static Logger logger = LoggerFactory.getLogger(PersonProxy.class);
    
        public PersonProxy(IPerson iPerson) {
            this.iPerson = iPerson;
        }
        public void doSomething() {
            logger.info("Before Proxy");
            iPerson.doSomething();
            logger.info("After Proxy");
        }
    
        public static void main(String[] args) {
            PersonProxy personProxy = new PersonProxy(new Person());
            personProxy.doSomething();
        }
    }

      通过代理类我们可以将日志代码集成到了目标类,但从上面我们可以看出它具有很大的局限性:需要固定的类编写接口(或许还可以接受,毕竟有提倡面向接口编程),需要实现接口的每一个函数(不可接受),同样会造成代码的大量重复,将会使代码更加混乱

    动态代理

      SpringAOP是动态代理的典范,我们需要先熟悉一下动态代理的相关概念

      Mybatis中,Mapper仅仅是一个接口,而不是一个包含逻辑的实现类,我们知道一个接口是无法去执行的,那么它是如何运行的呢?这不是违反了教科书所说的接口不能运行的道理吗?

      答案就是动态代理,不妨先来看一下Mapper到底是什么东西。

      很显然Mapper产生了代理类,这个代理类是MyBatis为我们创建

    代理模式

      所谓的代理模式就是在原有的服务上多加了一个占位,通过这个占位去控制服务的访问

      举例子,假设你是一个公司的工程师,能提供一些技术服务,公司的客服是一个美女,他不懂技术。而我是一个客户,徐你们公司提供技术服务。显然,我只会找你们公司的客服,和客服沟通,而不是找你沟通。客服会根据公司的规章制度和业务规则来决定找不找你服务。那么此时客服就等同于你的代理,她通过和我的交流来控制对你的访问,当然他也可以提供一些你们公司对外的服务。而我只能通知他的代理访问你。对我而言,我跟本不需要认识你,只需要认识客服就可以了。事实上,站在我的角度,我会认为客服就是代表你们公司,而不管真正为我服务的你是怎么样的

      其次,为什么要用代理模式呢?通过代理可以控制如何访问真正的服务对象,提供额外的服务。另外有机会通过重写一些类来满足特定的需要,正如客服也可以根据公司的业务规则,提供一些服务,这个时候就不需要你劳你大驾了。

      一般来说,代理分为两种,一种是JDK反射机制提供的代理,另一种是CGLIB代理

    JDK动态代理

      JDK的动态代理,是由JDK的Java.lang.reflect包提供支持的,我们需要完成几个步骤:

    •   编写服务类和接口,这个是真正的服务提供者,在JDK代理中接口是必须的。
    •   编写代理类,提供绑定和代理方法。

      JDK最大的缺点就是需要提供接口,而MyBatis的Mapper就是一个接口它采用的就是JDK的动态代理。我们先给一个服务接口。

    public interface HelloService{
       public void sayHello(String name);  
    }
    

      然后,写一个实现类

    public class HelloServiceImpl implements HelloService{
        public void sayHello(String name)
        {
            System.out.println("Hello"+name);
        }  
    }
    

      现在我们写一个代理类,提供真实对象的绑定和代理方法。代理类的要求是实现InvocationHandler接口的代理方法

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class HelloServiceProxy implements InvocationHandler
    {
        /**
         * 真实服务对象
         */
        private Object target;
    
        /**
         * 绑定委托对象,并返回一个代理类
         * @param target
         * @return
         */
        public Object bind(HelloService target)
        {
            this.target=target;
            //取得代理对象
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
        }
    
        /**
         *
         * @param proxy  代理对象
         * @param method  被调用的方法
         * @param args  方法的参数
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("*****我是JDK动态代理*****");
            Object result = null;
            //反射方法前调用
            System.out.println("我准备说Hello");
            //执行方法,相当于调用HelloServiceImp类的sayHello方法
            result = method.invoke(target,args);
            //反射方法后调用
            System.out.println("我说过Hello了");
            return result;
        }
    
    }
    

     下面这段代码让JDK产生一个代理对象,第一个参数是类加载器,第二个参数是接口(代理对象挂在哪一个接口下),第三个参数代表当前类,表示使用当前类的代理方法作为对象的代理执行者

      Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);

     一旦绑定后,在进入代理对象方法调用的时候就会到HelloServiceProxy的代理方法上,代理方法有三个参数,第一个proxy是代理对象,第二个是当前调用的方法、第三个是方法的参数

     我们可以用下面这段代码测试一下动态代理的效果:

    public class HelloServiceMain {
        public static void main(String[] args) {
            HelloServiceProxy helloHandler = new HelloServiceProxy();
            HelloService proxy = (HelloService) helloHandler.bind(new HelloServiceImpl());
            proxy.sayHello(",ms");
        }
    }

     效果是这样的:

    输出:
    *****我是JDK动态代理*****
    我准备说Hello
    Hello,ms
    我说过Hello了

    CGLIB动态代理

      JDK提供的动态代理存在一个缺陷,就是你必须提供接口才可以使用,为了克服缺陷,我们可以使用开源框架——CGLIB,它是一种流行的动态代理。

  • 相关阅读:
    删除数据库时报错 ERROR 1010 (HY000): Error dropping database (can't rmdir './cart', errno: 39)
    多线程异步操作导致异步线程获取不到主线程的request信息
    使用mybatis更新数据时 时间字段的值自动更新
    mysql死锁com.mysql.cj.jdbc.exception.MYSQLTransactionRollbackException Deadlock found when trying to get lock;try restarting transaction
    mysql 执行报错:Error querying database. Cause: java.sql.SQLSyntaxErrorException:which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
    Linux系统下部署eleasticsearch+kibana
    navicat突然连接不上远程linux服务器上的mysql
    即使是一条咸鱼,也要做最咸的那条
    linux环境下安装jdk,tomcat
    SpringBoot如何内置Tomcat
  • 原文地址:https://www.cnblogs.com/MrSaver/p/9774434.html
Copyright © 2011-2022 走看看