Spring核心思想之Aop

Aop的概述

1.什么是Aop的技术
  • 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程
  • AOP是一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构
  • AOP最早由AOP联盟的组织提出的,制定了一套规范.Spring将AOP思想引入到框架中,必须遵守AOP联盟的规范
  • 通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
  • AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型
  • 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
2. AOP:面向切面编程.(思想.---解决OOP遇到一些问题)
3. AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
4. 为什么要学习AOP
  • 可以在不修改源代码的前提下,对程序进行增强!!

Spring框架的AOP的底层实现

  1. Srping框架的AOP技术底层也是采用的代理技术,代理的方式提供了两种

    1. 1基于JDK的动态代理
      • 必须是面向接口的,只有实现了具体接口的类才能生成代理对象

    1.2.基于CGLIB动态代理
    * 对于没有实现了接口的类,也可以产生代理,产生这个类的子类的方式

  2. Spring的传统AOP中根据类是否实现接口,来采用不同的代理方式
    1. 如果实现类接口,使用JDK动态代理完成AOP
    2. 如果没有实现接口,采用CGLIB动态代理完成AOP

JDK的动态代理

原理: 针对内存中Class对象,使用类加载器 动态为目标对象实现接口的创建代理类
使用Proxy类来生成代理对象的一些代码如下:

            public static UserDao getProxy(final UserDao dao) {
                // 使用Proxy类生成代理对象
                UserDao proxy = (UserDao) Proxy.newProxyInstance(dao.getClass().getClassLoader(),
                        dao.getClass().getInterfaces(), new InvocationHandler() {
                            
                            // 代理对象方法一执行,invoke方法就会执行一次
                            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                if("save".equals(method.getName())){
                                    System.out.println("记录日志...");
                                    // 开启事务
                                }
                                // 提交事务
                                // 让dao类的save或者update方法正常的执行下去
                                return method.invoke(dao, args);
                            }
                        });
                // 返回代理对象
                return proxy;
            }
        }

CGLIB的代理技术:
CGlib采用非常底层字节码技术,可以为一个类创建子类,解决无接口代理问题

  1. 引入CBLIB的开发包
    * 如果想使用CGLIB的技术来生成代理对象,那么需要引入CGLIB的开发的jar包,在Spring框架核心包中已经引入了CGLIB的开发包了。所以直接引入Spring核心开发包即可!
    编写相关的代码:
