欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

spring之旅第五篇-AOP详解

程序员文章站 2022-05-29 12:57:30
一、什么是AOP? Aspect oritention programming(面向切面编程),AOP是一种思想,高度概括的话是“横向重复,纵向抽取”,如何理解呢?举个例子:访问页面时需要权限认证,如果每个页面都去实现方法显然是不合适的,这个时候我们就可以利用切面编程。 每个页面都去实现这个方法就是 ......

一、什么是aop?

aspect oritention programming(面向切面编程),aop是一种思想,高度概括的话是“横向重复,纵向抽取”,如何理解呢?举个例子:访问页面时需要权限认证,如果每个页面都去实现方法显然是不合适的,这个时候我们就可以利用切面编程。

每个页面都去实现这个方法就是横向的重复,我们直接从中切入,封装一个与主业务无关的权限验证的公共方法,这样可以减少系统的重复代码,降低模块之间的耦合度,简单的示意图如下:

spring之旅第五篇-AOP详解

二、应用场景

aop用来封装横切关注点,具体可以在下面的场景中使用:

authentication 权限

caching 缓存

context passing 内容传递

error handling 错误处理

lazy loading 懒加载

debugging  调试 l

ogging, tracing, profiling and monitoring 记录跟踪 优化 校准

performance optimization 性能优化

persistence  持久化

resource pooling 资源池

synchronization 同步 t

ransactions 事务

三、相关概念

1.连接点(joinpoint) 所谓连接点是指那些可能被拦截到的方法。例如:所有可以增加的方法

2.切点(pointcut) 已经被增强的连接点

3.增强(advice) 增强的代码

4.目标对象(target) 目标类,需要被代理的类

5.织入(weaving) 是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程

6.代理(proxy) 一个类被aop织入增强后,就产生出了一个结果类,它是融合了原类和增强逻辑的代理类。

7.切面(aspect)切入点+通知

通知类型:spring按照通知advice在目标类方法的连接点位置,可以分为5类

  • 前置通知 (在目标方法执行前实施增强)

  • 后置通知(在目标方法执行后实施增强)

  • 环绕通知(在目标方法执行前后实施增加)

  • 异常抛出通知(在方法跑出异常时通知)

  • 引介通知(在目标类中添加一些新的方法和属性)

四、实现原理

aop的实现关键在于aop框架自动创建的aop代理。aop代理主要分为两大类:

静态代理:使用aop框架提供的命令进行编译,从而在编译阶段就可以生成aop代理类,因此也称为编译时增强;静态代理一aspectj为代表。

动态代理:在运行时借助于jdk动态代理,cglib等在内存中临时生成aop动态代理类,因此也被称为运行时增强,spring aop用的就是动态代理。

4.1 静态代理

例子:在增加员工和删除员工时增加事务处理

//员工类
public class employee {
    private integer uid;

    public void setuid(integer uid) {
        this.uid = uid;
    }

    public integer getuid() {

        return uid;
    }

    private integer age;

    private string name;

    public integer getage() {
        return age;
    }

    public string getname() {
        return name;
    }

    public void setage(integer age) {
        this.age = age;
    }

    public void setname(string name) {
        this.name = name;
    }
}

员工接口:

//员工接口
public interface employeeservice {
    //新增方法
    void addemployee(employee employee);
    //删除方法
    void deleteemployee(integer uid);
}

员工实现:

//员工方法实现
public class employeeserviceimpl implements employeeservice {
    @override
    public void addemployee(employee employee) {
        system.out.println("新增员工");
    }

    @override
    public void deleteemployee(integer uid) {
        system.out.println("删除员工");

    }
}

事务类:

//事务类
public class mytransaction {
    //开启事务
    public void before(){
        system.out.println("开启事务");
    }
    //提交事务
    public void after(){
        system.out.println("提交事务");
    }
}

代理类:

//代理类
public class proxyemployee implements employeeservice {
    //
    private employeeservice employeeservice;

