web学习笔记19-静态代理,动态代理,spring AOP

1、AOP的原理就是使用动态代理:

首先我们看下为什么要使用动态代理,先看看静态代理有什么问题。
演示一个静态代理的例子:(jdk的代理是基于接口的)
功能就是,保存数据的时候添加事务处理
    //定义接口 PersonDao.java
        public interface PersonDao {
            public void savePerson();
        }
    //定义个实现类 PersonDaoImpl.java
        public class PersonDaoImpl implements PersonDao{
            public void savePerson() {
                System.out.println("save person");
            }
        }
    //定义一个事务类 Transaction.java
        public class Transaction {
            public void beginTransaction(){
                System.out.println("begin transaction");//开启事务
            }

            public void commit(){
                System.out.println("commit");//提交事务
            }
        }
    //定义个代理类 PersonDaoProxy.java
        public class PersonDaoProxy implements PersonDao{ //实现相同的接口
            private PersonDao personDao;
            private Transaction transaction;
            public PersonDaoProxy(PersonDao personDao,Transaction transaction) {
                super();
                this.personDao = personDao;//通过构造传进来
                this.transaction = transaction;
            }

            public void savePerson() {
                this.transaction.beginTransaction();
                this.personDao.savePerson();
                this.transaction.commit();
            }
        }
    //测试 ProxyTest.java
        public class ProxyTest {
            @Test
            public void testProxy(){
                PersonDao personDao = new PersonDaoImpl();
                Transaction transaction = new Transaction();
                PersonDaoProxy proxy = new PersonDaoProxy(personDao, transaction);
                proxy.savePerson();
            }
        }
看看静态代理有什么问题:
a.静态代理没有事务重用,我们还是要在每个方法里面调用this.transaction.beginTransaction();
b.如果我们dao层有100个方法,我们就需要写100个proxy类,变得更麻烦了。接口中定义了多少个方法,
    proxy也要实现多少个方法。
c.如果一个proxy实现了多个接口,如果其中的一个接口发生变化(添加了一个方法),那么proxy也要做相应的改变。
所以静态代理我们维护起来会更加的麻烦。

2、动态代理:

既然静态代理这么多的问题,那么我们看看动态代理。
顾名思义,动态代理就是动态生成Proxy类,不需要我们自己创建Proxy了。
演示下:
    public interface PersonDao {
        public void savePerson();
        public void updatePerson();
    }
    public class PersonDaoImpl implements PersonDao{
        public void savePerson() {
            System.out.println("save person");
        }

        public void updatePerson() {
            System.out.println("update person");
        }
    }
    public class Transaction {
        public void beginTransaction(){
            System.out.println("begin transaction");
        }
        public void commit(){
            System.out.println("commit");
        }
    }

    //最主要的设置拦截器
    public class MyInterceptor implements InvocationHandler{
        private Object target;//目标类
        private Transaction transaction;
        public MyInterceptor(Object target, Transaction transaction) {
            super();
            this.target = target;
            this.transaction = transaction;
        }
        //proxy这个是代表的我们MyInterceptor这个类,千万别写成method.invoke(proxy);
        //否则就死循环,jvm就挂了
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            String methodName = method.getName();
            if("savePerson".equals(methodName)||"updatePerson".equals(methodName)
                    ||"deletePerson".equals(methodName)){
                this.transaction.beginTransaction();//开启事务
                method.invoke(target);//调用目标方法
                this.transaction.commit();//事务的提交
            }else{
                method.invoke(target);
            }
            return null;
        }
    }

    //使用
    public class JDKProxyTest {
        @Test
        public void testJDKProxy(){
            Object target = new PersonDaoImpl();
            Transaction transaction = new Transaction();
            MyInterceptor interceptor = new MyInterceptor(target, transaction);
            /**
             * 参数说明:
             * 1、目标类的类加载器
             * 2、目标类实现的所有的接口
             * 3、拦截器
             */
            PersonDao personDao = (PersonDao)Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                    target.getClass().getInterfaces(), interceptor);
            //personDao.savePerson();
            personDao.updatePerson();
        }
    }
注意几个问题:
a、拦截器的invoke方法是在时候执行的?
    当在客户端,代理对象调用方法的时候,进入到了拦截器的invoke方法。
b、代理对象的方法体的内容是什么?
    拦截器的invoke方法的内容就是代理对象的方法的内容
c、拦截器中的invoke方法中的参数method是谁在什么时候传递过来的?
    代理对象调用方法的时候,进入了拦截器中的invoke方法,所以invoke方法中的参数method就是
    代理对象调用的方法。

