【spring基础】AOP概念与动态代理详解

1、代理模式java

代理模式的英文叫作Proxy或Surrogate,中文均可译为”代理“,所谓代理,就是一我的或者一个机构表明另外一我的或者另外一个机构采起行动。在一些状况下,一个客户不想或者不可以直接引用一个对象,而代理对象能够在客户端和目标对象之间起到中介的做用。程序员

以简单模拟事务的执行过程说明各类代理区别spring

1.1 静态代理express

由程序员建立或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。编程

public interface PersonDao {

    void savePerson();
}
public class PersonDaoImpl implements PersonDao {

    @Override
    public void savePerson() {
        System.out.println("save person");
    }
}
public class Transaction {
    
    void beginTransaction(){
        System.out.println("begin Transaction");
    }
    
    void commit(){
        System.out.println("commit");
    }
}

接下来编写静态代理类---实现PersonDao接口数组

/**
 * 静态代理类
 * @author qjc
 */
public class PersonDaoProxy implements PersonDao{

    PersonDao personDao;
    Transaction transaction;
    
    public PersonDaoProxy(PersonDao personDao, Transaction transaction) {
        this.personDao = personDao;
        this.transaction = transaction;
    }

    @Override
    public void savePerson() {
        this.transaction.beginTransaction();
        this.personDao.savePerson();
        this.transaction.commit();
    }
}

测试app

/**
 * 测试静态代理
 * @author qjc
 */
public class TestPersonProxy {
    
    @Test
    public void testSave(){
        PersonDao personDao = new PersonDaoImpl();
        Transaction transaction = new Transaction();
        PersonDaoProxy proxy = new PersonDaoProxy(personDao, transaction);
        
        proxy.savePerson();
    }
}

总结:ide

  一、静态代理模式并无作到事务的重用模块化

  二、假设dao有100个类,100个proxy,接口中有多少方法,在proxy层就得实现多少方法,有多少方法就要开启和提交多少事务工具

  三、若是一个proxy实现了多个接口,若是其中的一个接口发生变化(添加了一个方法),那么proxy也要作相应改变

 

1.2 JDK动态代理

动态代理类:在程序运行时,运用反射机制动态建立而成。

JDK的动态代理必须具有四个条件:一、目标接口 二、目标类 三、拦截器 四、代理类

 

使用上个例子的PersonDao接口、PersonDaoImpl类及Transaction类

编写拦截器

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

/**
 * 拦截器 
 *         一、目标类导入进来 
 *         二、事物导入进来 
 *         三、invoke完成:开启事务、调用目标对象的方法、事务提交
 * 
 * @author qjc
 */
public class Interceptor implements InvocationHandler {

    private Object target; // 目标类
    private Transaction transaction;

    public Interceptor(Object target, Transaction transaction) {
        this.target = target;
        this.transaction = transaction;
    }

    /**
     * @param proxy 目标对象的代理类实例
     * @param method 对应于在代理实例上调用接口方法的Method实例
     * @param args 传入到代理实例上方法参数值的对象数组
     * @return 方法的返回值,没有返回值是null
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        String methodName = method.getName();
        if ("savePerson".equals(methodName)
                || "deletePerson".equals(methodName)
                || "updatePerson".equals(methodName)) {

            this.transaction.beginTransaction(); // 开启事务
            method.invoke(target); // 调用目标方法
            this.transaction.commit(); // 提交事务

        } else {
            method.invoke(target);
        }
        return null;
    }
}

测试

/**
 * 测试jdk动态代理
 * @author qjc
 */
public class TestJDKProxy {
    
    @Test
    public void testSave(){
        /**
         * 一、建立一个目标对象
         * 二、建立一个事务
         * 三、建立一个拦截器
         * 四、动态产生一个代理对象
         */
        Object target = new PersonDaoImpl();
        Transaction transaction = new Transaction();
        Interceptor interceptor = new Interceptor(target, transaction);
        /**
         * 参数一:设置代码使用的类加载器,通常采用跟目标类相同的类加载器
         * 参数二:设置代理类实现的接口,跟目标类使用相同的接口
         * 参数三:设置回调对象,当代理对象的方法被调用时,会调用该参数指定对象的invoke方法
         */
        PersonDao personDao = (PersonDao) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                interceptor);
        personDao.savePerson();
    }
}

总结:

  一、由于利用JDKProxy生成的代理类实现了接口,因此目标类中全部的方法在代理类中都有。

  二、生成的代理类的全部的方法都拦截了目标类的全部的方法。而拦截器中invoke方法的内容正好就是代理类的各个方法的组成体。

       三、利用JDKProxy方式必须有接口的存在。

       四、invoke方法中的三个参数能够访问目标类的被调用方法的API、被调用方法的参数、被调用方法的返回类型。

缺点:

  一、在拦截器中除了能调用目标对象的目标方法之外,功能是比较单一的,在这个例子中只能处理事务
  二、拦截器中的invoke方法的if判断语句在真实的开发环境下是不靠谱的,由于一旦方法不少if语句须要写不少。

 

1.3 CGLIB动态代理

使用上个例子的PersonDaoImpl类和Transaction类(不用接口)

编写拦截器类

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * CGLIB代理 拦截器 
 * @author qjc
 */
public class Interceptor  implements MethodInterceptor {

    private Object target; // 代理的目标类
    private Transaction transaction;

    public Interceptor(Object target, Transaction transaction) {
        this.target = target;
        this.transaction = transaction;
    }

