Spring学习(六)

AOP和OOP

1、OOP:Object-Oriented Programming,面向对象程序设计,是静态的,一旦类写死了就不能改变了,要更改就得修改代码重新编译,父类类型引用指向对象来实现动态性。核心思想是将客观存在的不同事物抽象成相互独立的类,然后把与事物相关的属性和行为封装到类里,并通过继承和多态来定义类彼此间的关系,最后通过操作类的实例来完成实际业务逻辑的功能需求。

2、AOP:Aspect-Oriented Programming,面向切面编程,以OOP为基础修复本身不具备的能力具有动态语言的特点,和OOP互为补充,Aop的最大意义是在不改变原来代码的前提下,也不对源代码做任何协议接口要求, 对OOP而实现了类似插件的方式,来修改源代码,给源代码插入新的执行代码。核心思想是将业务逻辑中与类不相关的通用功能切面式的提取分离出来,让多个类共享一个行为,一旦这个行为发生改变,不必修改类,而只需要修改这个行为即可。Spring的AOP其代码实质,即代理模式的应用。

3、OOP和AOP的区别

  • 面向目标不同:简单来说OOP是面向名词领域,AOP面向动词领域。
  • 思想结构不同:OOP是纵向结构,AOP是横向结构。
  • 注重方面不同:OOP注重业务逻辑单元的划分,AOP偏重业务处理过程的某个步骤或阶段。

4、OOP和AOP的联系:两者之间是一个相互补充和完善的关系

5、AOP优点:利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

6、AOP应用

日志记录、事务处理、异常处理、安全控制和性能统计方面。

Spring中提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务和事务进行内聚性的开发。

AOP的重要概念

1、切面 : 切点(Pointcut) + Advice【 在哪里 、加什么 】

2、Advice: 在 切点 选中的 连接点 "加入" 的 代码 就是 Advice【确定 加什么 】

3、切点( Pointcut ) : 用来 筛选 连接点 的条件就是切点, 类似于sql语句,where条件就可以理解为切点【 确定 在哪里加 ,在那些方法里面加入代码

4、连接点( Join Point ) : 执行点 + 方位信息 所有被拦截的方法被加入了其他的方法,通过代理的方法将想要加入的想要的代码,加入的代码和原有的代码形成了连接点。一个方法有5个连接点,四个方法就有20个

  • 一个正在执行的方法 (执行点) 执行前 (方位信息)
  • 一个正在执行的方法 (执行点) 执行前、后 (方位信息)
  • 一个正在执行的方法 (执行点) 执行并返回后 (方位信息)
  • 一个正在执行的方法 (执行点) 执行抛出异常后 (方位信息)
  • 一个正在执行的方法 (执行点) 执行后 (方位信息)

5、方位信息

  • 方法执行前 ( before ) 、
  • 执行前和执行后,执行前后都有环绕,两个结合起来才可以使用,两者之间是有联系的 ( around ) 、
  • 正常执行并返回后 ( after-returning ) 、
  • 方法抛出异常后 ( after-throwing ) 、
  • 方法执行后 ( after )

6、执行点:一个可以执行的方法在执行时就是一个执行点

7、代理目标: 哪个对象将被其它对象所代理 ( 谁将被别人代理 )  [ target ]
8、代理对象: 哪个对象将要去代理别的对象 ( 谁将去代理别人 )  [ proxy ]

代理模式

1、什么是代理呢?

 例如:苹果公司的手机交由富士康工厂按照苹果公司的要求去生产手机,富士康工厂生产的手机由京东按照苹果公司的要求去完成销售,京东就可以看成是富士康的代理商(代理对象),而富士康就是代理目标。消费者在京东买手机,感觉是由京东提供的手机,实际上是由富士康生产的,由京东代理,这种思想就是代理思想
2、具体代码实例

IPhone 苹果手机类

package ecut.aop.proxy;

public class IPhone {
    
    public IPhone(){
        super();
        System.out.println( "爱疯" );
    }

    public void charge() {
        System.out.println( "charge" );
    }

    public void call() {
        System.out.println( "call" );
    }

    public void message() {
        System.out.println( "message" );
    }

}

AppleCompany 苹果公司接口

package ecut.aop.proxy;

public interface AppleCompany {
    //由苹果公司去设计然后交由富士康生产
    public IPhone create() ;
    
}

FoxconnFactory 富士康工厂类

package ecut.aop.proxy;