我们看看动态代理有什么问题:
a.在拦截器中除了能调用目标对象的目标方法以外,功能是比较单一的,在这个例子中只能处理事务。
    这里可以有个解决方案。比如我们有 Transaction(事务),Log(打印),Access(权限控制)
    我们可以让这几个类同时实现一个接口,我们传一个接口的List过来,然后for循环处理。
    这也是种解决方案。
b.拦截器中的invoke方法的if判断方法名称在真实的开发环境下是不靠谱的,
    因为一旦方法很多if语句需要写很多。
    我有100个方法就要判断100次,而且名称不能写错。
    这个暂时没有好的解决方法,在spring中可以解决。
c.调用时机的问题,我们是在invoke方法之前调用还是在invoke方法之后调用,或者是出异常在调用。

3、基于动态代理中的第一个问题,解决方案:

    //给事务、日志等做了一个抽象,而这个抽象就是Interceptor
    public interface Interceptor {
        public void interceptor();
    }
    //事务实现了接口,其他的也同样的做法
    public class Transaction implements Interceptor{
        public void interceptor() {
            System.out.println("begin transaction");
            System.out.println("commit");
        }
    }
    //拦截器传的是一个 List<Interceptor>
    public class MyInterceptor implements InvocationHandler{
        private Object target;//目标类
        //除了目标类以外的所有的功能都抽象为Interceptor
        private List<Interceptor> interceptors;

        public MyInterceptor(Object target, List<Interceptor> interceptors) {
            super();
            this.target = target;
            this.interceptors = interceptors;
        }

        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            for (Interceptor interceptor : interceptors) {//这样调用了多个功能方法
                interceptor.interceptor();
            }
            method.invoke(target);
            return null;
        }
    }
    //使用
    @Test
    public void testJDKProxy(){
        /**
         * 1、创建一个目标对象
         * 2、创建一个事务
         * 3、创建一个拦截器
         * 4、动态产生一个代理对象
         */
        Object target = new PersonDaoImpl();
        Transaction transaction = new Transaction();
        List<Interceptor> interceptors = new ArrayList<Interceptor>();
        interceptors.add(transaction);//我们可以添加不同的interceptor
        MyInterceptor interceptor = new MyInterceptor(target, interceptors);

        PersonDao personDao = (PersonDao)Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                target.getClass().getInterfaces(), interceptor);
        personDao.updatePerson();
    }

4、cglib动态代理:

