zoukankan      html  css  js  c++  java
  • 什么是aop?

    这个命题其实是讲了的,但是之前没有做,发现一些面试会问到,结合自己之前的学习经历。简单把这个问题描述一下。

    aop是跟oop相对应的一个概念。分别是aspect oriented programming 和 object oriented programming。也即面向切面编程和面向对象编程。那面向对象是我们一个很熟悉的概念了,【在面向对象之前还有面向过程的编程,这个概念这里面就不厘了】。画个示意图,希望能够阐述这样一个概念。

    那么对于前面的就是得到一个对象,或者得到一个完整的过程,直接对他进行操作。【事实上前面儿这个图画的也不理想】

    对于后面的图来说,面向的是整个过程的一个剖儿面。

    比如说下面的存储过程。

    原来的逻辑是 一个数据库的存储操作,现在,我们要在这个 完成的逻辑上面添加一些日志,比如说,在存储之前,我们让日志信息记录说,UserDaoImpl请求添加一条记录到数据库中,在存储操作完成以后,让日志信息再打印一个 UserDaoImpl已经成功添加了一条数据。

    对于这个完整过程来说,原来的逻辑是完完全全没有问题的。但是如果在一个大型系统中出了问题,如果在每个地方都有相应的提示信息的话,可以帮助我们快速定位到问题从而顺利解决。所以如果打印到“UserDaoImpl请求添加一条记录到数据库中”,然后报了错,下面那句话没有执行,是不是就很方便可以定位到存储时候出了问题。【这里只是举个例子说明织入也就是aop这个概念。之前dao都没出过问题,加上了切面反而出了问题,那还要这个切面干毛线?这个假设简直不能再糟糕,笑哭】【想到一个可能的逻辑,囧,比如在oracle里面的年龄字段写了check 18 to 60,而在前端的校验只定义成了0-150合理,这样在junit测试里面没有把例子跑全,或者自己只写了一个简单的测试用例比如数字都是23,34,59。导致在集成测试的时候有一个78被放行,这样就有可能出问题,在修改的时候可能回去数据库里面更改check 或者 去js验证里面,更改放行数字。终于圆回来了,反正总之现在的需求就是加一个aop!!!】

    好了问题引入完毕,先来扯点儿蛋。

    aop的知识点是从代理模式引入的。

    那么什么是代理,为什么要代理。代理是怎么一回事儿?

    代理分为两种形式,静态代理,动态代理。

    假设有一个接口UserDao,【这个包名我就很喜欢】

    package com.letben.dao;
    
    /**
     * userdao的 接口
     * @author Administrator
     *
     */
    public interface UserDao {
        public void saveUser();
    }

    还有一个它的实现类UserDaoImpl。

    package com.letben.dao;
    /**
     * 这个地方用来实现userdao的 真实逻辑
     * @author Administrator
     */
    public class UserDaoImpl implements UserDao {
        @Override
        public void saveUser() {
            System.out.println("存储用户到数据库");
        }
    }

    那原来这两个就能完成存储逻辑。

    现在需求改变我们要加一个代理,实现在存储之前之后打印日志。那么新写一个UserDaoStaticProxy

    package com.letben.dao;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    /**
     * 同样是userdao的一个 实现类,没有更多的业务逻辑。只是为了在 实现逻辑的前后 打印日志
     * @author Administrator
     */
    public class UserDaoStaticProxy implements UserDao {
        //得到打印日志的对象,它属于util包
        Logger logger = Logger.getLogger(UserDaoStaticProxy.class.getName());
        UserDao userDao;
        public UserDaoStaticProxy(UserDao userDao){
            this.userDao = userDao;
        }
        @Override
        public void saveUser() {
            logger.log(Level.INFO, "存储用户之前");
            userDao.saveUser();
            logger.log(Level.INFO,"存储用户之后");
        }
    }

    然后添加一段测试代码:

    package com.letben.dao;
    public class Test {
        public static void main(String[] args) {
            UserDao userDao = new UserDaoImpl();
            UserDao userDaoApplication = new UserDaoStaticProxy(userDao);
            userDaoApplication.saveUser();
        }
    }

    运行结果:

    二月 15, 2016 9:50:34 上午 com.letben.dao.UserDaoStaticProxy saveUser
    信息: 存储用户之前
    存储用户到数据库
    二月 15, 2016 9:50:35 上午 com.letben.dao.UserDaoStaticProxy saveUser
    信息: 存储用户之后
    

    在测试代码中就能够理解是为什么了。我们首先创建了一个UserDaoImpl也就是最开始用来完成存储业务的类。然后又创建了UserDaoImpl来增加日志信息。这样aop的概念,就比较好解释了,就是在这个逻辑不改变的情况下,多那么一点点东西,让整个流程更加清楚、完善。

    但是静态代理毕竟可复用性太差,要重新写java代码,这就比较糟糕。

    那动态代理的部分,就是利用框架,来实现这样一种需求的嵌入。这还有两种形式,一种是xml的配置形式,还有一种是 注解的形式。

    形式一:xml配置形式

    创建web工程。

    导入jar包。【没有的在下面写邮箱吧,或者小纸条我也行。】

    目录结构。

    1、那我们其实真正做的就是后来,导入了一些专用的jar包。

    2、添加一个切面包。以及文件:AspectDemo.java。

    package com.letben.aspect;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import org.aspectj.lang.ProceedingJoinPoint;
    /**
     * 切面类
     * @author Administrator
     */
    public class AspectDemo {
        Logger log = Logger.getLogger(AspectDemo.class.getName());
        /**
         *  在某断点之前添加通知,我们这里就是 给 添加方法 之前设置的 所以 这个 方法的名字,也是 这么取名的
         */
        public void beforeSaveUser(){
            log.log(Level.INFO, "存储前");
        }
        /**
         * 在saveUser方法 调用之后 执行此方法
         */
        public void afterSaveUser(){
            log.log(Level.INFO,"存储后");
        }
        public void aroundSaveUser(ProceedingJoinPoint point){
            log.log(Level.INFO,"环绕-前");
            try {
                point.proceed();
            } catch (Throwable e) {
                e.printStackTrace();
            }
            log.log(Level.INFO,"环绕-后");
        }
        public void exceptionAboutSave(Exception e){
            //在这里并不会有结果,因为 上一个 已经 处理了异常,并且 我们 的存储方法 是正确的 不存在 输入错误 这回事。。所以 为了巩固 我们再写一个 面向切面代理出错版本。
            log.log(Level.WARNING,"出现异常");
        }
    }

    3、在beans.xml里面进行配置。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
    
    
        <!-- 要有 对应 实现逻辑的 userDao -->
        <bean id="userDao" class="com.letben.dao.UserDaoImpl"></bean>
    
        <!-- 切面类 对应的 实体对象 -->
        <bean id="aspectDemo" class="com.letben.aspect.AspectDemo"></bean>
        <!-- aop的配置 -->
        <aop:config>
            
            <!-- 选定某一个类为切面 -->
            <aop:aspect ref="aspectDemo">
                <!-- 切入点表达式,就是 我们为了做切入,一定要有一个切入点,用来表示 在某一个地方开始 执行我们的某些程序-->
                <!-- 文档中 6.2.3.4里面的各个 示例 -->
                <!-- 解释:任意的修饰符  【空格】 这个 包下的 这个 方法里面的所有方法(这里面带着所有的参数)-->
                <aop:pointcut expression="execution(* com.letben.dao.UserDao.*(..))" id="point" />
                
                <!-- 通知类型+插入方法+切入点 =连接点 -->
                <aop:before method="beforeSaveUser" pointcut-ref="point" />
                <!-- 
                    上面这句话有两种书写方式:
                    <aop:before method="beforeSaveUser" pointcut="execution(* com.letben.dao.UserDao.*(..))">
                    要么直接 写切入点,要么采用引入的方式 写 切入点
                 -->
                 <aop:after method="afterSaveUser" pointcut-ref="point"/>
                 <aop:around method="aroundSaveUser" pointcut-ref="point"></aop:around>
                
            </aop:aspect>
        </aop:config>
    </beans>

    这样就完成了动态代理的xml形式的书写。

    注解方式:

    注解编程里面,配置就比较简单。

    jar包还是那些。

    beans.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
    
        <!-- 启动注解编程 -->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
        <bean id="userDao" class="com.letben.dao.impl.UserDaoImpl"></bean>
        
        <!-- 因为 当前注解类 并不需要 给谁使用,所以 直接注册 并不需要 别的什么逻辑 -->
        <bean class="com.letben.aspect.AspectForUserDao"/>
    
    </beans>

    切面类:

    package com.letben.aspect;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    /**
     * 声明这是一个 注解类
     * @author Administrator
     */
    @Aspect
    public class AspectForUserDao {
        Logger logger = Logger.getLogger(AspectForUserDao.class.getName());
        /**
         * 前置 方法 里面需要一个 切入点
         */
        @Before("execution(* com.letben.dao.UserDao.*(..))")
        public void test1(){
            logger.log(Level.INFO,"之前");
        }
        /**
         * 在7.0里面 那个 value 不能加上。但是 在 6.0里面这个 参数 是需要的
         * @Around(value="exection(*com.letben.dao.UserDao.*(..))")这是不正确的写法 在7.0里面
         */
        @After("execution(* com.letben.dao.UserDao.*(..))")
        public void test2(){
            logger.log(Level.INFO,"之后");
        }
        /**
         * 不,不是 那个 value 也不知道 是哪里,这个 注解编程 很奇怪,就是 很奇怪。我也不知道 那里 写错了,但是 就是 报了 一些 处理了20min的异常就在这个 写注解的地方
         * @param point
         */
        @Around(value="execution(* com.letben.dao.UserDao.*(..))")
        public void test3(ProceedingJoinPoint point){
            logger.log(Level.INFO,"环绕-前");
            try {
                point.proceed();
            } catch (Throwable e) {
                e.printStackTrace();
            }
            logger.log(Level.INFO,"环绕-后");
        }
    }

    所以动态的代理就是 我们所说的aop也就是织入了一个逻辑。织入这个词,感觉翻译起来还是很贴切的。就是在完整的东西上加进去一些东西,让他更加完美。上面是对日志的织入。

    下面有事务的织入。略有不同。

    只说修改的地方了,原来的那个工程还是有点儿小。。。

    对于事务这样的操作,想来应该是放到service层里面最合适。比如存取钱的操作同时完成。不应该封装到dao层理面,而应该是 服务层理面,但是其实,这样的操作也可在dao里面写,但是框架提供了这样的方式,可以让我们进行事务操作。所以我们来应用一下。

    【完整样例代码:欢迎写邮箱,或者小纸条我,主要十几个包里面的十几个类,复制粘贴确实容易让人没有兴趣看下去。但是都是前面儿的一些知识点的总结。】

     serviceImpl 里面添加:

    /**
         * 事务这样的操作都写在 业务层 而非 持久化层
         */
        public void tryTransaction(){
            UserPo user1 = getUserById(2);
            UserPo user2 = getUserById(3);
            user1.setUserAge(39);
            userDao.updateUser(user1);
            user2.setUserAge(Integer.parseInt("23"));
            userDao.updateUser(user2);
        }

    在beans.xml里面新增事务工厂:

        <!-- 事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 事务管理器 管理的是哪一个 数据源 -->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!-- 配置事务的代理工厂 -->
        <bean id="proxyFactoryBean" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
            <!-- 给事务代理工厂一个 事务管理器  -->
            <property name="transactionManager" ref="transactionManager"></property>
            <!-- 告诉代理工厂 的代理权限——要管理那些接口 -->
            <property name="proxyInterfaces">
                <list><!-- 列表给值 底层 list和 set 是一个 类型的 -->
                    <value>com.letben.service.UserService</value>
                </list>
            </property>
            <!-- 告诉代理工厂要管理的 实现类 -->
            <property name="target" ref="UserService"></property>
            <!-- 告诉事务管理器 要管理的规则 和方法 -->
            <property name="transactionAttributes">
                <props><!-- 参数给值  底层 props 和 map 是一个类型的 -->
                    <!-- 下面这个 key对应的是实现类里面的方法。符合通配符的使用规则* -->
                    <prop key="tryTransaction">PROPAGATION_REQUIRED</prop><!-- 需要传播 -->
                </props>
            </property>
        </bean>    

    这样就完成了 带有工厂的新增事务的配置方式。

    当然还有两种。一种是不使用事务工厂的方式,还有一种是自动代理的方式。

    当然这种织入不仅有日志的添加,事务的处理,还包括权限的检查等等多个方面。所谓切面编程就是在不影响原来事情处理逻辑的基础上,添加内容,让这个流程变得更加完善合情理合逻辑。

  • 相关阅读:
    BZOJ3197:[SDOI2013]刺客信条——题解
    C 程序与 C++ 程序之间的相互调用
    使用Dev C++调试(debug)程序
    ARM 汇编指令 ADR 与 LDR 使用
    华为交换机以 LACP 模式实现链路聚合
    DLCI 简介
    华为路由器帧中继 FR 实验
    GVRP 的工作机制和工作模式
    华为路由器 HDLC 实验
    华为路由器 IPSec 与 GRE 结合实验
  • 原文地址:https://www.cnblogs.com/letben/p/5190103.html
Copyright © 2011-2022 走看看