spring-AOP原理与应用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring-AOP原理与应用相关的知识,希望对你有一定的参考价值。

技术分享图片

技术分享图片


什么是AOP

技术分享图片

Spring是解决实际开发中的一些问题:

* AOP解决OOP中遇到的一些问题.OOP的延续和扩展.

AOP作用

    对程序进行增强:不修改源码的情况下.

    * AOP可以进行权限校验,日志记录,性能监控,事务控制.

SpringAOP的由来:

    AOP最早由AOP联盟的组织提出的,制定了一套规范.SpringAOP思想引入到框架中,必须遵守AOP联盟的规范.

底层实现:

    代理机制:

    * Spring的AOP的底层用到两种代理机制:

        * JDK的动态代理 :针对实现了接口的类产生代理.

        * Cglib的动态代理 :针对没有实现接口的类产生代理. 应用的是底层的字节码增强的技术 生成当前类的子类对象.

 

动态代理

1 运行时实现指定的接口

想实现某个接口,你需要写一个类,然后在类名字的后面给出“implementsXXX接口。这才是实现某个接口:

public interface MyInterface {
    void fun1();
    void fun2();
}
public class MyInterfaceImpl implements MyInterface {
    public void fun1() {
        System.out.println("fun1()");
    }
     
    public void fun2() {
        System.out.println("fun2()");
    }
}

上面的代码对我们来说没有什么新鲜感,我们要说的是动态代理技术可以通过一个方法调用就可以生成一个对指定接口的实现类对象。

Class[] cs = {MyInterface.class};

MyInterface mi = (MyInterface)Proxy.newProxyInstance(loader, cs, h);

 

上面代码中,Proxy类的静态方法newProxyInstance()方法生成了一个对象,这个对象实现了cs数组中指定的接口。没错,返回值miMyInterface接口的实现类。你不要问这个类是哪个类,你只需要知道miMyInterface接口的实现类就可以了。你现在也不用去管loaderh这两个参数是什么东东,你只需要知道,Proxy类的静态方法newProxyInstance()方法返回的方法是实现了指定接口的实现类对象,甚至你都没有看见实现类的代码。

动态代理就是在运行时生成一个类,这个类会实现你指定的一组接口,而这个类没有.java文件,是在运行时生成的,你也不用去关心它是什么类型的,你只需要知道它实现了哪些接口即可。

2 newProxyInstance()方法的参数

Proxy类的newInstance()方法有三个参数:

ClassLoader loader:它是类加载器类型,你不用去理睬它,你只需要知道怎么可以获得它就可以了:MyInterface.class.getClassLoader()就可以获取到ClassLoader对象,没错,只要你有一个Class对象就可以获取到ClassLoader对象;

Class[] interfaces:指定newProxyInstance()方法返回的对象要实现哪些接口,没错,可以指定多个接口,例如上面例子只我们只指定了一个接口:Class[] cs = {MyInterface.class};

InvocationHandler h:它是最重要的一个参数!它是一个接口!它的名字叫调用处理器!你想一想,上面例子中mi对象是MyInterface接口的实现类对象,那么它一定是可以调用fun1()fun2()方法了,难道你不想调用一下fun1()fun2()方法么,它会执行些什么东东呢?其实无论你调用代理对象的什么方法,它都是在调用InvocationHandlerinvoke()方法!

public static void main(String[] args) {
    Class[] cs = {MyInterface.class};
    ClassLoader loader = MyInterface.class.getClassLoader();
    InvocationHandler h = new InvocationHandler() {
        public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable {
            System.out.println("无论你调用代理对象的什么方法,其实都是在调用invoke()...");
            return null;
        }
    };
    MyInterface mi = (MyInterface)Proxy.newProxyInstance(loader, cs, h);
    mi.fun1();
    mi.fun2();
}

  InvocationHandler接口只有一个方法,即invoke()方法!它是对代理对象所有方法的唯一实现。也就是说,无论你调用代理对象上的哪个方法,其实都是在调用InvocationHandlerinvoke()方法。

想象中的类:

class X implements MyInterface {
    private InvocationHandler h;
    public X(InvocationHandler h) {
        this.h = h;
    }
    
    public void fun1() {
        h.invoke();
    }
    public void fun2() {
        h.invoke();
    }
}

注意,X类是我们用来理解代理对象与InvocationHandler之间的关系的,但它是不存在的类。是我们想象出来的!也就是说,它是用来说明,无论你调用代理对象的哪个方法,最终调用的都是调用处理器的invoke()方法。

3 InvocationHandlerinvoke()方法

技术分享图片


InvocationHandler的invoke()方法的参数有三个:

Object proxy:代理对象,也就是Proxy.newProxyInstance()方法返回的对象,通常我们用不上它;

Method method:表示当前被调用方法的反射对象,例如mi.fun1(),那么method就是fun1()方法的反射对象;

Object[] args:表示当前被调用方法的参数,当然mi.fun1()这个调用是没有参数的,所以args是一个零长数组。

  最后要说的是invoke()方法的返回值为Object类型,它表示当前被调用的方法的返回值,当然mi.fun1()方法是没有返回值的,所以invoke()返回的就必须是null了。

public static void main(String[] args) {
    Class[] cs = {MyInterface.class};
    ClassLoader loader = MyInterface.class.getClassLoader();
    InvocationHandler h = new InvocationHandler() {
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable {
        System.out.println("当前调用的方法是:" + method.getName());
        return null;
    }
    };
    MyInterface mi = (MyInterface)Proxy.newProxyInstance(loader, cs, h);
    mi.fun1();
    mi.fun2();
}

