Spring系列之AOP

一、概述

  AOP(Aspect Oriented Programming),面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可用性,提高开发效率。

  主要功能体现在:日志记录,性能统计,安全控制,事务处理,异常处理等。

  主要概念:

  • 切面(Aspect):要实现的交叉功能,是应用系统模块化的一个切面或领域,常见的例子就是日志记录
  • 连接点(JoinPoint):应用程序执行过程中插入切面的地点,可以是方法调用,异常抛出,切面代码在这些地方插入到你的应用流程中,添加新的行为
  • 通知(Advice):通知切面的实际实现,它通知应用系统新的行为
  • 切入点(Pointcut):定义了通知应该在应用在哪些连接点,通常通过指定类名和方法名或者匹配类名和方法名式样的正则表达式来指定切入点
  • 目标对象(Target):被通知的对象,既可以是你编写的类也可以是你要添加定制行为的第三方类

  AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理两大类,其中静态代理是使用AOP框架提供的命令进行编译,从而在编译阶段就可以生成AOP代理类,也称为编译时增强,动态代理则在运行时借助于JDK动态代理、CGLIB等在内存中临时生成的动态代理类,也被称为运行时增强。静态代理以AspectJ为代表,动态代理则以Spring AOP为代表。

  下面对两者进行逐一分析。

二、基于动态代理实现AOP

  1、JDK动态代理

  下面以记录日志为例作一个简单的示例,首先是声明一个接口:

public interface Aithmetic
{
    public int add(int i,int j);
    public int sub(int i,int j);
}

  然后定义接口的实现:

public class AithmeticImpl implements Aithmetic
{
    @Override
    public int add(int i, int j)
    {
        int result=i+j;
        return result;
    }
    @Override
    public int sub(int i, int j)
    {
        int result=i-j;
        return result;
    }
}

  如果想在做加减法操作前后加一些日志记录,就要编写代理实现:

public class AithmeticLoggingProxy
{
    private Aithmetic target;
    public AithmeticLoggingProxy(Aithmetic target)
    {
        super();
        this.target = target;
    }
    public Aithmetic getLoggingProxy()
    {
        Aithmetic proxy=null;
        //代理对象由哪一个类加载器加载
        ClassLoader loader=target.getClass().getClassLoader();
        //代理对象的类型,即其中有哪些方法
        Class[] interfaces=new Class[]{Aithmetic.class};
        //当调用代理对象其中的方法时,该执行的代码
        InvocationHandler h=new InvocationHandler()
        {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
            {
                String methodName=method.getName();
                System.out.println("the method:"+methodName+"开始"+Arrays.asList(args));
                Object result=method.invoke(target, args);
                System.out.println("the method:"+methodName+"结束"+result);
                return result;
            }
        };
        proxy=(Aithmetic) Proxy.newProxyInstance(loader, interfaces, h);
        return proxy;
    }
}

  编写测试类:

public class TestAithmethicLogging
{
    public static void main(String[] args)
    {
        Aithmetic target=new AithmeticImpl();
        Aithmetic proxy=new AithmeticLoggingProxy(target).getLoggingProxy();
        System.out.println(proxy.add(2, 3));
    }
}

  运行结果为:

  

  2、CGLIB动态代理实现AOP

  上面提到基于JDK动态代理的对象必须实现一个或多个接口,如果想代理没有接口的继承的类时,就需要使用CGLIB包,CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。我们在工程中引入下面这三个jar包:

  

  编写需要代理的目标对象:

public class Hello
{
    public int add(int i,int j)
    {
        int result=i+j;
        return result;
    }
}

  创建cglib代理的工厂类:

public class HelloProxy implements MethodInterceptor
{
    //设置需要创建子类的类
    private Object targetObject;
    public Object createProxyInstance(Object target)
    {
        this.targetObject = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.targetObject.getClass());
        //设置回调函数
        enhancer.setCallback(this);
        //通过字节码技术动态创建子类实例
        return enhancer.create();
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable
    {
        /*obj:目标类实例
         *method:目标类法方法的反射对象
         *args:方法的动态入参
         *methodProxy:代理类实例
        */
        System.out.println("加法操作开始");
        //通过代理类调用父类中的方法
        Object result = methodProxy.invoke(targetObject, args);
        System.out.println("加法操作结束");
        return result;
    }
}

  编写测试类并运行:

