Spring学习笔记之Aop

时间:2021-10-06 10:01:54

一、Aop概述

1.Aop概念

面向切面编程,扩展功能通过不修改源代码实现;Aop采用横向抽取机制,取代了传统纵向继承体系重复性代码

2.Aop原理

原始修改代码的过程:

public class User {

    // 添加用户的用法
    public void add() {

        // 添加逻辑
        // 修改源代码,添加日志逻辑(功能扩展在此处添加)
    }
}

纵向机制的解决

public class BaseUser {

    // 创建日志添加的方法
    public void writelog() {

        // 添加日志逻辑
    }
}

public class User extends BaseUser{

    public void add() {

        // 添加逻辑
        // 功能扩展,添加日志的功能
        // 调用父类的方法实现日志功能
        super.writelog();
    }
}

问题:比如父类的方法名称发生变化,在子类调用的方法也需要变化。
Aop:横向抽取机制
底层使用动态代理方式实现
第一种情况(有接口的情况):

public interface Dao {

    public void add();
}

public class DaoImpl implements Dap {

    public void add() {

        // 添加逻辑
    }
}

使用动态代理方式,创建接口实现类代理对象(对象并不是new出来的,而是代理出来的)
创建和DaoImpl类平级对象,这个类不是真正的对象,而是代理对象,实现和DaoImpl相同的功能
使用jdk动态代理,针对有接口情况
第二种情况(无接口的情况):

public class User {

    public void add() {
    ...
    }
}

// 动态代理实现
// 创建User类的子类的代理对象
// 在子类里面调用父类的方法完成增强

使用cglib动态代理,针对没有接口的情况。

二、Aop相关术语

以下面代码为例:

public class User {

    public void add() {...}
    public void delete() {...}
    public void update() {...}
    public void query() {...}
}

1.Joinpoint(连接点)(重要)

类里面有哪些方法可以被增强,这些方法称为连接点,例如上面类中的四个方法都可以被增强,所有这四个方法均为连接点。

2.Pointcut(切入点)(重要)

在类里面可以有很多的方法被增强,比如在实际操作中,只是增强了类里面add方法和update方法,那么add和update这两个被增强的方法就称为切入点。

3.通知/增强(重要)

增强的逻辑称为增强,比如扩展日志功能,这个日志功能称为增强。通知/增强可分为前置通知、后置通知、异常通知、最终通知、环绕通知。
(1)前置通知:在方法之前执行。
(2)后置通知:在方法执行之后执行,无论是否发生异常。
(3)异常通知:在目标方法抛出异常后通知。
(4)返回通知:在目标方法返回结果后执行。
(5)环绕通知:在方法之前和之后执行。

4.切面

把增强应用到具体方法上面,过程称为切面,即把增强用到切入点的过程。

5.Introduction(引介)

引介可以使用动态的方式向类中增加属性和方法。

5.Target(目标对象)

增强方法所在的类称为目标对象。

6.Weaving(织入)

织入就是增强应用到增强方法所在类的过程。

7.Proxy(代理)

一个类被Aop织入增强后,就产生一个结果代理类。

三、Spring的Aop操作

1.在Spring里面进行Aop操作,使用aspectj实现

(1)aspectj不是Spring的一部分,和Spring一起使用进行Aop操作
(2)Spring2.0以后新增了对aspectj的支持

2.Aop操作的准备

(1)除了导入基本的jar包之外,还需要导入aop相关的jar包:

aopalliance-1.0.jar
aspectjweaver-1.8.7.jar
spring-aspects-5.0.4.RELEASE.jar
spring-aop-5.0.4.RELEASE.jar

附:
aopalliance-1.0.jar下载地址:
http://mvnrepository.com/artifact/aopalliance/aopalliance/1.0
这个包是AOP联盟的API包,里面包含了针对面向切面的接口。通常Spring等其它具备动态织入功能的框架依赖此包。
aspectjweaver-1.8.7.jar下载地址:
http://mvnrepository.com/artifact/org.aspectj/aspectjweaver/1.8.7
aspectjweaver这个包是spring的切入点表达式需要用的包。
(2)创建Spring核心配置文件
除了引入了约束spring-beans之外还需要引入新约束spring-aop

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-aop.xsd">

</beans>

3.使用表达式配置切入点