    private mytransaction mytransaction;

    public proxyemployee(employeeservice employeeservice,mytransaction mytransaction)
    {
       this.employeeservice=employeeservice;
       this.mytransaction=mytransaction;
    }
    @override
    public void addemployee(employee employee) {
         mytransaction.before();
         employeeservice.addemployee(employee);
         mytransaction.after();
    }

    @override
    public void deleteemployee(integer uid) {
         mytransaction.before();
         employeeservice.deleteemployee(uid);
         mytransaction.after();
    }
}

测试:

@test
    public void fun1(){
        mytransaction transaction = new mytransaction();
        employeeservice employeeservice = new employeeserviceimpl();
        //产生静态代理对象
        proxyemployee proxy = new proxyemployee(employeeservice, transaction);
        proxy.addemployee(null);
        proxy.deleteemployee(0);
    }

结果:

spring之旅第五篇-AOP详解

这是静态代理的实现方式,静态代理有明显的缺点:

1、代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。

2、如果接口增加一个方法,比如 employeeservice增加修改 updateemployee()方法,则除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

4.2 动态代理

动态代理就不要自己手动生成代理类了,我们去掉 proxyemployee.java 类,增加一个 objectinterceptor.java 类

public class objectinterceptor implements invocationhandler {

    //目标类
    private object target;
    //切面类(这里指事务类)
    private mytransaction transaction;

    //通过构造器赋值
    public objectinterceptor(object target,mytransaction transaction){
        this.target = target;
        this.transaction = transaction;
    }


    @override
    public object invoke(object proxy, method method, object[] args)
            throws throwable {
        this.transaction.before();
        method.invoke(target,args);
        this.transaction.after();
        return  null;
    }
}

测试:

@test
public void fun2(){
    //目标类
    object target = new employeeserviceimpl ();
    //事务类
    mytransaction transaction = new mytransaction();
    objectinterceptor proxyobject = new objectinterceptor(target, transaction);
    /**
     * 三个参数的含义:
     * 1、目标类的类加载器
     * 2、目标类所有实现的接口
     * 3、拦截器
     */
    employeeservice employeeservice = (employeeservice) proxy.newproxyinstance(target.getclass().getclassloader(),
            target.getclass().getinterfaces(), proxyobject);
    employeeservice.addemployee(null);
    employeeservice.deleteemployee(0);
}

结果:

spring之旅第五篇-AOP详解

 五、spring的处理aop的方式

spring 有两种方式实现aop的:一种是采用声明的方式来实现(基于xml),一种是采用注解的方式来实现(基于aspectj)

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

     <!--目标类-->
     <bean name="employeeservice" class="com.yuanqinnan.aop.employeeserviceimpl"></bean>
     <bean name="transaction" class="com.yuanqinnan.aop.mytransaction"></bean>
     <aop:config>
          <aop:aspect ref="transaction">
               <aop:pointcut id="pointcut" expression="execution(* com.yuanqinnan.aop.employeeserviceimpl..*(..))"/>
               <!-- 配置前置通知,注意 method 的值要和 对应切面的类方法名称相同 -->
               <aop:before method="before" pointcut-ref="pointcut"></aop:before>
               <!--配置后置通知,注意 method 的值要和 对应切面的类方法名称相同-->
               <aop:after-returning method="after" pointcut-ref="pointcut"/>
          </aop:aspect>
     </aop:config>

</beans>

测试:

@test
public void fun3(){
    applicationcontext context = new classpathxmlapplicationcontext("meta-inf/applicationcontext.xml");
    employeeservice employeeservice = (employeeservice) context.getbean("employeeservice");
    employeeservice.addemployee(null);
}

结果:

spring之旅第五篇-AOP详解

补充:

1.如果位于元素中,则命名切点只能被当前内定义的元素访问到,为了能被整个元素中定义的所有增强访问,则必须在下定义切点。

