zoukankan      html  css  js  c++  java
  • AOP的相关概念

    AOP的相关概念

    简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

    AOP,都知道它是面向切面编程,在网上搜索可以找到很多的解释。这里我用一句话来总结:AOP是能够让我们在不影响原有功能的前提下,为软件横向扩展功能。

    那么横向扩展怎么理解呢,我们在WEB项目开发中,通常都遵守三层原则,包括控制层(Controller)->业务层(Service)->数据层(dao),那么从这个结构下来的为纵向,它具体的某一层就是我们所说的横向。我们的AOP就是可以作用于这某一个横向模块当中的所有方法。

    我们在来看一下AOP和OOP的区别:AOP是OOP的补充,当我们需要为多个对象引入一个公共行为,比如日志,操作记录等,就需要在每个对象中引用公共行为,这样程序就产生了大量的重复代码,使用AOP可以完美解决这个问题。

    切面:拦截器类,其中会定义切点以及通知

    切点:具体拦截的某个业务点。

    通知:切面当中的方法,声明通知方法在目标业务层的执行位置,通知类型如下:

    前置通知:@Before 在目标业务方法执行之前执行
    后置通知:@After 在目标业务方法执行之后执行
    返回通知:@AfterReturning 在目标业务方法返回结果之后执行
    异常通知:@AfterThrowing 在目标业务方法抛出异常之后
    环绕通知:@Around 功能强大,可代替以上四种通知,还可以控制目标业务方法是否执行以及何时执行

    客户的业务层实现类 /** 
    
     * 账户的业务层实现类  
     * @author 黑马程序员 
     * @Company http://www.ithiema.com 
     * @Version 1.0  */
        public class AccountServiceImpl implements IAccountService {    
            private IAccountDao accountDao; 
    
     public void setAccountDao(IAccountDao accountDao) {   
         this.accountDao = accountDao;  } 
    
     @Override 
            public void saveAccount(Account account) throws SQLException {                 				accountDao.save(account);  } 
    
     @Override 
            public void updateAccount(Account account) throws SQLException{   							accountDao.update(account);  } 
    
     @Override  
            public void deleteAccount(Integer accountId) throws SQLException{   						accountDao.delete(accountId); 
     } 
    
     @Override 
            public Account findAccountById(Integer accountId) throws SQLException {   					return accountDao.findById(accountId);  } 
    
     @Override  
            public List<Account> findAllAccount() throws SQLException{   
             	   return accountDao.findAll();  } 
    } 
    
    

    问题就是: 事务被自动控制了。换言之,我们使用了 connection 对象的 setAutoCommit(true) 此方式控制事务,如果我们每次都执行一条 sql 语句,没有问题,但是如果业务方法一次要执行多条 sql 语句,这种方式就无法实现功能了。

    当我们执行时,由于执行有异常,转账失败。但是因为我们是每次执行持久层方法都是独立事务,导致无法实现事务控制(不符合事务的一致性)

    解决办法: 让业务层来控制事务的提交和回滚。(这个我们之前已经在 web 阶段讲过了)

    改造后的业务层实现类: 注:此处没有使用 spring的 IoC.

    通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制,也产生了一个新的问题: 业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了。 试想一下,如果我们此时提交,回滚,释放资源中任何一个方法名变更,都需要修改业务层的代码,况且这还
    只是一个业务层实现类,而实际的项目中这种业务层实现类可能有十几个甚至几十个。

    动态代理的特点

    字节码随用随创建,随用随加载。 它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。 装饰者模式就是静态代理的一种体现。

    pom.xlm

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.itheima</groupId>
        <artifactId>day03_eesy_02proxy</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <dependencies>
            <dependency>
                <groupId>cglib</groupId>
                <artifactId>cglib</artifactId>
                <version>2.1_3</version>
            </dependency>
        </dependencies>
    </project>
    

    动态代理:

    特点:字节码随用随创建,随用随加载

    • 作用:不修改源码的基础上对方法增强
      分类:

      基于接口的动态代理

      基于子类的动态代理

      基于子类的动态代理:
      涉及的类:Enhancer

      提供者:第三方cglib库

      如何创建代理对象:
      使用Enhancer类中的create方法

      创建代理对象的要求:
      被代理类不能是最终类

      create方法的参数:
      Class:字节码

      它是用于指定被代理对象的字节码。

      Callback:用于提供增强的代码

      它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。

      此接口的实现类都是谁用谁写。

      我们一般写的都是该接口的子接口实现类:MethodInterceptor

      client,java

    package com.itheima.cglib;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    /**
     * 模拟一个消费者
     */
    public class Client {
    
        public static void main(String[] args) {
            final Producer producer = new Producer();
    
            Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
                /**
                 * 执行北地阿里对象的任何方法都会经过该方法
                 * @param proxy
                 * @param method
                 * @param args
                 *    以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
                 * @param methodProxy :当前执行方法的代理对象
                 * @return
                 * @throws Throwable
                 */
                @Override
                public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                    //提供增强的代码
                    Object returnValue = null;
    
                    //1.获取方法执行的参数
                    Float money = (Float)args[0];
                    //2.判断当前方法是不是销售
                    if("saleProduct".equals(method.getName())) {
                        returnValue = method.invoke(producer, money*0.8f);
                    }
                    return returnValue;
                }
            });
            cglibProducer.saleProduct(12000f);
        }
    }
    

    Poducter.java

    package com.itheima.cglib;
    
    /**
     * 一个生产者
     */
    public class Producer {
    
        /**
         * 销售
         * @param money
         */
        public void saleProduct(float money){
            System.out.println("销售产品,并拿到钱:"+money);
        }
    
        /**
         * 售后
         * @param money
         */
        public void afterService(float money){
            System.out.println("提供售后服务,并拿到钱:"+money);
        }
    }
    

    动态代理:
    特点:字节码随用随创建,随用随加载

    作用:不修改源码的基础上对方法增强
    分类:

    基于接口的动态代理

    基于子类的动态代理

    基于接口的动态代理:
    涉及的类:Proxy

    提供者:JDK官方

    如何创建代理对象:
    使用Proxy类中的newProxyInstance方法

    创建代理对象的要求:
    被代理类最少实现一个接口,如果没有则不能使用

    newProxyInstance方法的参数:
    ClassLoader:类加载器

    它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。

    Class[]:字节码数组

    它是用于让代理对象和被代理对象有相同方法。固定写法。

    InvocationHandler:用于提供增强的代码

    它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
    * 此接口的实现类都是谁用谁写。

    package com.itheima.proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 模拟一个消费者
     */
    public class Client {
    
        public static void main(String[] args) {
            final Producer producer = new Producer();
    
    
           IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                    producer.getClass().getInterfaces(),
                    new InvocationHandler() {
                        /**
                         * 作用:执行被代理对象的任何接口方法都会经过该方法
                         * 方法参数的含义
                         * @param proxy   代理对象的引用
                         * @param method  当前执行的方法
                         * @param args    当前执行方法所需的参数
                         * @return        和被代理对象方法有相同的返回值
                         * @throws Throwable
                         */
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            //提供增强的代码
                            Object returnValue = null;
    
                            //1.获取方法执行的参数
                            Float money = (Float)args[0];
                            //2.判断当前方法是不是销售
                            if("saleProduct".equals(method.getName())) {
                                returnValue = method.invoke(producer, money*0.8f);
                            }
                            return returnValue;
                        }
                    });
            proxyProducer.saleProduct(10000f);
        }
    }
    

    Iproduce.java

    package com.itheima.proxy;
    
    /**
     * 对生产厂家要求的接口
     */
    public interface IProducer {
    
        /**
         * 销售
         * @param money
         */
        public void saleProduct(float money);
    
        /**
         * 售后
         * @param money
         */
        public void afterService(float money);
    }
    

    produce.java

    package com.itheima.proxy;
    
    /**
     * 一个生产者
     */
    public class Producer implements IProducer{
    
        /**
         * 销售
         * @param money
         */
        public void saleProduct(float money){
            System.out.println("销售产品,并拿到钱:"+money);
        }
    
        /**
         * 售后
         * @param money
         */
        public void afterService(float money){
            System.out.println("提供售后服务,并拿到钱:"+money);
        }
    }
    

    AOP 相关术语

    Joinpoint(连接点): 所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的 连接点。

    Pointcut(切入点): 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。

    Advice(通知/增强):

    所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。

    通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。

    Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方 法或 Field。

    Target(目标对象): 代理的目标对象。

    Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程。

    spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。

    Proxy(代理): 一个类被 AOP 织入增强后,就产生一个结果代理类。

    Aspect(切面): 是切入点和通知(引介)的结合。

  • 相关阅读:
    LeetCode:35. 搜索插入位置
    LeetCode:34. 在排序数组中查找元素的第一个和最后一个位置
    LeetCode:33. 搜索旋转排序数组
    LeetCode:31. 下一个排列
    LeetCode:23. 合并K个排序链表
    LeetCode:21. 合并两个有序链表
    JDE910笔记1--基础介绍及配置[转]
    JDE910笔记2--OMW项目建立及简单使用[转]
    活动目录—远程安装额外域控制器[转]
    java入门第五步之数据库项目实战【转】
  • 原文地址:https://www.cnblogs.com/Lilwhat/p/12590803.html
Copyright © 2011-2022 走看看