(1)切入点:实际增强的方法
(2)常用表达式
语法:
excute(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
被增强类:

package com.jxs.aop;

/** * Created by jiangxs on 2018/3/28. */
public class Book {

    public void print() {

        System.out.println("I'm a Normal Book!");
    }
}

增强类

package com.jxs.aop;

/** * Created by jiangxs on 2018/3/28. */
public class NBBook {

    public void before1() {

        System.out.println("前置增强");
    }
}

表达式写法:

(1)execute(* com.jxs.aop.Book.print(..)) <!-- .. 表示其中有参数也会包含-->
(2)excution(* com.jxs.aop.Book.*(..))<!--对类中的所有参数都做增强-->
(3)excution(* *.*(..))<!--对所有类中的所有方法都做增强-->
(4)excution(* add*(..))<!--匹配所有add开头的方法-->

aop操作示例代码1(前置增强):
Book.java和NBBook.java见上
bean3.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">

    <!-- 1.配置对象 -->
    <bean id="book" class="com.jxs.aop.Book"></bean>
    <bean id="nbBook" class="com.jxs.aop.NBBook"></bean>

    <!-- 2.配置aop操作 -->
    <aop:config>
        <!-- 2.1配置切入点 -->
        <aop:pointcut id="pointcut1" expression="execution(* com.jxs.aop.Book.*(..))"/>

        <!-- 2.2配置切面 -->
        <aop:aspect ref="nbBook">
            <!-- 配置增强类型 method:增强类里面使用哪个方法作为前置 pointcut-ref:增强的切入点的id -->
            <aop:before method="before1" pointcut-ref="pointcut1"/>
        </aop:aspect>
    </aop:config>
</beans>

测试代码:

package com.jxs.aop;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/** * Created by jiangxs on 2018/3/28. */
public class TestAop {

    @Test
    public void testBook() {

        ApplicationContext context =
                new ClassPathXmlApplicationContext("bean3.xml");
        Book book = (Book) context.getBean("book");
        book.print();
    }
}

测试结果:

log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

前置增强
I'm a Normal Book!

Process finished with exit code 0

aop操作示例代码2(后置增强+环绕通知):

package com.jxs.aop;

/** * Created by jiangxs on 2018/3/28. */
public class Book {

    public void print() {

        System.out.println("I'm a Normal Book!");
    }
}
package com.jxs.aop;

import org.aspectj.lang.ProceedingJoinPoint;

/** * Created by jiangxs on 2018/3/28. */
public class NBBook {

    public void before1() {

        System.out.println("前置增强");
    }

    public void after1() {

        System.out.println("后置增强");
    }

    // 环绕通知
    public void around1(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        // 方法之前
        System.out.println("方法之前");

        // 执行被增强的方法
        proceedingJoinPoint.proceed();

        // 方法之后
        System.out.println("方法之后");
    }
}
<?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">

    <!-- 1.配置对象 -->
    <bean id="book" class="com.jxs.aop.Book"></bean>
    <bean id="nbBook" class="com.jxs.aop.NBBook"></bean>

    <!-- 2.配置aop操作 -->
    <aop:config>
        <!-- 2.1配置切入点 -->
        <aop:pointcut id="pointcut1" expression="execution(* com.jxs.aop.Book.*(..))"/>

        <!-- 2.2配置切面 -->
        <aop:aspect ref="nbBook">
            <!-- 配置增强类型 method:增强类里面使用哪个方法作为前置 pointcut-ref:增强的切入点的id -->
            <aop:before method="before1" pointcut-ref="pointcut1"/>

            <!-- 后置 -->
            <aop:after method="after1" pointcut-ref="pointcut1"/>

            <!-- 环绕 -->
            <aop:around method="around1" pointcut-ref="pointcut1"/>
        </aop:aspect>
    </aop:config>
</beans>

测试代码:

package com.jxs.aop;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/** * Created by jiangxs on 2018/3/28. */
public class TestAop {

    @Test
    public void testBook() {

        ApplicationContext context =
                new ClassPathXmlApplicationContext("bean3.xml");
        Book book = (Book) context.getBean("book");
        book.print();
    }
}

测试结果:

log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

前置增强
方法之前
I'm a Normal Book!
方法之后
后置增强

Process finished with exit code 0

可以看出环绕通知的前后与前置增强和后置增强的前后关系。

四、基于aspectj的注解Aop操作

1.创建对象

2.在Spring核心配置文件中开启Aop操作

具体操作见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">

    <!-- 1.开启aop操作 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    <!-- 2.配置对象 -->
    <bean id="book" class="com.jxs.aspectj.Book"></bean>
    <bean id="nbBook" class="com.jxs.aspectj.NBBook"></bean>

</beans>

3.在增强类上面使用注解完成Aop操作

被增强类:

package com.jxs.aspectj;

/** * Created by jiangxs on 2018/3/29. */
public class Book {

    public void print() {

        System.out.println("I'm a book");
    }
}

增强类:

package com.jxs.aspectj;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/** * Created by jiangxs on 2018/3/29. */
// 在增强的方法所在类上加@Aspect注解
@Aspect
public class NBBook {

    // 在方法上面使用注解完成增强配置
    @Before(value = "execution(* com.jxs.aspectj.Book.*(..))")
    public void before1() {

        System.out.println("前置通知");
    }
}

测试方法:


/** * Created by jiangxs on 2018/3/29. */
public class TestBook {

    @Test
    public void testBook() {

        ApplicationContext context =
                new ClassPathXmlApplicationContext("bean1.xml");
        Book book = (Book) context.getBean("book");
        book.print();
    }
}

测试结果:

前置通知
I'm a book

Process finished with exit code 0