2.如果在元素下直接定义,必须保证之前定义。下还可以定义,三者在中的配置有先后顺序的要求:首先必须是,然后是,最后是。而在中定义的则没有先后顺序的要求,可以在任何位置定义。

:用来定义切入点,该切入点可以重用;

:用来定义只有一个通知和一个切入点的切面;

:用来定义切面,该切面可以包含多个切入点和通知,而且标签内部的通知和切入点定义是无序的;和advisor的区别就在此,advisor只包含一个通知和一个切入点。

3.在使用spring框架配置aop的时候,不管是通过xml配置文件还是注解的方式都需要定义pointcut"切入点" 例如定义切入点表达式 execution(* com.sample.service.impl...(..)) execution()是最常用的切点函数,其语法如下所示:

整个表达式可以分为五个部分: (1)、execution(): 表达式主体。

(2)、第一个号:表示返回类型,号表示所有的类型。

(3)、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。

(4)、第二个号:表示类名,号表示所有的类。

(5)、(..):最后这个星号表示方法名,号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

5.2 注解配置

新建注解类:

@component
@aspect
public class aopaspectj {
    /**
     * 必须为final string类型的,注解里要使用的变量只能是静态常量类型的
     */
    public static final string edp="execution(* com.yuanqinnan.aop.employeeserviceimpl..*(..))";

    /**
     * 切面的前置方法 即方法执行前拦截到的方法
     * 在目标方法执行之前的通知
     * @param jp
     */
    @before(edp)
    public void dobefore(joinpoint jp){

        system.out.println("=========执行前置通知==========");
    }


    /**
     * 在方法正常执行通过之后执行的通知叫做返回通知
     * 可以返回到方法的返回值 在注解后加入returning
     * @param jp
     * @param result
     */
    @afterreturning(value=edp,returning="result")
    public void doafterreturning(joinpoint jp,string result){
        system.out.println("===========执行后置通知============");
    }

    /**
     * 最终通知:目标方法调用之后执行的通知(无论目标方法是否出现异常均执行)
     * @param jp
     */
    @after(value=edp)
    public void doafter(joinpoint jp){
        system.out.println("===========执行最终通知============");
    }

    /**
     * 环绕通知:目标方法调用前后执行的通知,可以在方法调用前后完成自定义的行为。
     * @param pjp
     * @return
     * @throws throwable
     */
    @around(edp)
    public object doaround(proceedingjoinpoint pjp) throws throwable{

        system.out.println("======执行环绕通知开始=========");
        // 调用方法的参数
        object[] args = pjp.getargs();
        // 调用的方法名
        string method = pjp.getsignature().getname();
        // 获取目标对象
        object target = pjp.gettarget();
        // 执行完方法的返回值
        // 调用proceed()方法,就会触发切入点方法执行
        object result=pjp.proceed();
        system.out.println("输出,方法名:" + method + ";目标对象:" + target + ";返回值:" + result);
        system.out.println("======执行环绕通知结束=========");
        return result;
    }

    /**
     * 在目标方法非正常执行完成, 抛出异常的时候会走此方法
     * @param jp
     * @param ex
     */
    @afterthrowing(value=edp,throwing="ex")
    public void doafterthrowing(joinpoint jp,exception ex) {
        system.out.println("===========执行异常通知============");
    }
}

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">
    <context:component-scan base-package="com.yuanqinnan.aop" ></context:component-scan>
    <!-- 声明spring对@aspectj的支持 -->
    <aop:aspectj-autoproxy/>
    <!--目标类-->
    <bean name="employeeservice" class="com.yuanqinnan.aop.employeeserviceimpl"></bean>
</beans>

测试:

@test
public void fun4(){
    applicationcontext act =  new classpathxmlapplicationcontext("meta-inf/applicationcontext.xml");
    employeeservice employeeservice = (employeeservice) act.getbean("employeeservice");
    employeeservice.addemployee(null);
}

结果:

spring之旅第五篇-AOP详解

pringaop的知识就总结到这里