public class TestHello
{
    public static void main(String[] args)
    {
        HelloProxy proxy=new HelloProxy();
        Hello hello=(Hello) proxy.createProxyInstance(new Hello());
        System.out.println(hello.add(2, 3));
    }
}

  运行结果与上图一致。

  注:CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法不要声明成final。

三、Spring的AOP实现

  Spring的连接点模型是建立在方法拦截上的,我们编写的Spring通知会在方法调用周围的各个地方织入系统中,以下是Spring提供的几种类型的通知以及他们在什么地方织入到你的代码中:

  

  注:使用Spring的AOP需要引入Spring和aopalliance的jar包。

  现在我们假设有这样的接口和其实现:

public interface Service
{
    public void add(int i,int j);
}

//接口实现类
public class ServiceImpl implements Service
{
    @Override
    public void add(int i,int j)
    {
        int result=i+j;     return result;
    }
}

  Spring可以通过不同的方式来引入通知:

  1、编程式

  我们来写一个前置通知:

public class ServiceBeforeAdvice implements MethodBeforeAdvice
{
    @Override
    public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable
    {
        System.out.println("Before");
    }
}

  编写客户端将他们集成起来:

public class Client
{
    public static void main(String[] args)
    {
        ProxyFactory proxyFactory=new ProxyFactory();
        //射入目标对象
        proxyFactory.setTarget(new ServiceImpl());
        //添加前置增强
        proxyFactory.addAdvice(new ServiceBeforeAdvice());
        Service  service=(Service) proxyFactory.getProxy();
        System.out.println("运算结果为:"+service.add(2, 3));
    }
}

  运行结果为:

  

  2、声明式

  下面引入一个环绕通知:

@Component
public class ServiceAroundAdvice implements MethodInterceptor
{
     @Override
        public Object invoke(MethodInvocation invocation) throws Throwable
        {
            before();
            Object result = invocation.proceed();
            after();
            return result;
        }
        private void before()
        {
            System.out.println("Before");
        }
        private void after()
        {
            System.out.println("After");
        }
}

  然后编写Spring配置文件:

<?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/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
        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-3.0.xsd">
    <!--配置自动扫描的包-->
    <context:component-scan base-package="springAOP"></context:component-scan>    

    <!-- 配置一个代理 -->
   <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="interfaces" value="springAOP.Service"/>
        <property name="target" ref="serviceImpl"/>
        <property name="interceptorNames" value="serviceAroundAdvice"/>
    </bean>
</beans>

  最后编写客户端

public class Client
{
    public static void main(String[] args)
    {
        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
        Service service=(Service) context.getBean("serviceProxy");
        System.out.println(service.add(2, 3));
    }
}

  运行结果截图:

  

  Spring AOP切面:我们可以通过切面,将增强类与拦截匹配条件组合在一起,然后将这个切面配置到ProxyFactory中,从而生成代理,这里的拦截条件在AOP即为切点。

  假设我们现在的实现类有两个方法,add和sub,我们只想拦截add方法,对sub不做拦截。

@Component
public class ServiceImpl implements Service
{

    @Override
    public int add(int i, int j)
    {
        int result=i+j;
        return result;
    }

    @Override
    public int sub(int i, int j)
    {
        int result=i-j;
        return result;
    }
}

  Spring配置文件为:

<?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/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
        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-3.0.xsd">
    <!--配置自动扫描的包-->
     <context:component-scan base-package="springAOP"></context:component-scan>

     <!--配置一个切面  -->
     <bean id="serviceAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
         <property name="advice" ref="serviceAroundAdvice"/>
         <property name="pattern" value="springAOP.ServiceImpl.add"/>
     </bean>

    <!-- 配置一个代理 -->
   <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="serviceImpl"/>
           <property name="proxyTargetClass" value="true"/>
            <property name="interceptorNames" value="serviceAdvisor"/>
    </bean>
</beans>

  编写并运行客户端: 

public class Client
{
    public static void main(String[] args)
    {
        ApplicationContext context=new ClassPathXmlApplicationContext("bean2.xml");
        Service service=(Service) context.getBean("serviceProxy");
        System.out.println(service.add(2, 3));
        System.out.println(service.sub(3, 1));
    }
}

  结果截图

  

  可以看到只对add方法引入了环绕通知,而sub方法并没有进行拦截。

