AOP概念和原理

一、什么是AOP?

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用"横切"技术,AOP把软件系统分为两个部分:核心关注点横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

二、AOP核心概念

2.1.连接点 Joint point:

类里面那些可以被增强的方法,这些方法称之为连接点,表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point

2.2.切入点 Pointcut:

实际被增强的方法,称之为切入点,表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方

2.3.通知 Advice:

实际增强的逻辑部分称为通知 (增加的功能),Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
通知类型:

  • 前置通知
  • 后置通知
  • 环绕通知
  • 异常通知
  • 最终通知

2.4.目标对象 Target:被增强功能的对象(被代理的对象)

织入 Advice 的目标对象

2.5 切面Aspect: 表现为功能相关的一些advice方法放在一起声明成的一个Java类

Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。

2.6 织入 Weaving:

创建代理对象并实现功能增强的声明并运行过程,将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

三、Spring对AOP的支持

Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:

  • 默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
  • 当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

AOP编程其实是很简单的事情,纵观AOP编程,只需要参与三个部分:

  • 定义普通业务组件
  • 定义切入点,一个切入点可能横切多个业务组件
  • 定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作

所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。

四、spring中对于AOP的实现方法

AspectJ本身并不是spring框架中的组成部分, 是一个独立的AOP框架,一般把AspectJ和Spring框架的AOP依赖一起使用,所以要导入一个独立的依赖

4.1.方式一:通过注解的方式实现

4.1.1.导入依赖

创建Maven项目,导入依赖如下:

<?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.augus</groupId>
    <artifactId>spring5_ioc01</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>


        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.22</version>
        </dependency>

        <!--spring切面 包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.22</version>
        </dependency>

        <!--织入包  spring-aspects 已经导入该包,这里可以不导入,有依赖导入-->
        <!--<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>-->

        <!--aop联盟包-->
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>

        <!--Apache Commons日志包-->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>6</source>
                    <target>6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

4.1.2.切入表达式语法说明

切入点表达式: 通过一个表达式来确定AOP要增强的是哪个或者那些方法,语法结构:

execution([权限修饰符][返回值类型][类的全路径名][方法名](参数 列表) )

说明如下:

  • execution(* com.augus.dao.UserDaoImpl.add(..)) //指定切点为UserDaoImpl.add方法
  • execution(* com.augus.dao.UserDaoImpl.*(..)) //指定切点为UserDaoImpl.所有的方法
  • execution(* com.augus.dao.*.*(..)) //指定切点为dao包下所有的类中的所有的方法
  • execution(* com.augus.dao.*.add(..)) // 指定切点为dao包下所有的类中的add的方法
  • execution(* com.augus.dao.*.add*(..)) // 指定切点为dao包下所有的类中的add开头的方法

4.1.3.基于注解方式实现项目结构

4.1.4.准备接口UserDao和EmpDao

package com.augus.dao;

public interface UserDao {
    int addUser(Integer userid,String username,String password);
}
package com.augus.dao;

public interface EmpDao {
    int addEmp(Integer empno,String ename,String job);
}

4.1.5.接口实现类

package com.augus.dao.impl;

import com.augus.dao.EmpDao;
import org.springframework.stereotype.Repository;

@Repository
public class EmpDaoImpl implements EmpDao {
    @Override
    public int addEmp(Integer empno, String ename, String job) {
        System.out.println("*************这是EmpDaoImpl************");
        return 1;
    }
}
package com.augus.dao.impl;
import com.augus.dao.UserDao;
import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImpl implements UserDao {
    @Override
    public int addUser(Integer userid, String username, String password) {
        System.out.println("*************这是UserDaoImpl************");
        return 1;
    }
}

4.1.6.准备切面

  • Join Point对象

Join Point对象封装了Spring A op中切面方法的信息,在切面方法中添加Join Point参数,就可以获取到封装了该方法信息的Join Point对象。常用api如下:

方法名 功能
Signature getSignature(); 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
Object[] getArgs(); 获取传入目标方法的参数对象
Object getTarget(); 获取被代理的对象
Object getThis(); 获取代理对象
  • Proceeding Join Point对象

Proceeding Join Point对象是Join Point的子接口,该对象只用在@Around的切面方法中,添加了两个方法.

  • Object proceed ()  throws Throw able   //执行目标方法
  • Object proceed(Object[] var1)throws Throw able  //传入的新的参数去执行目标方法

