Spring中的AOP

 一、AOP简介

1.什么是AOP?

(1)AOP的全称是Aspect-Oriented Programming,即面向切面编程,是OOP思想的一种补充,目前已经是一种比较成熟的编程方式。传统的OOP可以通过组合或者继承的方式来达到代码的重用,但是如果要实现某个功能,同样的代码会分散到各个方法中去,这样想关闭某个功能,或者想对其进行修改,就必须修改所有的相关的方法。

(2)AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或者运行的时候,再将这些提取出来的代码应用到需要执行的地方。

目前最流行的AOP框架有两个,分别是Spring AOP和AspectJ。Spring AOP使用纯java实现,不需要专门的编译过程和类加载器,在运行期间通过代理的方式向目标织入增强代码,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译的时候提供了横向代码的织入。

2.AOP术语

(1)Aspect(切面):在实际应用过程中,切面是指封装的用于横向插入下同功能(如事务、日志等)的类,如上图中的Aspect,该类要被Spring容器识别为切面,需要在配置文件中通过配置文件进行指定。

(2)Jointpount(连接点):

 在程序的执行过程中的某个点,实际上是对象的一个操作,例如方法的调用或者异常的抛出,而在Spring AOP中,连接点就是指方法的调用。

(3)PointCut(切入点):即切面与程序流程的交叉点,即那些需要处理的连接点,如下图所示,切入点是指类或者方法名,如某个通知需要用到以add开头的方法,那么所有满足这一规则的都是切入点。

(4)Advice(通知/增强处理):AOP在特定的切入点执行增强处理,即定义好的切入点处所要执行的代码,可以将其理解为切面类中的方法,它是切面的具体实现。

(5)Target Object(目标对象):指所有被通知的对象,也叫做被增强的对象。如果AOP框架采用的是动态AOP实现,那么该对象就是一个被代理的对象。

(6)Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。

(7)Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。

 二、动态代理

0.AOP中的代理是由AOP框架动态生成的一个对象,该对象可以作为目标对象使用,Spring中AOP代理可以是JDK动态代理,也可以是CGLIB代理。

1.JDK动态代理。

(1)JDK动态代理是通过java.lang.reflect.Proxy类来实现的,可以通过Proxy类的neProxyInstance()方法创建代理对象,对于使用业务接口的类,Spring会默认使用JDK动态代理实现AOP。

(2)动态代理的实现代码:

//UserDao类
public interface UserDao {
    /**
     *
     */
    public void addUser();
    public void deleteUser();
}


//UserDao实现类UserDaoImpl
//
/** * 目标类,本案例中会将类UserDaoImpl做为一个目标类,对其方法进行增强处理。 */ public class UserDaoImpl implements UserDao { public void addUser() { System.out.println("添加用户"); } public void deleteUser() { System.out.println("删除用户"); } }