public class FoxconnFactory implements AppleCompany {
    //需要实现苹果公司接口
    @Override
    public IPhone create() {
        System.out.println( "富士康" );
        IPhone phone = new IPhone();
        return phone ;
    }

    @Override
    public String toString() {
        return "FoxconnFactory";
    }
    
}

JingDong 京东类

package ecut.aop.proxy;

public class JingDong implements AppleCompany {
    
    private FoxconnFactory factory ;
    
    @Override
    public IPhone create() {
        System.out.println( "京东" );
        return factory.create() ;
    }
    //通过getter , setter 方法注入FoxconnFactory
    public FoxconnFactory getFactory() {
        return factory;
    }

    public void setFactory(FoxconnFactory factory) {
        this.factory = factory;
    }

}

Main主类

package ecut.aop.proxy;

public class Main {

    public static void main(String[] args) {
        //被代理的目标target, 京东买的手机由富士康生产
        FoxconnFactory factory = new FoxconnFactory();
        //京东是富士康的代理商,代理对象proxy,代理商和代理对象都实现了苹果公司的这个接口(前提),富士康按照苹果公司要求生产,京东按照苹果公司的要求买手机
        JingDong jd = new JingDong();
        
        jd.setFactory(factory);
        //相当于消费者买手机
        IPhone p = jd.create();
        
        System.out.println( p );
        
        p.call();

    }

}

先调用JingDong的create方法,实际上调用的FoxconnFactory的create方法(调用的proxy方法的时候调用target方法,表面上感觉是proxy调用的实际上target完成具体实现的)。用JingDong之所以可以代理FoxconnFactory,是因为FoxconnFactory和JingDong都实现了AppleCompany,这个是代理的前提

3、代理模式代码的主要特点是:不改变原有类的前提下,在原有类某些方法执行前后,插入任意代码。所以代理模式需要写新的类对原有的类进行包装。Struts2中的拦截器,Spring中的赖加载都是用代理模式实现

4、代理模式目前实现的方式有三种:

  • 静态代理:需要增强原有类的哪个方法,就需要对在代理类中包装哪个方法。个人理解,从功能上来说,原有类和代理类不一定要实现共同接口,但是为了赋予代理和和被代理类之间的逻辑关系,增加程序的可读性,可理解性,逻辑性,增加代理对象和被代理对象之间的关系,以更加符合面向对象编程是思维,而应该实现共同接口。
  • 动态代理:使用反射机制,方法和对象都是传入的变量,就可以经过传入的对象和方法而动态调用被代理对象的任何方法,jdk中提供了实现此动态代理的api,被代理类必须实现接口
  • Cglib代理:返回对象是代理对象的子类,不需要代理对象实现接口。当调用原对象方法时,实际上调用的是代理子类的方法。

JDK动态代理

1、目的:通过反射实现动态产生一个代理对象来代理富士康工厂(代理目标)
2、动态代理主要是通过Proxy类中的newProxyInstance创建一个代理对象,newProxyInstance方法需要传三个参数ClassLoader loader,Class<?>[] interfaces,InvocationHandler h。

  • ClassLoader loader:代理目标对应的 类的 "类加载器"
  • Class<?>[] interfaces:代理目标对应的类所直接实现的接口
  • InvocationHandler h:请求指派器,将代理对象的请求派遣个代理目标

3、InvocationHandler 请求指派器的作用就是作用就是要将代理对象的请求派遣个代理目标(调用的proxy方法的时候调用target方法,表面上感觉是proxy调用的实际上target完成具体实现的)。首先要通过匿名内部类实现InvocationHandler接口,并实现接口中的方法invoke( Object proxy , Method method , Object[] args )

  • Object proxy:代理对象
  • Method method:调用的方法
  • Object[] args:方法中要传入的参数

在invoke方法中要是proxy和target产生联系通过method.invoke( target , args ) 来实现将代理对象的请求派遣个代理目标

4、测试代码如下