需要注意的是

  • 有多个增强类对同一个方法进行增强,通过@Order注解设置增强类优先级
  • 数字越小,优先级越高
  • 数字越小,其代理位置越靠近注入位置
package com.augus.asprct;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class DaoAspect {
    //定义公共的切点,匹配com.augus.dao这个包下所有add开头的方法进行增强
    @Pointcut("execution(* com.augus.dao.*.add*(..))")
    public void addPointCut(){} //这个方法就像是给上面的这个切点起了一个别名一样,切点的名字就是方法名*/

    /*前置通过:切点方法执行之前先执行的功能
    参数列表可以用JoinPoint接收切点对象
    可以获取方法值的参数*/
    @Before("addPointCut()")
    public void methodBefore(JoinPoint joinPoint){
        System.out.println("==============这是Before前置通知==============");
    }

    /*
    后置通知:方法执行之后要增强的功能
    无论切点方法是否出现异常都会执行的方法
    参数列表可以用于JoinPoint接受切点对象*/
    @After("addPointCut()")
    public void methodAfter(){
        System.out.println("==============这是After后置通知==============");
    }

    /*
    返回通知:切点方法正常运行结束后增强的功能
    如果方法运行过程中出现异常,则该功能不允许
    参数列表可以用于JoinPoint接受切点对象
    可以用Object res 接受方法返回值 需要用returning指定返回值名称
    */
    @AfterReturning(value = "addPointCut()",returning = "res")
    public void methodAfterReturning(JoinPoint joinPoint,Object res){
        System.out.println("==============这是AfterReturning返回通知==============");
    }

    /*
    异常通知:切点方法出现异常时运行的增强功能
    如果方法没有出现异常,该功能不运行
    参数列表可以用Exception ex 接收异常对象 需要通过throwing指定异常的名称
    */
    @AfterThrowing(value = "addPointCut()",throwing = "ex")
    public void methodAfterThrowing(Exception ex){
        System.out.println("==============这是AfterThrowing异常通知==============");
    }

    /*
     * 环绕通知:在切点方法之前和之后都进行功能增加
     * 需要在通知中定义方法执行的位置,并在执行位置之前和之后自定义增强的功能
     * 方法列表可以通过ProceedingJoinPoint获取切点
     * 通过proceedingJoinPoint.proceed()方法控制切点方法的执行位置
     * proceedingJoinPoint.proceed()方法会将切点方法的返回值获取到,并交给我们,可以做后续处理
     * 我们在环绕通知的最后需要将切点方法的返回值继续向上返回,否则切点方法在执行时接收不到返回值*/
    @Around("addPointCut()")
    public Object methodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("==============这是Around前置==============");
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("==============这是Around后置==============");
        return proceed;
    }
}

4.1.7.准备配置文件

创建spring配置文件,名为applicationContext.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"
       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
">

    <!--开启spring包扫描-->
    <context:component-scan base-package="com.augus"/>
    <!--自动生成aop代理对象-->
    <aop:aspectj-autoproxy/>
</beans>

4.1.8.测试文件

import com.augus.dao.EmpDao;
import com.augus.dao.UserDao;
import com.augus.dao.impl.UserDaoImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test1 {
    @Test
    public void testJoinPoint(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        /*UserDao userDao = context.getBean(UserDao.class);
        int i = userDao.addUser(12, "zhangsanfeng", "wudang001");*/

        EmpDao empDao = context.getBean(EmpDao.class);
        int i = empDao.addEmp(20, "张雯", "测试");
        System.out.println(i);
    }
}

执行后结果如下:

4.1.9.完全使用注解开发

需要注意的是,上面虽然大量应用了注解,但是包扫描已经开启代理模式依然需要使用applicationContext.xml配置文件完成,我们也可以完成采用注解的方式开发,这样就直接不需要xml配置文件了,内容如下

准备配置类
package com.augus.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(basePackages = "com.augus") //开启包扫描
@EnableAspectJAutoProxy(proxyTargetClass = true) //开启代理模式
public class SpringConfig {
}
测试文件
import com.augus.config.SpringConfig;
import com.augus.dao.EmpDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test2 {
    @Test
    public void testJoinPoint(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        /*UserDao userDao = context.getBean(UserDao.class);
        int i = userDao.addUser(12, "zhangsanfeng", "wudang001");*/

        EmpDao empDao = context.getBean(EmpDao.class);
        int i = empDao.addEmp(20, "张雯", "测试");
        System.out.println(i);
    }
}