四、基于Spring+AspectJ方式

  使用Spring AOP,在Spring配置文件中会存在大量的切面配置,相当的麻烦。现在Spring在保留了上面的提到的切面与代理配置方式也集成了AspectJ,使用AspectJ需要引入下面两个包

  

  1、基于注解:通过Aspect execution表达式拦截方法

  首先我们来定义一个Aspect切面类:

@Aspect
@Component
//把这个类声明为一个切面:需要把这个类放入到IOC容器中,在声明为一个切面
public class ServiceAspect
{
    @Around("execution(* springAOP.Service.*(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable
    {
        before();
        Object result=point.proceed();
        after();
        return result;
    }
    private void after()
    {
        System.out.println("after");

    }
    private void before()
    {
        System.out.println("before");
    }
}

  类上面标注的@Aspect注解,表明该类是一个Aspect,在方法上标注@Around表示环绕增强,在注解中使用了Aspect切点表达式,方法的参数中包括一个ProceedingJoinPoint对象,在AOP中被称为JoinPoint连接点,可以通过该对象获取方法的任何信息,例如方法名、参数等。

  再看看Spring的配置文件:

<?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/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
        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-3.0.xsd">

    <!--配置自动扫描的包-->
    <context:component-scan base-package="springAOP"></context:component-scan>        

    <!--使AspectJ注解起作用:自动为匹配的类生成代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

  编写客户端并运行:

public class Client
{
    public static void main(String[] args)
    {
        ApplicationContext context=new ClassPathXmlApplicationContext("bean3.xml");
        Service service=(Service) context.getBean(Service.class);
        System.out.println(service.add(2, 3));
        System.out.println(service.sub(3, 1));
    }
}

  结果为:

  

  2、基于配置

  Spring AOP还提供了基于配置的方式来定义切面类:

/*@Aspect*/
/*@Component*/
//把这个类声明为一个切面:需要把这个类放入到IOC容器中,在声明为一个切面
public class ServiceAspect
{
    /*@Around("execution(* springAOP.Service.(..))")*/
    public Object around(ProceedingJoinPoint point) throws Throwable
    {
        before();
        Object result=point.proceed();
        after();
        return result;
    }
    private void after()
    {
        System.out.println("after");

    }
    private void before()
    {
        System.out.println("before");
    }
}

  Spring配置文件如下:

<?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/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
        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-3.0.xsd">

    <bean id="ServiceImpl" class="springAOP.ServiceImpl">
    </bean>

    <bean id="ServiceAspect" class="springAOP.ServiceAspect">
    </bean>

    <aop:config>
        <aop:aspect ref="ServiceAspect">
            <aop:around method="around" pointcut="execution(* springAOP.Service.*(..))"/>
        </aop:aspect>
    </aop:config>
</beans>

  编写客户端并运行:

public class Client
{
    public static void main(String[] args)
    {
        ApplicationContext context=new ClassPathXmlApplicationContext("bean4.xml");
        Service service=(Service) context.getBean("ServiceImpl");
        System.out.println(service.add(2, 3));
        System.out.println(service.sub(3, 1));
    }
}

  运行结果同上图一致。

五、总结

