Spring动态代理小结

189 阅读7分钟

Spring AOP设计原理详解

Aspect Oriented Programming:面向切面编程

Spring AOP目标

  1. 将分散在程序各处的横切关注点剥离出来并以集中的方式进行表达
  2. 使得开发人员能够专注于业务逻辑的实现而非繁杂的非功能代码,简化了程序编写与单元测试
  3. 应用场景:日志、安全、事务

AOP vs 继承

  1. AOP重点考虑的是程序的横切逻辑
  2. 继承重点考虑的是纵向的职责分派

AOP核心概念

  1. Advice:通知:定义在连接点处的行为,围绕方法调用而进行注入———(确定要切入的方法是什么)
  2. Pointcut:切点:确定在哪些连接点处应用通知———(确定在哪些地方应用要切入的方法)
  3. Advisor:通知器:组合Advice和Pointcut

Spring AOP实现

  1. ProxyFactoryBean

    1. A FactoryBean implemention that builds an AOP proxy based on beans in Spring BeanFactory
    2. Spring AOP的底层实现与源头
  2. ProxyFactoryBean的典型配置

    <bean id="myAOP" class="org.springframework.aop.framework.ProxyFactoryBean">
          <properties name="proxyInterfaces">
             <value>com.test.myInterface</value>
       </properties>
       <properties name="interceptorNames">
             <list>	
                <value>myAdvisor</value>
             </list>
       </properties>
        <properties name="target">
             <ref bean="myTarget"></ref>
       </properties>
    </bean>
    
    <bean id="myTarget" class="com.test.myTarget"></bean>
    
    <bean id="myAdvisor" class="com.test.MyAdvisor"></bean>
    

    当从myAOP这个“ProxyFactoryBean”中获取对象时,取得的是ProxyFactoryBean所构造的一个实例,该实例是myTarget类的一个代理对象,且该实例应用了MyAdvisor通知器(通知器可以有多个),同时该代理对象还实现了myInterface接口

  3. ProxyFactoryBean的构成

    1. target:目标对象,需要对其进行切面增强
    2. proxyInterfaces:代理对象所实现的接口
    3. interceptorNames:通知器(Advisor)列表,通知器中包含了通知(Advice)和切点(Pointcut)
  4. ProxyFactoryBean的作用 总的来说,ProxyFactoryBean的作用可以用下面这句话概括:针对目标对象来创建代理对象,将对目标对象方法的调用转到对相应代理对象方法的调用,并且可以在调用代理对象方法前后执行与之匹配的各个通知器中定义好的方法

目标代理对象的创建

  1. Spring通过两种方式来创建目标代理对象

    1. JDK动态代理
    2. cglib
  2. JDK动态代理:如果目标对象实现了接口,那么Spring就会通过JDK动态代理为目标对象生成代理对象,这也是JDK的要求,要求目标对象必须实现某一个接口,否则实现不了JDK动态代理

  3. 创建动态代理:其中AopProxy是代理的接口,Spring中有两个实现类:JdkDynamicAopProxy(JDK动态代理)和CglibAopProxy(cglib动态代理)

    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
    			Class targetClass = config.getTargetClass();
    			if (targetClass == null) {
    				throw new AopConfigException("TargetSource cannot determine target class: " +
    						"Either an interface or a target is required for proxy creation.");
    			}
    			if (targetClass.isInterface()) {
    				return new JdkDynamicAopProxy(config);
    			}
    			return CglibProxyFactory.createCglibProxy(config);
    		}
    		else {
    			return new JdkDynamicAopProxy(config);
    		}
    	}
    
    