4.2.方法二:通过XML方式实现

通过xml方式实现的时候,还是依赖之前的的几个类来完成,只不过 DaoAspect1 需要重新准备,不要在写注解,注解实现的功能有xml配置文件完成 

4.2.1.DaoAspect1 切面准备

package com.augus.asprct;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;


public class DaoAspect1 {


    /*
    * 前置通过:切点方法执行之前先执行的功能
    * 参数列表可以用JoinPoint接收切点对象
    * 可以获取方法值的参数*/
    public void methodBefore(JoinPoint joinPoint){
        System.out.println("***********这是Before前置通知*********");
    }

    /*
    * 后置通知:方法执行之后要增强的功能
    * 无论切点方法是否出现异常都会执行的方法
    * 参数列表可以用于JoinPoint接受切点对象*/
    public void methodAfter(){
        System.out.println("***********这是After后置通知*********");
    }

    /*
    * 返回通知:切点方法正常运行结束后增强的功能
    * 如果方法运行过程中出现异常,则该功能不允许
    * 参数列表可以用于JoinPoint接受切点对象
    * 可以用Object res 接受方法返回值 需要用returning指定返回值名称
    * */
    public void methodAfterReturning(JoinPoint joinPoint, Object res){
        System.out.println("***********这是AfterReturning返回通知*********");
    }

    /*
    * 异常通知:切点方法出现异常时运行的增强功能
    * 如果方法没有出现异常,该功能不运行
    * 参数列表可以用Exception ex 接收异常对象 需要通过throwing指定异常的名称
    * */
    public void methodAfterThrowing(Exception ex){
        System.out.println("***********这是AfterThrowing异常通知*********");
    }

    /*
    * 环绕通知:在切点方法之前和之后都进行功能增加
    * 需要在通知中定义方法执行的位置,并在执行位置之前和之后自定义增强的功能
    * 方法列表可以通过ProceedingJoinPoint获取切点
    * 通过proceedingJoinPoint.proceed()方法控制切点方法的执行位置
    * proceedingJoinPoint.proceed()方法会将切点方法的返回值获取到,并交给我们,可以做后续处理
    * 我们在环绕通知的最后需要将切点方法的返回值继续向上返回,否则切点方法在执行时接收不到返回值*/
    public Object methodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        System.out.println("************这是Around前置**************");
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("************这是Around后置**************");
        return proceed;
    }
}

4.2.2.配置文件

<?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"
       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
">

    <!--开启spring 包扫描-->
    <context:component-scan base-package="com.augus"></context:component-scan>
    <!--自动生成代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    <!--创建对象-->
    <bean id="userDao" class="com.augus.dao.impl.UserDaoImpl"></bean>
    <bean id="empDao" class="com.augus.dao.impl.EmpDaoImpl"></bean>
    <bean id="daoAspect" class="com.augus.asprct.DaoAspect1"></bean>

    <!--在spring配置文件中配置切点-->
    <aop:config>
        <!--切入点-->
        <aop:pointcut id="pointcutAdd" expression="execution(* com.augus.dao.*.add*(..)))"/>
        <!--配置切面-->
        <aop:aspect ref="daoAspect">
            <!--具体使用那种增强-->
            <aop:before method="methodBefore" pointcut-ref="pointcutAdd"></aop:before>
            <aop:after method="methodAfter" pointcut-ref="pointcutAdd"></aop:after>
            <aop:around method="methodAround" pointcut-ref="pointcutAdd"></aop:around>
            <aop:after-returning method="methodAfterReturning" pointcut-ref="pointcutAdd" returning="res"></aop:after-returning>
            <aop:after-throwing method="methodAfterThrowing" pointcut-ref="pointcutAdd" throwing="ex"></aop:after-throwing>
        </aop:aspect>
    </aop:config>


</beans>

4.2.3.测试代码

注意:测试之前需要取消掉接口实现类上面的@Repository注解

 

import com.augus.dao.EmpDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test3 {
    @Test
    public void testJoinPoint(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        /*UserDao userDao = context.getBean(UserDao.class);
        int i = userDao.addUser(12, "zhangsanfeng", "wudang001");*/

        EmpDao empDao = context.getBean(EmpDao.class);
        int i = empDao.addEmp(20, "张雯", "测试");
        System.out.println(i);
    }
}
posted @ 2019-10-22 21:04  酒剑仙*  阅读(2224)  评论(0编辑  收藏  举报