我们上面说的是jdk的动态代理,是基于接口的,我们的目标类需要实现接口,代理类就实现相同的接口。
cglib代理是基于子类的。
演示下:
    //首先需要导入cglib的包
    cglib-nodep-2.1_3.jar

    //我们现在就没有接口了
    public class PersonDaoImpl{
        public void savePerson() {
            System.out.println("save person");
        }
        public void updatePerson() {
            System.out.println("update person");
        }
    }
    public class Transaction {
        public void beginTransaction(){
            System.out.println("begin transaction");
        }

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

    //我们的拦截器
    public class MyInterceptor implements MethodInterceptor{
        private Object target;//目标类
        private Transaction transaction;

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

        public Object createProxy(){
            //代码增强类
            Enhancer enhancer = new Enhancer();
            enhancer.setCallback(this);//参数为拦截器
            enhancer.setSuperclass(target.getClass());//生成的代理类的父类是目标类
            return enhancer.create();
        }

        public Object intercept(Object arg0, Method method, Object[] arg2,
                MethodProxy arg3) throws Throwable {
            this.transaction.beginTransaction();
            method.invoke(target);
            this.transaction.commit();
            return null;
        }
    }
    //使用
    //通过cglib产生的代理对象,代理类是目标类的子类
    public class CGLibProxyTest {
        @Test
        public void testCGlib(){
            Object target = new PersonDaoImpl();
            Transaction transaction = new Transaction();
            MyInterceptor interceptor = new MyInterceptor(target, transaction);
            //使用拦截器创建一个代理的对象
            PersonDaoImpl personDaoImpl = (PersonDaoImpl)interceptor.createProxy();
            personDaoImpl.savePerson();
        }
    }

使用cglib的好处是我们目标类不需要实现接口就可以完成动态代理功能了。

讲了这么多,还是没解决多if判断等问题。

5、AOP的一些基本概念:

这里写图片描述

这里写图片描述

6、spring AOP的简单例子:

    public interface PersonDao {
        public void savePerson();
    }
    public class PersonDaoImpl implements PersonDao{
        public void savePerson() {
            System.out.println("save person");
        }
    }
    //切面
    public class Transaction {
        public void beginTransaction(){
            System.out.println("begin transaction");
        }

        public void commit(){
            System.out.println("commit");
        }
    }
    public class TransactionTest {
        @Test
        public void testTransaction(){
            ApplicationContext context = 
                    new ClassPathXmlApplicationContext("applicationContext.xml");
            PersonDao personDao = (PersonDao)context.getBean("personDao");
            personDao.savePerson();
        }
    }
    //xml配置
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans 
               http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
               http://www.springframework.org/schema/aop 
               http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
        <bean id="personDao" class="com.example.spring.aop.xml.transaction.PersonDaoImpl"></bean>
        <bean id="transaction" class="com.example.spring.aop.xml.transaction.Transaction"></bean>

        <aop:config>
            <!-- 
                切入点表达式  确定目标类
             -->
            <aop:pointcut 
                expression="execution(* com.example.spring.aop.xml.transaction.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>
    //测试
    public class TransactionTest {
        @Test
        public void testTransaction(){
            ApplicationContext context = 
                    new ClassPathXmlApplicationContext("applicationContext.xml");
            PersonDao personDao = (PersonDao)context.getBean("personDao");
            personDao.savePerson();
        }
    }

7、spring AOP的原理:

    a、当spring容器启动的时候,加载两个bean,对两个bean进行实例化。
    b、当spring容器对配置文件解析到<aop:config>的时候,
        把切入点表达式解析出来,按照切入点表达式匹配spring容器内容的bean
    c、如果匹配成功,则为该bean创建代理对象
    d、当客户端利用context.getBean获取一个对象时,如果该对象有代理对象,则返回代理对象
        如果没有代理对象,则返回对象本身

8、spring AOP的各种通知:

前置通知:在目标方法执行之前
后置通知:在目标方法执行之后,方法抛出异常以后是不会执行的。
最终通知:类似finally功能,无论目标方法是否抛出异常都将执行。
异常通知:接受目标方法抛出的异常
环绕通知:joinPoint.proceed();这个代码如果在环绕通知中不写,则目标方法不再执行。
    可以控制目标方法的执行。

简单的演示下:
    public interface PersonDao {
        public String savePerson();
    }
    public class PersonDaoImpl implements PersonDao{
        public String savePerson() {
            int a = 1/0;//构造一个异常
            System.out.println("save person");
            return "aaa";
        }
    }

    //切面
    public class Transaction {
        //前置通知
        public void beginTransaction(JoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            System.out.println("连接点的名称:"+methodName);
            System.out.println("目标类:"+joinPoint.getTarget().getClass());
            System.out.println("begin transaction");
        }

        //后置通知,可以使用xml定义的val接受目标方法的返回值
        public void commit(JoinPoint joinPoint,Object val){
            System.out.println("目标方法的返回值:"+val);
            System.out.println("commit");
        }

        //最终通知
        public void finallyMethod(){
            System.out.println("finally method");
        }

        //异常通知,注意 ex 是在xml文件中定义的
        public void throwingMethod(JoinPoint joinPoint,Throwable ex){
            System.out.println(ex.getMessage());
        }

        //环绕通知
        public void aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable{
            System.out.println("aaaa");
            joinPoint.proceed();//调用目标方法
        }
    }
xml的配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans 
               http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
               http://www.springframework.org/schema/aop 
               http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
        <bean id="personDao" class="com.example.spring.aop.xml.transaction.PersonDaoImpl"></bean>
        <bean id="transaction" class="com.example.spring.aop.xml.transaction.Transaction"></bean>

        <aop:config>
            <aop:pointcut //切入点
                expression="execution(* com.example.spring.aop.xml.transaction.PersonDaoImpl.*(..))" 
                id="perform"/>
            <!-- 
                ref指向的对象就是切面
             -->
            <aop:aspect ref="transaction">
                <aop:before method="beginTransaction" pointcut-ref="perform"/>
                <aop:after-returning method="commit" pointcut-ref="perform" returning="val"/>
                <aop:after method="finallyMethod" pointcut-ref="perform"/>
                <aop:after-throwing method="throwingMethod" throwing="ex" pointcut-ref="perform"/>//定义了一个ex的异常
                <aop:around method="aroundMethod" pointcut-ref="perform"/>
            </aop:aspect>
        </aop:config>
    </beans>

注意:前置通知和后置通知能在目标方法的前面和后面加一些代码,但是不能控制目标方法的执行,环绕通知可以控制。
版权声明:本文为gaopinqiang原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/gaopinqiang/article/details/77587866

智能推荐

Spring AOP与动态代理

代码如下: 接口类 package test; public interface Caculate { } 接口实现类 package test; public class AddCaculate implements Caculate{ } 代理类 package test; public class CaculateProxy implements Caculate{ } 测试 AddCacu...

Spring(二)----动态代理、AOP

Spring(二)----动态代理、AOP Spring基础知识学习笔记(二),内容包括: 代理模式:静态代理和动态代理 AOP实现:注解实现+配置文件实现 切面、通知、切入点、切入点表达式 环绕通知 OOP:(Object Oriented Programming) 面向对象编程。 AOP:(Aspect Oriented Programming) 面向切面编程,基于OOP基础之上的编程思想,在...

Spring Aop基础: 动态代理

重要概念: 通知(advice) 用来定义切面方法(如:日志方法;事务方法;加密解密方法等)调用时机。 前置通知:@Before 目标方法调用前执行 后置通知: @After 目标方法返回或抛出异常后调用 返回通知: @AfterReturning 目标方法返回后调用 异常通知: @AfterThrowing 目标方法抛出异常后调用 环绕通知: @Around 目标方法封装起来(方法前后都调用) ...

Spring学习——【AOP】代理模式 & Spring AOP

Spring AOP 文章目录 Spring AOP 代理模式 1. 静态代理 2. 动态代理 3. CGLIB代理 Spring AOP AOP 术语 通知的类型 简单实现 execution表达式 Spring 框架的一个关键组件是面向方面的编程(AOP)框架。面向方面的编程需要把程序逻辑分解成不同的部分称为所谓的关注点。跨一个应用程序的多个点的功能被称为横切关注点,这些横切关注点在概念上独立...

人工智能基础-数学方法-形式逻辑

1956 年召开的达特茅斯会议宣告了人工智能的诞生。在人工智能的襁褓期,各位奠基者们,包括约翰·麦卡锡、赫伯特·西蒙、马文·明斯基等未来的图灵奖得主,他们的愿景是让“具备抽象思考能力的程序解释合成的物质如何能够拥有人类的心智”。 通俗地说,理想的人工智能应该具备抽象意义上的学习、推理与归纳能力,其通用性将远远强于解决国际象棋或是围棋...

猜你喜欢

P3397 地毯——题解2020.10.3

P3397 地毯 思路分析 定义一个二维数组 a[ ][ ]存放每个点覆盖地毯的个数,下标表示每个点的坐标; 设置一个二重循环依次遍历每个地毯覆盖的坐标范围,使地毯覆盖范围内点的值+1; 打印出该二维数组 a[ ][ ]即为本题答案; 注意事项 由题可知:对于20%的数据,有 n≤50,m≤100;对于100%的数据,有 n,m≤1000;所以数组定义为a[1003][1003]...

反射注解案例

1、反射案例: 需求 写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法 实现: 配置文件 反射 步骤: 创建对象 将需要创建的对象的全类名和需要执行的方法定义在配置文件中 在程序中加载读取配置文件 使用反射技术来加载类文件进内存 执行方法 第一步:Person类(创建对象) 第二步:配置文件 pro.properties(将需要创...

lambert与half lambert模型逐顶点和逐片元的漫反射光照

兰伯特模型 逐顶点光照 逐片元光照: 效果对比:左边为逐片元光照。右边为逐顶点光照。右边明暗交界处有较明显的锯齿 半兰伯特光照模型 兰伯特模型的一个问题就是背光面只有一种颜色,缺乏立体感。Half Lambert用于解决这个问题 半兰伯特模型公式: 与兰伯特模型的差别主要在于不同于将背光面光都设为0,它将背光的光也即负值也映射到[0,1]区间。避免了背光的颜色只有0这一种值。需要注意的是,half...

[React官网入门教程]三子棋游戏完整代码

入门教程: 认识 React 最终效果 完整代码 index.css部分 index.css部分...

票据打印机-ESC/POS指令使用

给打印机输入串口命令,是打印机处于一种状态,然后就能干你想让他干的活了.百度ESC/POS文档随便拿一个正规的都一样,就不在这里放地址了,拿到这个文档以后代码的编写我只举一个例子,其它的模式也都一样 比如说这个功能为初始化打印机,他有三种输入模式,第一种是ASCII码(ESC @),第二种是Hex也就是16进制数(1B 40),第三种Decimal十进制数(27 64),我以16进制为例,那么他的...