代理模式

  1. 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问

  2. 在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用

  3. 代理模式一般涉及到的角色有:

    1. 抽象角色:声明真实角色和代理角色对象的共同接口

    2. 代理角色:代理对象角色内部含有真实角色的引用,从而可以操作真实对象,同时代理对象提供与真色角色相同的接口以便在任何时刻都能代替真实对象;同时代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装

    3. 真实角色:代理对象所代表的真实对象,是我们最终要引用的对象

    4. 代码示例:

      // 公共接口
      public interface Subject{
         void doSomething();
      }
      
      // 真实角色
      public class RealSubject implements Subject {
         @Override
         public void doSomething() {
            System.out.println("做些什么呢?");
         }
      }
      
      // 代理角色
      public class ProxySubject implements Subject {
         private RealSubject realSubject;
         public ProxySubject() {
            if(realSubject == null) {
               realSubject = new RealSubject();
            }
         }
      
         @Override
         public void doSomething() {
            System.out.println("befor do something");
            realSubject.doSomething();
            System.out.println("after do something");
         }
      }
      
      // 客户端访问
      public class Client {
         public static void main(String[] args) {
            Subject subject = new ProxySubject();
            subject.doSomething();
         }
      }
      
    5. 由以上代码可以看出,客户实际上需要调用的是真实角色RealSubject的方法,现在用ProxySubject来代理RealSubject类,同样达到目的,同时还可以封装其他方法,可以处理其他问题

    6. 另外,如果按照上述的方法使用代理模式,那么真实角色必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色,该如何使用代理呢?此时可以使用动态代理来解决!

  4. 动态代理:Spring中动态代理实现有两种方式:JDK动态代理和cglib动态代理

    1. Java动态代理位于java.lang.Reflect包下,一般主要涉及到以下两个类(接口):

      1. Interface InvocationHandler:该接口中仅定义了一个方法:

        public object invoke(Object object,Method method,Object[] args);
        

        在实际使用时,第一个参数obj一般指代理类,method一般指被代理的方法,args为该方法的参数数组,如果没参数则为null,这个抽象方法在代理类中动态实现。

      2. Proxy:该类即为动态代理类,其中主要包含以下内容:

        static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
        

        返回代理类的一个实例,返回后的代理类可以被当做被代理类使用

    2. 动态代理是这样一种class:在运行时生成class(代理类),在生成它时必须提供一组interface给它,然后该class就会声明它实现了这些interface,因此可以将该class的实例当做这些interface中的任何一个来使用。实际上,这个class类就是一个Proxy类,它不会替你做实质性的工作,在生成它的实例时必须提供一个handler,由handler接管实际的工作。(在Java里面有这样一个限定:要想创建某一个对象的代理对象,那么该对象必须实现一个接口,每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,将对方法调用进行编码并将其指派到它的调用处理程序InvocationHandler的 invoke 方法。所以对代理方法的调用都是通InvocationHadler的invoke来实现中,而invoke方法根据传入的代理对象,方法和参数来决定调用代理的哪个方法。)

    3. JDK动态代理代码示例:

      // 公共接口
      public interface Subject{
         void doSomething();
      }
      
      // 真实角色
      public class RealSubject implements Subject {
         @Override
         public void doSomething() {
               System.out.println("做些什么呢?");
         }
      }
      
      // 动态代理角色
      public class DynamicProxySubject implement InvocationHandler {
            private Object target;
               public Object getProxy(Object target) {
               this.target = target;
               return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
            }
               public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
               // 前置处理
               System.out.println("befor do something");
               method.invoke(object,args);// ...核心,实际上是真实角色在调用
               // 后置处理
               System.out.println("after do something");
                  return null;
            }
      }
      
      // 客户端访问
      public class Client {
         public static void main(String[] args) {
               // 1. 来一个真实角色:
               Subject realSubject = new RealSubject();
               // 2. 代理类,(相当于一个生成代理实例的工厂),且生成的代理类类名以"$Proxy"开头
               DynamicProxySubject dynamicProxySubject = new DynamicProxySubject();
               // 3. 生成代理实例
               Subject proxy = (Subject) dynamicProxySubject.getProxy(realSubject);
               // 4. 调用方法,实际上会转发到invoke(...)中调用
               proxy.doSomething();
         }
      }
      
    4. 大概流程:

      1. 为接口创建代理类的字节码文件:
      2. 使用ClassLoader将字节码文件加载到JVM
      3. 创建代理类实例对象,执行对象的目标方法