spring aop

AOP

看这篇里面的AOP
  • 对AOP的理解
    • OOP(面向对象编程)是纵向的,而AOP(面向切面编程)是横向的
    • 什么是切面。炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面
    • 执行这个方法前,你需要干什么,执行方法后,你需要干什么,都可以用AOP的思想
    • 以一个简单的例子来比喻一下 AOP 中 Aspect, Joint point, PointcutAdvice之间的关系
      • 让我们来假设一下, 从前有一个叫爪哇的小县城, 在一个月黑风高的晚上, 这个县城中发生了命案. 作案的凶手十分狡猾, 现场没有留下什么有价值的线索. 不过万幸的是, 刚从隔壁回来的老王恰好在这时候无意中发现了凶手行凶的过程, 但是由于天色已晚, 加上凶手蒙着面, 老王并没有看清凶手的面目, 只知道凶手是个男性, 身高约七尺五寸. 爪哇县的县令根据老王的描述, 对守门的士兵下命令说: 凡是发现有身高七尺五寸的男性, 都要抓过来审问. 士兵当然不敢违背县令的命令, 只好把进出城的所有符合条件的人都抓了起来
      • 来让我们看一下上面的一个小故事和 AOP 到底有什么对应关系。首先我们知道, 在 Spring AOPJoint point 指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 Joint point, 通过 point cut, 我们就可以确定哪些 Joint point 可以被织入 Advice. 对应到我们在上面举的例子, 我们可以做一个简单的类比, Joint point 就相当于 爪哇的小县城里的百姓,pointcut 就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸, 而 Advice 则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问
      • Joint point: 爪哇的小县城里的百姓: 因为根据定义, Joint point 是所有可能被织入 Advice 的候选的点, 在 Spring AOP中, 则可以认为所有方法执行点都是 Joint point. 而在我们上面的例子中, 命案发生在小县城中, 按理说在此县城中的所有人都有可能是嫌疑人.
      • Pointcut:男性, 身高约七尺五寸: 我们知道, 所有的方法(joint point) 都可以织入 Advice, 但是我们并不希望在所有方法上都织入 Advice, 而 Pointcut 的作用就是提供一组规则来匹配joint point, 给满足规则的 joint point 添加 Advice. 同理, 对于县令来说, 他再昏庸, 也知道不能把县城中的所有百姓都抓起来审问, 而是根据凶手是个男性, 身高约七尺五寸, 把符合条件的人抓起来. 在这里 凶手是个男性, 身高约七尺五寸 就是一个修饰谓语, 它限定了凶手的范围, 满足此修饰规则的百姓都是嫌疑人, 都需要抓起来审问
      • Advice :抓过来审问, Advice 是一个动作, 即一段 Java 代码, 这段 Java 代码是作用于 point cut 所限定的那些 Joint point 上的. 同理, 对比到我们的例子中, 抓过来审问 这个动作就是对作用于那些满足 男性, 身高约七尺五寸 的爪哇的小县城里的百姓
      • Aspect: Aspectpoint cutAdvice 的组合, 因此在这里我们就可以类比: “根据老王的线索, 凡是发现有身高七尺五寸的男性, 都要抓过来审问” 这一整个动作可以被认为是一个 Aspect
      • Joinpoint可以有多种类型:构造方法调用,字段的设置和获取,方法的调用,方法的执行,异常的处理执行,类的初始化。但是在Spring中却没有实现上面所有的Joinpoint,确切的说,Spring只支持方法执行类型的Joinpoint。在Spring中,通过动态代理动态字节码技术实现AOP
  • AOP的实现
    • 动态代理。Java中的一个方法,这个方法可以实现动态创建一组指定的接口的实现对象
      1
      public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
      • ClassLoader loader:方法需要动态生成一个类,这个类实现了A和B两个接口,然后创建这个类的对象。需要生成一个类,而且这个类也需要加载到方法区中,所以我们需要一个ClassLoader来加载该类
      • Class<?>[] interfaces:我们需要代理对象实现的数组
      • InvocationHandler h:调用处理器,这里就是增强的地方
        1
        2
        3
        4
        5
        6
        7
        /**
        * Created by KebabShell
        * on 2020/4/26 下午 01:49
        */
        public interface hello {
        Object test();
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        public void test0() {
        /**
        * 三个参数
        * 1、ClassLoader
        * 方法需要动态生成一个类,这个类实现了A和B两个接口,然后创建这个类的对象
        * 需要生成一个类,这个类也需要加载到方法区中,所以我们需要一个ClassLoader来加载该类
        *
        * 2、Class[] interfaces
        * 我们需要代理对象实现的数组
        *
        * 3、InvocationHandler
        * 调用处理器
        */
        ClassLoader classLoader = this.getClass().getClassLoader();
        InvocationHandler invocationHandler = new InvocationHandler() {

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("handler invoke");
        return "invoke return";
        }
        };
        //hello是自定义接口
        Object obj = Proxy.newProxyInstance(classLoader, new Class[]{hello.class}, invocationHandler);
        hello h = (hello) obj;
        Object test = h.test();
        //System.out.println("h.toString()" + h.toString());
        //System.out.println("h.getClass()" + h.getClass());
        //System.out.println("obj.getClass()" + obj.getClass());
        System.out.println("test" + test);
        }
      • 以上是残疾版的AOP。代理对象方法的返回值其实就是invoke方法的返回值,代理对象其实就是使用反射机制实现的一个运行时对象。下面是比较完整的实现
        1
        2
        3
        public interface BeforeAdvice {
        void before();
        }
        1
        2
        3
        public interface AfterAdvice {
        void after();
        }
        1
        2
        3
        4
        5
        6
        7
        public class HelloImpl implements hello {
        @Override
        public Object test() {
        System.out.println("helloImpl test");
        return "helloImpl";
        }
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        /**
        * Created by KebabShell
        * on 2020/4/26 下午 11:18
        * 代理工厂
        */
        public class ProxyFactory {
        private Object target;
        //自定义的
        private BeforeAdvice beforeAdvice;
        private AfterAdvice afterAdvice;

        public Object createProxy(){
        ClassLoader classLoader = this.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler invocationHandler = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //执行前
        beforeAdvice.before();
        //实现目标需要执行的方法
        Object result = method.invoke(target, args);
        //执行后
        afterAdvice.after();
        return result;
        }
        };
        Object obj = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        return obj;
        }
        //getter setter
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        public void test1(){
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(new HelloImpl());

        proxyFactory.setBeforeAdvice(new BeforeAdvice() {
        @Override
        public void before() {
        System.out.println("before");
        }
        });
        proxyFactory.setAfterAdvice(new AfterAdvice() {
        @Override
        public void after() {
        System.out.println("after");
        }
        });
        hello h = (hello) proxyFactory.createProxy();
        Object result = h.test();
        System.out.println(result);

        }
        1
        2
        //这是spring的ProxyFactory
        public class ProxyFactory extends ProxyCreatorSupport
    • 动态字节码技术 CGLIB
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib-nodep</artifactId>
      <version>3.2.0</version>
      </dependency>
      <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.2.0</version>
      </dependency>
      • 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。然后在需要使用目标对象的时候,通过CGLIB动态代理获取代理对象
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        //目标对象
        public class TargetTest {
        public String doFirst(){
        System.out.println("first");
        return "doFirst";
        }
        public void doSecond(){
        System.out.println("second");
        }
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        /**
        * Created by KebabShell
        * on 2020/4/27 下午 09:20
        */
        public class CglibFactory implements MethodInterceptor {
        public TargetTest myCglibCreator(){
        Enhancer enhancer = new Enhancer();
        //将目标类设置为父类,cglib动态代理增强的原理就是子类增强父类,cglib不能增强目标类为final的类
        //因为final类不能有子类
        enhancer.setSuperclass(TargetTest.class);
        //设置回调接口,这里的MethodInterceptor实现类回调接口,而我们又实现了MethodInterceptor,其实
        //这里的回调接口就是本类对象,调用的方法其实就是intercept()方法
        enhancer.setCallback(this);
        //create()方法用于创建cglib动态代理对象
        return (TargetTest) enhancer.create();
        }
        /**
        * @param o Object为由CGLib动态生成的代理类实例
        * @param method Method为上文中实体类所调用的被代理的方法引用
        * @param objects Object[]为参数值列表
        * @param methodProxy MethodProxy为生成的代理类对方法的代理引用。
        * @return
        * @throws Throwable
        *
        * 回调接口的方法
        * 回调接口的方法执行的条件是:代理对象执行目标方法时会调用回调接口的方法
        */
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //执行目标类的方法
        //Object result = methodProxy.invokeSuper(o, objects);
        //这里实现将返回值字符串变为大写的逻辑
        //if(result != null) {
        //result = ((String) result).toUpperCase();
        //result += " intercept";
        //}
        //return result;
        //调用代理类实例上的methodProxy方法的父类方法(即实体类TargetTest中对应的方法)
        System.out.println("intercept");
        return "HHH";
        }
        }
        1
        2
        3
        4
        5
        6
        7
        8
        @Test
        public void test0(){
        //TargetTest target = new TargetTest();
        TargetTest creator = new CglibFactory().myCglibCreator();
        String result = creator.doFirst();
        System.out.println(result);
        creator.doSecond();
        }
      • CGLIB也有其缺陷,那就是必须目标类必须是可以继承的,如果目标类不可继承,那么我们就无法使用CGLIB来增强该类
    • Spring实现AOP的原理是JDK动态代理和cglib代理。JDK动态代理有缺陷,就是被代理对象必须实现接口才能产生代理对象,如果没有接口,就不能使用动态代理技术。我们用spring容器来实现动态代理,假如要管理的对象没有实现接口,那么就不能产生代理对象了。为了让所有的对象都能产生动态代理对象,Spring又融入了第三方代理技术CGLIB代理。CGLIB可以对任何类生成代理对象,它的原理是对目标对象进行继承代理,如果目标对象被final修饰,那么该类无法被CGLIB代理。
    • 那么Spring到底使用的是JDK代理,还是cglib代理呢?
      答案是混合使用。如果被代理对象实现了接口,就优先使用JDK代理,如果没有实现接口,就用用cglib代理。
    • Spring切面可以应用5种类型的通知
      • 前置通知(Before)
      • 后置通知(After,在方法完成之后调用通知,无论方法执行是否成功)
      • 后置通知(After-returning,在方法成功执行之后调用通知)
      • 异常通知(After-throwing,在方法抛出异常后调用通知)
      • 环绕通知(Around,在目标方法之前之后都调用)。
  • AOP的应用
    • spring的AOP事务
      • 事务的概述
        ACID。即原子性(amoticity)、一致性(consitency)、隔离性(isolation)、持久性(durability)
        原子性:指事务包含的所有操作要么全部成功,要么全部失败回滚
        一致性:指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性
        隔离性:当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行
        持久性:指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作
      • 事务的隔离级别
        Read uncommitted:读未提交。即一个事务可以读取另一个未提交事务的数据。这就是脏读。解决脏读方法就是Read committed 读提交
        Read committed:读提交。一个事务要等另一个事务提交后才能读取数据。程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的。这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。解决不可重复读的方法就是Repeatable read重复读
        Repeatable read:重复读。就是在开始读取数据(事务开启)时,不再允许修改操作。程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。那什么是幻读?程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读,怎么解决幻读问题?Serializable序列化
      • Serializable:序列化。Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读不可重复读幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
      • 大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , OracleMysql的默认隔离级别是Repeatable read

      • spring中事务的分类
        可以分为编程式事务控制声明式事务控制
        自己手动控制事务,就叫做编程式事务控制。开发起来比较繁琐,每次都要开启、提交、回滚。可以对指定的方法、指定的方法的某几行添加事务控制
        Spring提供了对事务的管理, 这个就叫声明式事务管理。实现了对事务控制的最大程度的解耦。核心实现就是基于AOP。只能给整个方法应用事务,不可以对方法的某几行应用事务。Spring声明式事务管理器类:Jdbc技术:DataSourceTransactionManager、Hibernate技术:HibernateTransactionManager
        使用配置文件的方法略。
        spring管理事务的属性介绍
        (1)事务的隔离级别
        (2)是否只读
        (3)事务的传播行为。有7种。分别是REQUIREDREQUIRES_NEWSUPPORTSNOT_SUPPORTEDMANDATORYNESTEDNEVER。我们常用的是propagation="REQUIRED",默认的就是REQUIRED,指得是支持当前事务,如果不存在,就新建一个(默认),所以这个属性不用配置。其余6个属性几乎不用

        在需要添加事务管理的方法上添加:
        1
        @Transactional(isolation=Isolation.REPEATABLE_READ,readOnly=false,propagation=Propagation.REQUIRED)

参考文章1
参考文章2
参考文章3
参考文章4
参考文章5
参考文章6