public static OrderDaoImpl getProxy(){
            // 创建CGLIB核心的类
            Enhancer enhancer = new Enhancer();
            // 设置父类
            enhancer.setSuperclass(OrderDaoImpl.class);
            // 设置回调函数
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object obj, Method method, Object[] args,
                        MethodProxy methodProxy) throws Throwable {
                    if("save".equals(method.getName())){
                        // 记录日志
                        System.out.println("记录日志了...");
                    }
                    return methodProxy.invokeSuper(obj, args);
                }
            });
            // 生成代理对象
            OrderDaoImpl proxy = (OrderDaoImpl) enhancer.create();
            return proxy;
        }
    ```

结论:
1).若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
2).若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类,程序中应优先对接口创建代理,便于程序解耦维护

Spring基于AspectJ的AOP的开发

技术分析之AOP的相关术语
  1. Joinpoint(连接点) 所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
  2. Pointcut(切入点) 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
  3. Advice(通知/增强) 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
  4. Introduction(引介) 引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field
  5. Target(目标对象) 代理的目标对象
  6. Weaving(织入) 是指把增强应用到目标对象来创建新的代理对象的过程 生成动态代理的过程
  7. Proxy(代理) 一个类被AOP织入增强后,就产生一个结果代理类
  8. Aspect(切面) 是切入点和通知的结合,以后咱们自己来编写和配置的

技术分析之AspectJ的XML方式完成AOP的开发

  1. 步骤一:创建JavaWEB项目,引入具体的开发的jar包
    先引入Spring框架开发的基本开发包
    再引入Spring框架的AOP的开发包
    spring的传统AOP的开发的包
    spring-aop-4.2.4.RELEASE.jar
    com.springsource.org.aopalliance-1.0.0.jar//aop联盟定义了规范
    aspectJ的开发包
    com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar//aspectJ jar包
    spring-aspects-4.2.4.RELEASE.jar//spring整合aspectJ的jar包
  2. 步骤二:创建Spring的配置文件,引入具体的AOP的schema约束
        <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.xsd
                http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
  1. 步骤三:创建包结构,编写具体的接口和实现类
    * com.demo2
    * CustomerDao -- 接口
    * CustomerDaoImpl -- 实现类
  2. 步骤四:将目标类配置到Spring中
<bean id="customerDao" class="com.demo3.CustomerDaoImpl"/>
  1. 步骤五:定义切面类
        public class MyAspectXml {
            // 定义通知
            public void log(){
                System.out.println("记录日志...");
            }
        }
  1. 步骤六:在配置文件中定义切面类
        <bean id="myAspectXml" class="com.demo3.MyAspectXml"/>
  1. 步骤七:在配置文件中完成aop的配置
        <aop:config>
            <!-- 引入切面类 -->
            <aop:aspect ref="myAspectXml">
                <!-- 定义通知类型:切面类的方法和切入点的表达式 -->
                <aop:before method="log" pointcut="execution(public * com.itheima.demo3.CustomerDaoImpl.save(..))"/>
            </aop:aspect>
        </aop:config>
  1. 完成测试
        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration("classpath:applicationContext.xml")
        public class Demo3 {
            @Resource(name="customerDao")
            private CustomerDao customerDao;
            @Test
            public void run1(){
                customerDao.save();
                customerDao.update();
                customerDao.delete();
            }
        }

切入点的表达式

  1. 再配置切入点的时候,需要定义表达式,重点的格式如下:execution(public * *(..)),具体展开如下:
    切入点表达式的格式如下:
    execution([修饰符] 返回值类型 包名.类名.方法名(参数))

修饰符可以省略不写,不是必须要出现的。
返回值类型是不能省略不写的,根据你的方法来编写返回值。可以使用 * 代替。
包名例如:com..demo3.BookDaoImpl
首先com是不能省略不写的,但是可以使用 * 代替
中间的包名可以使用 * 号代替
如果想省略中间的包名可以使用 ..
类名也可以使用 * 号代替,也有类似的写法:*DaoImpl
方法也可以使用 * 号代替
参数如果是一个参数可以使用 * 号代替,如果想代表任意参数使用 ..

AOP的通知类型

  1. 前置通知
    在目标类的方法执行之前执行。
    配置文件信息:<aop:after method="before" pointcut-ref="myPointcut3"/>
    应用:可以对方法的参数来做校验
    1. 最终通知
      在目标类的方法执行之后执行,如果程序出现了异常,最终通知也会执行。
      在配置文件中编写具体的配置:<aop:after method="after" pointcut-ref="myPointcut3"/>
      应用:例如像释放资源

    2. 后置通知
      方法正常执行后的通知。
      在配置文件中编写具体的配置:<aop:after-returning method="afterReturning" pointcut-ref="myPointcut2"/>
      应用:可以修改方法的返回值

    3. 异常抛出通知
      在抛出异常后通知
      在配置文件中编写具体的配置:<aop:after-throwing method="afterThorwing" pointcut-ref="myPointcut3"/>
      应用:包装异常的信息

    4. 环绕通知
      方法的执行前后执行。
      在配置文件中编写具体的配置:<aop:around method="around" pointcut-ref="myPointcut2"/>
      要注意:目标的方法默认不执行,需要使用ProceedingJoinPoint对来让目标对象的方法执行。
      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.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->
    
  <bean id="customerDao" class="demo02.CustomerDaoImpl"></bean>
  <!-- 配置切面类 -->
  <bean id="myaspectXml" class="demo02.MyaspectXml"></bean>
  <!-- 配置aop -->
  <aop:config>
  <!-- 配置切面类:切入点+通知(类型) -->
  <aop:aspect ref="myaspectXml">
  <!-- 配置前置通知:save在执行之前,增强的方法会执行 -->
  <!-- 切入点表达式execution(public void demo02.CustomerDaoImpl.save() ) -->
  <aop:before method="log" pointcut="execution(public void demo02.CustomerDaoImpl.save() )"/>
  </aop:aspect>
  
  </aop:config>
    
</beans>

接口类代码:

package demo02;

public interface CustomerDao {
    public void save();
    public void update();

}

实现类代码:

package demo02;

public class CustomerDaoImpl implements CustomerDao {

    @Override
    public void save() {
        System.out.println("保存客户信息");

    }

    @Override
    public void update() {
        System.out.println("修改客户信息");

    }

}

切面类代码:

package demo02;

import org.aspectj.lang.ProceedingJoinPoint;

/*
 * 切点类:切入点+通知
 */

public class MyaspectXml {
    /*
     * 通知强
     */
    //前置通知
    public void log() {
        System.out.println("记录日志");

    }
    //后置通知
    public void after() {
        System.out.println("后置通知");
    }
    //环绕通知
    public void around(ProceedingJoinPoint joinPoint) {
        System.out.println("环绕通知1");
        try {
            //手动让目标方法执行
            joinPoint.proceed();
        } catch (Throwable e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("环绕通知2");
    }
    //异常抛出通知
    public void afterthrowing() {
        System.out.println("异常抛出通知");
    }
    //最终通知
    public void afterreturning() {
        System.out.println("最终通知");
    }

}

测试代码:

package demo02;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/*
 * 测试aop功能
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class Main {

    @Resource(name="customerDao")
    private CustomerDao customerDao;

    @Test
    public void run() {
        customerDao.save();
        customerDao.update();

    }

}

Spring框架的Aop技术(注解方式)

  1. 步骤一:创建JavaWEB项目,引入具体的开发的jar包
    先引入Spring框架开发的基本开发包
    再引入Spring框架的AOP的开发包
    spring的传统AOP的开发的包
    spring-aop-4.2.4.RELEASE.jar
    com.springsource.org.aopalliance-1.0.0.jar
    aspectJ的开发包
    com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
    spring-aspects-4.2.4.RELEASE.jar
  2. 步骤二:创建Spring的配置文件,引入具体的AOP的schema约束
<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.xsd
                http://www.springframework.org/schema/aop 
                http://www.springframework.org/schema/aop/spring-aop.xsd">
        </beans>
  1. 步骤三:创建包结构,编写具体的接口和实现类
    com.demo1
    CustomerDao -- 接口
    CustomerDaoImpl -- 实现类

  2. 步骤四:将目标类配置到Spring中

     <bean id="customerDao" class="com.CustomerDaoImpl"/
    
  3. 步骤五:定义切面类
    具体的代码如下

@Aspect
public class MyAspectAnno {
@Before(value="execution(public void com.demo1.CustomerDaoImpl.save())")
public void log(){
    System.out.println("记录日志...");
                }
            }
  1. 步骤六:在配置文件中定义切面类
        <bean id="myAspectAnno" class="com.demo1.MyAspectAnno"/>
  1. 步骤七:在配置文件中开启自动代理
        <aop:aspectj-autoproxy/>
  1. 完成测试
        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration("classpath:applicationContext.xml")
        public class Demo1 {
            
            @Resource(name="customerDao")
            private CustomerDao customerDao;
            
            @Test
            public void run1(){
                customerDao.save();
                customerDao.update();
            }
        }

需要增强的类代码如下:

package demo02;

public class CustomerDaoImpl implements CustomerDao {

    @Override
    public void save() {
        System.out.println("保存客户信息");

    }

    @Override
    public void update() {
        System.out.println("修改客户信息");

    }

}

切面类代码如下:

package demo02;

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;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.weaver.JoinPointSignatureIterator;
/*
 * 注解方式的aop
 */

@Aspect
public class MyaspectAnno {
    @Before(value="execution(public void demo02.CustomerDaoImpl.save())")
    public void log() {
        System.out.println("记录日志");
        
    }
    @After(value="execution(public void demo02.CustomerDaoImpl.save())")
    public void after() {
        System.out.println("最终通知");
    }
    @Around(value="MyaspectAnno.fn()")
    public void around(ProceedingJoinPoint joinPoint)  {
        System.out.println("环绕一");
        try {
            joinPoint.proceed();
        } catch (Throwable e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("环绕二");
    }
    //自定义切入点
    @Pointcut(value="execution(public void demo02.CustomerDaoImpl.save())")
    public void fn() {
        
    }

}

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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/tx 
    http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!-- 我们需要增强的类 -->
    <bean id="customerDao" class="demo02.CustomerDaoImpl"></bean>
    <!-- 配置切面类 -->
    <bean id="myaspectAnno" class="demo02.MyaspectAnno"></bean>
    <!-- 开启aop注解扫描 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容