package ecut.aop.proxy;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class ProxyTest {

    public static void main(String[] args) {
        
        // 代理目标 ( 谁将被别人代理 )
        final Object target = new FoxconnFactory();
        
        // 代理目标 的类型 : 获得 被代理的 对象的 运行时 类型,jdk代理的缺陷(前提)代理的目标类得实现接口,Spring提供了子类代理父类(Cglib)
        Class<?> targetClass = target.getClass(); 
        
        /** 用 代理目标 类型对应的类加载器 去加载 运行阶段动态产生的 代理类 */
        ClassLoader loader = targetClass.getClassLoader() ; // 获得 代理目标 对应的 类的 "类加载器"
        
        /** 指示 将来产生 动态代理类 所需要实现的接口 */
        Class<?>[] interfaces = targetClass.getInterfaces() ; // 获得 代理目标 对应的类所直接实现的接口
        System.out.println( Arrays.toString( interfaces ) );
        
        /** 请求指派器 (快递公司),调用的proxy方法的时候调用target方法,给你感觉是proxy调用的实际上target实现的,是使proxy 和 target产生联系,InvocationHandler将请求派给target
         * method被调用的方法是谁, args 方法中的参数"借尸还魂"匿名内部类 */
        InvocationHandler handler = new InvocationHandler(){
            @Override
            //proxy对象
            public Object invoke( Object proxy , Method method , Object[] args ) throws Throwable {
                //target.method(args);//被调用的是method方法;传入的参数是args;调用的是target所引用的对象的method方法
                Object result = method.invoke( target , args ) ; // target.create();指派给target,由target调用方法
                 
                return result;
            }
        } ;
        // 代理对象 ( 谁将去代理别人 )Proxy动态代理类的父类Proxy.newProxyInstance创建一个代理对象
        Object proxy = Proxy.newProxyInstance( loader , interfaces , handler ) ;
        
        System.out.println( proxy ); // proxy.toString()会被指派,通过InvocationHandler.invoke方法指派给target.toString(),因此返回的是target的tostring方法
        
        // proxy.getClass()不会被指派。代理类 : ( 在 运行阶段 动态产生 ( 没有 .java 文件、没有 .class 文件 ,直接产生字节码放在内存中去) )
        Class<?> proxyClass = proxy.getClass();
        
        System.out.println( proxyClass );
        
        // 所有的 由  java.lang.refrect.Proxy 产生的 动态代理类 的直接父类都是 Proxy
        System.out.println( proxyClass.getSuperclass() );
        
        // 获得 动态代理类 所实现过的 接口 ( 在 创建 代理对象时指定的数组中有哪些接口,这里就有哪些接口 )
        System.out.println( Arrays.toString( proxyClass.getInterfaces() ) );
        
        System.out.println( "~~~构造方法~~~~~~~~~~~~" );
        
        Constructor<?>[] cons = proxyClass.getDeclaredConstructors();
        for( Constructor<?> c : cons ){
            System.out.println( c );
        }
        
        System.out.println( "~~~ 属性 ( Field ) ~~~~~~~~~~~~" );
        //属性都由private static修饰 ,代理对象 $Proxy0继承了父类的Proxy类中的protected  InvocationHandler h属性,其他m0,m1....都与方法相对应
        Field[] fields = proxyClass.getDeclaredFields();
        for( Field c : fields ){
            System.out.println( c );
        }
        
        System.out.println( "~~~ 方法 ( Method ) ~~~~~~~~~~~~" );
        //重写了toString,hashCode,equals方法,都由public final修饰
        Method[] methods = proxyClass.getDeclaredMethods();
        for( Method c : methods ){
            System.out.println( c );
        }
        
        
    }

}

运行结果如下:

[interface ecut.aop.proxy.AppleCompany]
FoxconnFactory
class com.sun.proxy.$Proxy0
class java.lang.reflect.Proxy
[interface ecut.aop.proxy.AppleCompany]
~~~构造方法~~~~~~~~~~~~
public com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
~~~ 属性 ( Field ) ~~~~~~~~~~~~
private static java.lang.reflect.Method com.sun.proxy.$Proxy0.m1
private static java.lang.reflect.Method com.sun.proxy.$Proxy0.m2
private static java.lang.reflect.Method com.sun.proxy.$Proxy0.m3
private static java.lang.reflect.Method com.sun.proxy.$Proxy0.m0
~~~ 方法 ( Method ) ~~~~~~~~~~~~
public final boolean com.sun.proxy.$Proxy0.equals(java.lang.Object)
public final java.lang.String com.sun.proxy.$Proxy0.toString()
public final int com.sun.proxy.$Proxy0.hashCode()
public final ecut.aop.proxy.IPhone com.sun.proxy.$Proxy0.create()