/**
 * 切面类,可以存在多个通知Advice(即增强的方法)
  在该类中模拟一个权限检查方法和模拟一个日志记录的方法,这两个方法就表示切面中的通知
*/ public class MyAscpect { //下面这两个方法表示切面中的通知 public void check_Permissions(){ System.out.println("模拟检查权限。。。。。"); } public void log(){ System.out.println("模拟记录日志"); } }import com.cisco.aspect.MyAscpect;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 代理类
 * 该类需要实现InvocationHandler接口,并编写代理方法,并实现了接口中的invoke()方法,所有动态代理所调用的方法都交由该方法处理,
 * 在创建代理方法createProxy()中,使用Proxy类的newProxyInstance()方法来创建代理对象。
  newProxyInstance()方法包含3个参数,其中第一个参数表示当前类的加载器,第二个参数表示被代理对象实现的所有接口,
  第三个参数this表示代理类jdkProxy本身,在invoke()方法中,目标方法执行的前后,
会分别执行切面类的check_Permission()方法和log()方法
Proxy.newProxyInstance(classLoader,clazz,this);
*/
public class JdkProxy implements InvocationHandler {
    //1.声明目标类接口
    private UserDao userDao;
    //2.创建代理方法
    public Object createProxy(UserDao userDao){
        this.userDao=userDao;
        //3.类加载器
        ClassLoader classLoader=JdkProxy.class.getClassLoader();
        //4.被代理对象实现的所有接口
        Class[] clazz = userDao.getClass().getInterfaces();
        //5.使用代理类进行增强,返回的是代理后的对象
        return Proxy.newProxyInstance(classLoader,clazz,this);
    }

    /**
     * 所有动态代理类的方法的调用,都会交由Invoke()方法进行处理,
     * @param proxy 被代理后的对象
     * @param method 将要被执行的方法信息(反射)
     * @param args 执行方法时候所需要的参数
     * @return
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //6声明切面
        MyAscpect myAscpect = new MyAscpect();
        //7.前增强
        myAscpect.check_Permissions();
        //8.在目标类上调用方法,并传入参数
        Object obj=method.invoke(userDao,args);
        //9.后增强
        myAscpect.log();
        return obj;
    }
}

//最后是测试类

package com.cisco.jdk;

public class JdkTest {
    public static void main(String[] args){
        //1.创建代理对象
        JdkProxy jdkProxy = new JdkProxy();
        //2.创建目标对象
        UserDao userDao = new UserDaoImpl();
        //3.从代理对象中获取增强后的目标对象
        UserDao userDao1 = (UserDao) jdkProxy.createProxy(userDao);
        //4.执行方法
        userDao1.addUser();
        userDao1.deleteUser();
    }
}

 三、基于代理类的AOP实现

1.Spring的通知类型,Spring中的通知按照在目标中类方法的连接点位置,可以分为以下5种类型

(1)环绕通知(org.aopalliance.intercept.MethodInterceptor):在目标方法执行前后实施增强,可以用于日志和事务管理等功能。

(2)前置通知(org.springframework.aop.MethodBeforeAdvice):在目标方法执行前实施增强,可以用于权限管理等功能。

(3)后置通知(org.springframework.aop.AfterAdvice):在目标方法执行之后实施增强,可用于关闭流、上传文件、删除临时文件等功能。

(4)异常通知(org.springframework.aop.ThrosAdvice):在方法抛出异常后实施增强,可以用以处理异常激烈日志能等功能。

(5)引介通知(org.springframework.aop.IntroductionInterceptor):在目标类中添加一些新的方法和属性,可用于修改老版本程序。(增强类)

2.ProxyFactoryBean

(1)ProxyFactoryBean是FactoryBean接口的实现类,FactoryBean负责实例化一个Bean,而ProxyFactoryBean负责为其他Bean创建代理实例。在Spring中,使用ProxyFactoryBean时创建AOP代理的基本方式。

(2)ProxyFactoryBean的常用可配置属性:

target:代理的目标对象;ProxyInterface:代理实现的接口,如果是多个接口,可使用下面的各式<list><value></value></list>

(3)ProxyFactoryBean典型的环绕通知案例:

首先创建切面类MyAspect类,由于实现环绕通知需要实现MethodInterceptor接口,所以MyAspect类需要试下该接口,并试下接口中的invoke方法

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
 * 环绕通知需要实现MethodInterceptor接口
 * 这里为了演示效果,在目标方法前后分别执行了检查权限和记录日志的方法,这两个方法也就是增强的方法,也就是通知。
 */
public class MyAspect implements MethodInterceptor {

    public Object invoke(MethodInvocation mi) throws Throwable {
        check_Permissons();
        //执行目标方法
        Object obj = mi.proceed();
        log();
        return obj;
    }
    public void check_Permissons(){
        System.out.println("模拟检查权限");
    }
    public void log(){
        System.out.println("模拟日志记录权限");
    }


}

其次为了演示效果,在目标方法前后分别执行了检查权限和记录日子和方法,这两个方法也就是增强的方法,也就是通知。创建配置文件applicationContext.xml,并指定代理对象。首先通过<bean>元素定义了目标类和切面,然后使用ProxyFactoryBean定义了代理的目标对象,在定义的代理对象中,分别通过<property>子元素指定了代理的实现接口,代理的目标对象、需要织入的目标类的通知以及代理的方式。

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--1.目标类-->
    <bean id="userDao" class="com.cisco.jdk.UserDaoImpl"></bean>

    <!--2.切面类-->
    <bean id="myAspect" class="com.cisco.factoryBean.MyAspect"></bean>

    <!--3.在Spring代理工厂定义一个名称为userDaoProxy的代理对象-->
    <bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--3.1指定代理实现的接口-->
        <property name="proxyInterfaces" value="com.cisco.jdk.UserDao"/>
        <!--3.2指定目标对象-->
        <property name="target" ref="userDao"/>
        <!--3.3指定切面,植入环绕通知-->
        <property name="interceptorNames" value="myAspect"/>
        <!--3.4指定代理方式,true:使用cglib,false:默认使用jdk动态代理-->
        <property name="proxyTargetClass" value="true"/>
    </bean>
</beans>

 四、AspectJ开发

1.AspectJ是一个基于Java语言的AOP框架,Spring AOP引入了对ASpectJ的支持,并允许使用ASpectJ进行编程。AspectJ试下AOP有两种方式:一种是基于XML的声明式AspectJ,另一种是基于注解的AspectJ。

2.基于XML的声明式AspectJ。

就XML的声明式AspectJ是指通过XML文件来定义切面、切入点以及通知。所有的切面、切入点和通知都必须定义在<aop:config>元素内,<aop:config>元素以及子元素如下图所示:

对于Spring配置文件中的<beans>元素下可以包含多个<aop:config>元素,一个<aop:config>元素又可以包含属性和子元素,其子元素包括<aop:pointcut>  <aop:advisor>和<aop:aspect>。在配置的时候,这3个元素必须按照这个顺序来进行定义,在<aop:aspect>元素下,同样包含了属性和多个子元素,通过使用<aop:aspect>元素及其子元素就可以在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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

        <!--定义切面Bean-->
        <bean id="myAspect" class="com.cisco.aspect.MyAscpect" />
        <aop:config>
            <!--配置切面-->
            <aop:aspect id="aspect" ref="myAspect">
                <!--配置切入点-->
                <aop:pointcut id="myPointCut" expression="execution(* com.cisco.jdk.*.*(..))"/>
                <!--配置通知-->
                <!--前置通知-->
                <aop:before method="myBefore" pointcut-ref="myPointCut"/>

                <!--后置通知-->
                <aop:after-returning method="myAfterRunning" pointcut-ref="myPointCut" returning="returnVal"/>

                <!--环绕通知-->
                <aop:around method="myAround" pointcut-ref="myPointCut"/>

                <!--异常通知-->
                <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
                
                <!--最终通知-->
                <aop:after method="myAfter" pointcut-ref="myPointCut"/>
            </aop:aspect>
        </aop:config>
</beans>

 上述配置的讲解:在Spring的配置文件中,配置切面使用的是<aop:aspect>,这个元素会将已经定义好的Spring Bean转换为切面Bean,所以在配置文件中要先顶一个普通的Spring Bean,如上述代码中的myAspect,定义完成后,通过<aop:aspect>元素的ref属性即可引用该Bean。在配置<aop:aspect>元素的时候,通常会指定id和ref属性;配置切入点:切入点是同构<aop:pointcut>元素来定义的,当

======================================================

未完待续~

原文地址:https://www.cnblogs.com/bigdata-stone/p/10909873.html