    /**
     * 建立目标对象的代理对象
     * 
     * @return
     */
    public Object createProxy() {
        // 代码加强
        Enhancer enhancer = new Enhancer(); // 该类用于生成代理对象
        enhancer.setCallback(this); // 参数为拦截器
        enhancer.setSuperclass(target.getClass());// 设置父类
        return enhancer.create(); // 建立代理对象
    }

    /**
     * @param obj 目标对象代理类的实例
     * @param method 代理实例上 调用父类方法的Method实例
     * @param args 传入到代理实例上方法参数值的对象数组
     * @param methodProxy 使用它调用父类的方法
     * @return
     * @throws Throwable
     */
    public Object intercept(Object obj, Method method, Object[] args,
            MethodProxy methodProxy) throws Throwable {
        this.transaction.beginTransaction();
        method.invoke(target);
        this.transaction.commit();
        return null;
    }
}

测试

/**
 * 测试cglib动态代理
 * 经过cglib产生的代理对象,代理类是目标类的子类
 * @author qjc
 */
public class TestCglibProxy {
    
    @Test
    public void testSave(){
    
        Object target = new PersonDaoImpl();
        Transaction transaction = new Transaction();
        Interceptor interceptor = new Interceptor(target, transaction);
        
        PersonDaoImpl personDaoImpl = (PersonDaoImpl) interceptor.createProxy();
        personDaoImpl.savePerson();
    }
}

总结:

  一、CGlib是一个强大的,高性能,高质量的Code生成类库。它能够在运行期扩展Java类与实现Java接口。

  二、用CGlib生成代理类是目标类的子类。

  三、用CGlib生成 代理类不须要接口

  四、用CGLib生成的代理类重写了父类的各个方法。

  五、拦截器中的intercept方法内容正好就是代理类中的方法体

 

CGLIB和JDK动态代理区别:

  JDK:

        目标类和代理类实现了共同的接口

        拦截器必须实现InvocationHandler接口,而这个接口中invoke方法体的内容就是代理对象方法体的内容

  CGLIB:

        目标类 是代理类的父类

        拦截器必须实现MethodInterceptor接口,而接口中的intercept方法就是代理类的方法体,使用字节码加强机制建立代理对象的.

 

2、面向切面编程

OOP(面向对象编程):封装、继承、多态、抽象

        封装,对代码进行基本的管理、模块化的管理。每一个类可能都有本身的职能,出了问题就是论事找人就好了。从修改角度讲,直接修改代码可能有风险,这不是个长远之计,最天然的是从类型封装变化。可是新的类型和旧的体系之间怎么去融合,因此说须要在类与类之间创建一种血缘关系。那么这就是继承的需求,经过继承就能够发现这些类之间是有关联的,它们之间是有父子关系的。而后在继承基础之上多态起决定性的特征。因此说通常认为面向对象最核心的特征实际上是多态前面几个都是在作铺垫的多态才是它最核心的特征子类中经过重写方法表明了扩展这个层面的东西而它能融入老的体系中可以正常工做这是重用这个层面的东西新的方法旧的体系扩展和重用。

 

AOP(面向切面编程):

      面向切面编程,是一种经过预编译方式运行期动态代理实如今不修改源代码的状况下给程序动态统一添加功能的一种技术.

 

OOPAOP区别:

  OOP:针对业务处理过程的实体及其属性和行为进行抽象封装,以得到更加清楚的逻辑单元划分。

   AOP:针对业务处理过程当中的横切逻辑 进行提取,它所面对的是处理过程当中的某个步骤或者阶段,以得到逻辑过程当中各部分之间低耦合的隔离效果。这两种设计思想在目标上有着本质的差别。AOP作到了代码块的重用

 

spring AOP代理机制:

  一、若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。

          优势:由于有接口,因此使系统更加松耦合

          缺点:为每个目标类建立接口

  二、若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。

          优势:由于代理类与目标类是继承关系,因此不须要有接口的存在。

          缺点:由于没有使用接口,因此系统的耦合性没有使用JDK的动态代理好。

 

使用第一个例子的 PersonDao接口、PersonDaoImpl类和Transaction类

编写spring配置

    <bean id="personDao" class="cn.qjc.aop.xml.PersonDaoImpl"></bean>
    <bean id="transaction" class="cn.qjc.aop.xml.Transaction"></bean>
    
    <aop:config>
        <!-- 切入点表达式  肯定目标类 -->
        <aop:pointcut expression="execution(* cn.qjc.aop.xml.PersonDaoImpl.*(..))" id="perform"/>
    
        <!-- ref指向对象就是切面 -->
        <aop:aspect ref="transaction">
            <aop:before method="beginTransaction" pointcut-ref="perform"/>
            <aop:after-returning method="commit" pointcut-ref="perform"/>
        </aop:aspect>
    </aop:config>
    
</beans>

测试

/**
 * 测试spring动态代理
 * @author qjc
 */
public class TransactionTest {

    @Test
    public void testSave(){
        ApplicationContext context = new ClassPathXmlApplicationContext("cn/qjc/aop/xml/applicationContext.xml");
        PersonDao personDao = (PersonDao) context.getBean("personDao");
        personDao.savePerson();
    }
}

 

spring AOP原理

  一、当spring容器启动的时候,加载两个bean,对像个bean进行实例化  二、当spring容器对配置文件解析到<aop:config>的时候,把切入点表达式解析出来,按照切入点表达式匹配spring容器内容的bean  三、若是匹配成功,则为该bean建立代理对象  四、当客户端利用context.getBean获取一个对象时,若是该对象有代理对象,则返回代理对象,若是没有代理对象,则返回对象自己

相关文章
相关标签/搜索