由以上输出可以推测出来代理类的类名是$Proxy0,且有一个有参构造,参数是InvocationHandler类型,且有四个属性,四个方法,还继承了父类的Proxy类中的protected InvocationHandler h属性,推测其他m0,m1....都与方法相对应。$Proxy0源码可能类似于

Proxy类

public class Proxy  {

      protected  InvocationHandler h  ; // 父类中 public 修饰的 和 protected 修饰 都可以被子类继承

      protected  Proxy( InvocationHandler h )  {
              this.h = h ;
      }
 
}

 $Proxy0类

package com.sun.proxy ;

public class $Proxy0  extends  Proxy  implements AppleCompany {

       public $Proxy0( InvocationHandler h ){
              super( h );
       }

       private static Method m0 ;
       private static Method m1 ;
       private static Method m2 ;
       private static Method m3 ;
       
       /* ..........  */

       static {
            Class<?> c = $Proxy0.class ;
            m0 = c.getMethod( "equals" , Object.class );
            m1 = c.getMethod( "toString" );
            m2 = c.getMethod( "hashCode" );
            m3 = c.getMethod( "create" );
            
            /* ..........  */
       }

        public final boolean equals( Object another ) {
            Object[] args = new Object[]{ another  };
            return h.invoke( this , m0 , args );
        }

        public final String  toString() {
            Object[] args = new Object[ 0 ];
            return h.invoke( this , m1 , args );
        }

        public final int hashCode() {
            Object[] args = new Object[ 0 ];
            return h.invoke( this , m2 , args );
        }

        public final  IPhone create()  {
            Object[] args = new Object[ 0 ];
            return h.invoke( this , m3 , args );
        }

}

5、动态代理的实现和源码分析

查看Proxy类的newProxyInstance方法,从生成对象方法中,我们看到三个关键的地方:

  • Class<?> cl = getProxyClass0(loader, interfaces);//得到代理类
  • final Constructor<?> cons = cl.getConstructor(constructorParams);   
  • newInstance(cons, h);//将InvocationHandler h传入代理对象中

首先是通过Proxy.newProxyInstance( loader , interfaces , handler ) 将handler传入了$Proxy0 中的 InvocationHandler h属性中,然后当我们调用方法时候实际上是通过h.invoke( this , m3 , args )来实现的即调用handler里面的invoke方法,而在handler的invoke方法中通过method.invoke( target , args )方法,最终调用的是target里面的方法。

结合动态代理理解AOP相关概念

1、理解Advice测试案例

package ecut.aop.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyTest2 {

    public static void main(String[] args) {
        
        // 代理目标 ( 谁将被别人代理 )
        final Object target = new FoxconnFactory();
        
        // 代理目标 的类型 : 获得 被代理的 对象的 运行时 类型
        Class<?> targetClass = target.getClass(); 
        
        /** 用 代理目标 类型对应的类加载器 去加载 运行阶段动态产生的 代理类 */
        ClassLoader loader = targetClass.getClassLoader() ; // 获得 代理目标 对应的 类的 "类加载器"
        
        /** 指示 将来产生 动态代理类 所需要实现的接口 */
        Class<?>[] interfaces = targetClass.getInterfaces() ; // 获得 代理目标 对应的类所实现的接口
        
        /** 请求指派器  */
        InvocationHandler handler = new InvocationHandler(){
            @Override
            public Object invoke( Object proxy , Method method , Object[] args ) throws Throwable {
                System.out.println( "方法[ " + method.getName() +  " ]将要执行了" ); // Advice
                Object result = method.invoke( target , args ) ; // target.create();
                System.out.println( "方法[ " + method.getName() +  " ]执行结束并返回了: " + result );  // Advice
                return result;
            }
        } ;
        
        // 代理对象 ( 谁将去代理别人 ),这四个方法执行其前后就被我们拦截下来了 ,方法前后增加了输出语句,没有动方法,只是在动态代理类handler里面增加了代码
        //我们所增加的代码如果按照规范来封装好就是Advice
        Object proxy = Proxy.newProxyInstance( loader , interfaces , handler ) ;
        
        if( proxy instanceof AppleCompany ) {
            AppleCompany ac = (AppleCompany) proxy ;
            System.out.println( ac == proxy );
            
            IPhone p = ac.create();
            System.out.println( p );
            
            System.out.println( ac.toString() );
            
            System.out.println( ac.hashCode() );
            
            System.out.println( ac.equals(target) );//target.equals(target);
        }
        
    }

}