AOP的开发中的相关术语:

Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring,这些点指的是方法,因为spring只支持方法类型的连接点.

Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义.

Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)

Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.

Target(目标对象):代理的目标对象

Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程.

spring采用动态代理织入,而AspectJ采用编译期织入和类装在期织入

Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类

Aspect(切面): 是切入点和通知(引介)的结合

Spring使用AspectJ进行AOP的开发:XML的方式(*****

引入相应的jar

* 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

技术分享图片


引入Spring的配置文件

引入AOP约束:

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


通知类型

    前置通知 :在目标方法执行之前执行

    后置通知 :在目标方法执行之后执行

    环绕通知 :在目标方法执行前和执行后执行

    异常抛出通知:在目标方法执行出现 异常的时候执行

    最终通知 :无论目标方法是否出现异常最终通知都会执行

切入点表达式

    execution(表达式)

    表达式:

    [方法访问修饰符] 方法返回值 包名.类名.方法名(方法的参数)

    public * cn.spring.dao.*.*(..)

    * cn.spring.dao.*.*(..)

    * cn.spring.dao.UserDao+.*(..)

    * cn.spring.dao..*.*(..)

案例

技术分享图片


其他的增强的配置:

<!-- 配置切面类 -->
<bean id="myAspectXml" class="cn.itcast.spring.demo3.MyAspectXml"></bean>

<!-- 进行aop的配置 -->
<aop:config>
    <!-- 配置切入点表达式:哪些类的哪些方法需要进行增强 -->
    <aop:pointcut expression="execution(* cn.spring.demo3.*Dao.save(..))" id="pointcut1"/>
    <aop:pointcut expression="execution(* cn.spring.demo3.*Dao.delete(..))" id="pointcut2"/>
    <aop:pointcut expression="execution(* cn.spring.demo3.*Dao.update(..))" id="pointcut3"/>
    <aop:pointcut expression="execution(* cn.spring.demo3.*Dao.find(..))" id="pointcut4"/>
    <!-- 配置切面 -->
    <aop:aspect ref="myAspectXml">
        <aop:before method="before" pointcut-ref="pointcut1"/>
        <aop:after-returning method="afterReturing" pointcut-ref="pointcut2"/>
        <aop:around method="around" pointcut-ref="pointcut3"/>
        <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4"/>
        <aop:after method="after" pointcut-ref="pointcut4"/>
    </aop:aspect>
</aop:config>


Spring使用AspectJ进行AOP的开发:注解的方式

引入相关的jar:

引入Spring的配置文件

引入AOP约束:

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

编写目标类:

public class ProductDao {
public void save(){
System.out.println("保存商品...");
}
public void update(){
System.out.println("修改商品...");
}
public void delete(){
System.out.println("删除商品...");
}
public void find(){
System.out.println("查询商品...");
}
}

配置目标类:

<!-- 目标类============ -->
     <bean id="productDao" class="cn.spring.demo4.ProductDao"></bean>

开启aop注解的自动代理:

<aop:aspectj-autoproxy/>

AspectJAOP的注解:

@Aspect:定义切面类的注解

 

通知类型:

    * @Before   :前置通知

    * @AfterReturing  :后置通知

    * @Around   :环绕通知

    * @After    :最终通知

    * @AfterThrowing  :异常抛出通知.

 

@Pointcut:定义切入点的注解

编写切面类:

@Aspect
public class MyAspectAnno {
 
    @Before("MyAspectAnno.pointcut1()")
    public void before(){
    System.out.println("前置通知===========");
    }
    
    @Pointcut("execution(* cn.spring.demo4.ProductDao.save(..))")
    private void pointcut1(){}
}

配置切面:

<!-- 配置切面类 -->
     <bean id="myAspectAnno" class="cn.spring.demo4.MyAspectAnno"></bean>

其他通知的注解:

@Aspect
public class MyAspectAnno {
 
    @Before("MyAspectAnno.pointcut1()")
    public void before(){
        System.out.println("前置通知===========");
    }
    
    @AfterReturning("MyAspectAnno.pointcut2()")
    public void afterReturning(){
        System.out.println("后置通知===========");
    }
    
    @Around("MyAspectAnno.pointcut3()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        System.out.println("环绕前通知==========");
        Object obj = joinPoint.proceed();
        System.out.println("环绕后通知==========");
        return obj;
    }
    
    @AfterThrowing("MyAspectAnno.pointcut4()")
    public void afterThrowing(){
        System.out.println("异常抛出通知========");
    }
    
    @After("MyAspectAnno.pointcut4()")
    public void after(){
        System.out.println("最终通知==========");
    }
    
    @Pointcut("execution(* cn.itcast.spring.demo4.ProductDao.save(..))")
    private void pointcut1(){}
    @Pointcut("execution(* cn.itcast.spring.demo4.ProductDao.update(..))")
    private void pointcut2(){}
    @Pointcut("execution(* cn.itcast.spring.demo4.ProductDao.delete(..))")
    private void pointcut3(){}
    @Pointcut("execution(* cn.itcast.spring.demo4.ProductDao.find(..))")
    private void pointcut4(){}
}



以上是关于spring-AOP原理与应用的主要内容,如果未能解决你的问题,请参考以下文章

深入理解spring-AOP注解的底层实现原理

spring-AOP(二) 自动代理

Spring-AOP

spring-aop代理的生效原理

你必须要懂的 Spring-Aop 之源码跟踪分析 Aop

Spring-AOP基础概念和操作详解