  通过上述的阐述,可用一张图来总结Spring AOP的知识结构:

  

时间: 2024-10-01 00:06:12

Spring系列之AOP的相关文章

Spring系列之AOP的原理及手动实现

目录 Spring系列之IOC的原理及手动实现 Spring系列之DI的原理及手动实现 引入 到目前为止,我们已经完成了简易的IOC和DI的功能,虽然相比如Spring来说肯定是非常简陋的,但是毕竟我们是为了理解原理的,也没必要一定要做一个和Spring一样的东西.到了现在并不能让我们松一口气,前面的IOC和DI都还算比较简单,这里要介绍的AOP难度就稍微要大一点了. tips 本篇内容难度较大,每一步都需要理清思路,可能需要多看几遍,多画类图和手动实现更容易掌握. AOP 什么是AOP Asp

Spring系列之AOP实现的两种方式

AOP常用的实现方式有两种,一种是采用声明的方式来实现(基于XML),一种是采用注解的方式来实现(基于AspectJ). 首先复习下AOP中一些比较重要的概念: Joinpoint(连接点):程序执行时的某个特定的点,在Spring中就是某一个方法的执行 .Pointcut(切点):说的通俗点,spring中AOP的切点就是指一些方法的集合,而这些方法是需要被增强.被代理的.一般都是按照一定的约定规则来表示的,如正则表达式等.切点是由一类连接点组成. Advice(通知):还是说的通俗点,就是在

Spring系列之手写一个SpringMVC

目录 Spring系列之IOC的原理及手动实现 Spring系列之DI的原理及手动实现 Spring系列之AOP的原理及手动实现 Spring系列之手写注解与配置文件的解析 引言 在前面的几个章节中我们已经简单的完成了一个简易版的spring,已经包括容器,依赖注入,AOP和配置文件解析等功能.这一节我们来实现一个自己的springMvc. 关于MVC/SpringMVC springMvc是一个基于mvc模式的web框架,SpringMVC框架是一种提供了MVC(模型 - 视图 - 控制器)架

[Spring系列02]Spring AOP模拟

在博文[Spring系列01]Spring IOC/DI模拟中简略模拟了Spring IOC/DI的实现原理,本文接着模拟了Spring AOP的实现原理. 代码结构图如下: 全部代码如下: UserDAO.java package com.ctsh.dao; import com.ctsh.model.User; public interface UserDAO { public void save(User user); public void delete(); } UserDAO Use

Spring系列(四):Spring AOP详解

一.AOP是什么 AOP(面向切面编程),可以说是一种编程思想,其中的Spring AOP和AspectJ都是现实了这种编程思想.相对OOP(面向过程编程)来说,提供了另外一种编程方式,对于OOP过程中产生的横切性问题,这些横切性与业务无关,可以通过预编译方式和运行期动态代理来实现.比如可以应用在:日志记录.性能监控.事务管理等. 二.AOP的基本概念 Aspect(切面):通常来说是一个类,里面定义了切点和通知,Spring AOP中可以用@AspectJ来标注这个类是切面: Join poi

[JavaEE] IBM - Spring 系列: Spring 框架简介

Spring AOP 和 IOC 容器入门 在这由三部分组成的介绍 Spring 框架的系列文章的第一期中,将开始学习如何用 Spring 技术构建轻量级的.强壮的 J2EE 应用程序.developerWorks 的定期投稿人 Naveen Balani 通过介绍 Spring 框架开始了他由三部分组成的 Spring 系列,其中还将介绍 Spring 面向方面的编程(AOP)和控制反转(IOC)容器. Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的.框架的主要优势之一

Spring中的AOP(五)——在Advice方法中获取目标方法的参数

摘要: 本文介绍使用Spring AOP编程中,在增强处理方法中获取目标方法的参数,定义切点表达式时使用args来快速获取目标方法的参数. 获取目标方法的信息 访问目标方法最简单的做法是定义增强处理方法时,将第一个参数定义为JoinPoint类型,当该增强处理方法被调用时,该JoinPoint参数就代表了织入增强处理的连接点.JoinPoint里包含了如下几个常用的方法: Object[] getArgs:返回目标方法的参数 Signature getSignature:返回目标方法的签名 Ob

Spring 系列: Spring 框架简介

Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的.框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架. 在这篇由三部分组成的 Spring 系列 的第 1 部分中,我将介绍 Spring 框架.我先从框架底层模型的角度描述该框架的功能,然后将讨论两个最有趣的模块:Spring 面向方面编程(AOP)和控制反转 (IOC) 容器.接着将使用几个示例演示 IOC 容器在典型应用程序用例场景中的应用情况.这些示例还将成

Spring 3.0 AOP (一)AOP 术语

关于AOP.之前我已写过一个系列的随笔: <自己实现简单的AOP>,它的关注点在于实现.实现语言是C#,实现方式为 自定义实现 RealProxy 抽象类.重写Invoke方法,以便进行方法调用的拦截.借此实现AOP.感兴趣的园友可以去瞅瞅. 今天.我们来看一下Spring中的AOP,本随笔着重关注AOP术语. 先说一句废话:如果你对AOP不是很熟悉.第一次看到这些术语可能会有点迷惑,不过没有关系.坚持继续向下看,然后再反过来看一遍,有些东西就能恍然大悟了. 连接点(Joinpoint) 连接