这四个方法执行其前后就被我们拦截下来了 ,方法前后增加了输出语句,没有动方法,只是在动态代理类handler里面增加了代码 ,我们所增加的代码如果按照规范来封装好就是Advice。

理解连接点测试案例:

package ecut.aop.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyTest3 {

    public static void main(String[] args) {
        
        // 代理目标 ( 谁将被别人代理 )
        final Object target = new FoxconnFactory();
        
        Class<?> targetClass = target.getClass(); 
        
        ClassLoader loader = targetClass.getClassLoader() ;
        
        Class<?>[] interfaces = targetClass.getInterfaces() ; 
        
        InvocationHandler handler = new InvocationHandler(){
            @Override
            public Object invoke( Object proxy , Method method , Object[] args ) throws Throwable {
                
                System.out.println( "方法[ " + method.getName() +  " ]将要执行了" ); // before
                
                Object result = null ;
                try{
                    long start = System.nanoTime(); // around
                    result = method.invoke( target , args ) ; // 执行点
                    long end = System.nanoTime(); // around
                    System.out.println( "执行用时: " + ( end - start ) + " 毫微妙" ); // around
                    System.out.println( "执行后并返回: " + result); // after-returing
                } catch ( Throwable t ){
                    System.out.println( "发生错误: " + t.getMessage()  ); // after-throwing
                }
                
                System.out.println( "方法[ " + method.getName() +  " ]执行结束" );  // after
                
                return result;
            }
        } ;
        
        Object proxy = Proxy.newProxyInstance( loader , interfaces , handler ) ;
        
        if( proxy instanceof AppleCompany ) {
            AppleCompany ac = (AppleCompany) proxy ;
            System.out.println( ac == proxy );
            //通过create调用了target中create的方法
            IPhone p = ac.create();
            System.out.println( p );
            //代理对象 $Proxy0中的四个方法是可执行的,方法在执行时候就可以视为是一个执行点
            String s = ac.toString() ;
            System.out.println( s );
        }
        
    }

}

连接点 ( Join Point ) : 执行点 + 方位信息 所有被拦截的方法被加入了其他的方法,通过代理的方法将想要加入的想要的代码,加入的代码和原有的代码形成了连接点

执行点就是一个可以执行的方法在执行时就是一个执行点,当create方法在执行的时候就是一个执行点,方位信息就是加入Advice的位置就是方位信息,执行点加上分为信息就是连接点。

理解切点测试案例:

package ecut.aop.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyTest4 {

    public static void main(String[] args) {
        
        // 代理目标 ( 谁将被别人代理 )
        final Object target = new FoxconnFactory();
        
        Class<?> targetClass = target.getClass(); 
        
        ClassLoader loader = targetClass.getClassLoader() ;
        
        Class<?>[] interfaces = targetClass.getInterfaces() ; 
        
        InvocationHandler handler = new InvocationHandler(){
            @Override
            //只保留了before和after的方位信息,20个连接点里挑选了8个,增加了了判断语句后只对create方法增加Advice,这些筛选条件就是切点
            public Object invoke( Object proxy , Method method , Object[] args ) throws Throwable {
                
                if( "create".equals( method.getName() ) ) {
                    System.out.println( "方法[ " + method.getName() +  " ]将要执行了" ); // before
                }
                
                Object result = method.invoke( target , args ) ; // 执行点
                
                if( "create".equals( method.getName() ) ){
                    System.out.println( "方法[ " + method.getName() +  " ]执行结束" );  // after
                }
                
                return result;
            }
        } ;
        
        Object proxy = Proxy.newProxyInstance( loader , interfaces , handler ) ;
        
        if( proxy instanceof AppleCompany ) {
            AppleCompany ac = (AppleCompany) proxy ;
            System.out.println( ac == proxy );
            
            IPhone p = ac.create();
            System.out.println( p );
            
            String s = ac.toString() ;
            System.out.println( s );
            
            System.out.println( ac.hashCode() );
        }
        
    }

}

切点就是筛选连接点的条件,比如增加了了判断语句后只对create方法增加Advice,这些筛选条件就是切点。

参考博客链接:

https://blog.csdn.net/pdsygt/article/details/46433537

https://blog.csdn.net/u011266694/article/details/78918394

转载请于明显处标明出处:

https://www.cnblogs.com/AmyZheng/p/9264215.html

原文地址:https://www.cnblogs.com/AmyZheng/p/9264215.html