Spring自定义面向切面编程(AOP)

来源:互联网 发布:unity3d 给模型加动画 编辑:程序博客网 时间:2024/06/02 13:18

摘自标度 -> AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容

个人理解的AOP产生由来:

面向切面编程是一种思想(AOP),以另一种角度来优化程序代码.
OOP的特点是封装,继承,多态,将功能封装到多个对象中,而在封装的过程中,难免会有相同的代码。
例如:日志记录,性能统计,安全控制,事务处理,异常处理等等。

比方说,我们现在有N个类,每个类都有N个方法,每个方法中都要写一遍事务处理,提交回滚,这不是蛋疼吗?
写的不烦吗?有没有什么办法进行统一的管理?有的人可能会想把公共代码提取出来,写成颇为复杂的抽象基类,在N个类之间调用,但是这样又形成了高耦合,因为Java是单继承,还占用了一个继承名额。

你想想啊。在公司的业务层写业务代码,每个方法前需要写日志记录,每个方法后又需要写日志记录,业务的核心代码被淹没在重复的日志代码中,可读性又差,代码又重复。

有些时候,我们需要在Controller层的某些方法前干点别的事情?需要在Dao层的某些方法后干点别的事情?难道就只有硬编码到代码里,而不能基于一些配置,一些约定,将代码灵活的织入进去吗?

AOP是如何处理的呢,程序员将共有的功能抽取出来,由AOP动态的切入到代码中,实现零编程添加功能,代码更简洁,很好的解决了代码冗余问题。比方说业务层,就只需要专注于业务逻辑代码,这些琐碎的事务处理,日志记录就完全不需要管了,是不是很开心?

接下来,关于Spring的AOP的几个必须要清楚的概念:

通知(Advice):就是需要的功能的具体定义实现,在Java中,就是切面类的具体方法,这些方法就是通知。通知分为五种类型:环绕通知(Around),前置通知(Before),后置通知(AfterReturning),异常通知(AfterThrowing), 最终通知(After)。

连接点(Joinpoint):用我自己的理解来看,就是地点。目标调用前,调用后,抛出异常时。就是目标执行的时候某个特定的点。程序在运行的时候能够织入的一个点。这个点可以是方法,字段,属性,类,但是在目前的Spring中,只支持方法连接点。连接点只是一个概念,为了更好的理解,实际开发中我个人是没有发现有啥用的…

切入点(Pointcut):切入点可以筛选通知应该织入到哪个包的哪个类的哪个方法连接点上,不同的通知肯定是切入到不同的连接点嘛,如何定位呢?Spring用正则表达式来定位。

目标对象(Target):这个真的没啥好说的,打个比方:业务层的功能太多,有日志记录,核心业务代码,我们把日志记录抽取出来,业务层只剩下干干净净的核心逻辑代码,日志记录就可以用切面表示,而这个等待Spring动态织入功能的业务层就是目标对象,业务层会在毫不知情的情况下,加入新的功能,实现零代码编写。

切面(Aspect):切面包含了通知和切入点,通知实现了具体的功能和应该什么时候干活,切入点决定了通知在什么地方干活,这就是一个完整的切面

引入(Introduction):SpringAOP是针对方法的增强,虽然没有在类中添加新的方法,但是实实在在的在已有的方法上增加了新的功能,既然能够在现有的基础上增强,能不能直接性的添加新的功能,引入做到了…

织入(Weaving):将切面织入到目标中,这种行为就叫织入。有三种实现方式:
1.编译期织入
2.类装载期织入
3.动态代理织入
Spring默认使用J2SE动态代理(dynamic proxies)作为AOP的代理方式,也支持使用CGLIB代理,如果一个类没有实现接口,就会使用CGLIB

代理(Proxy):通知被应用到了目标中,会产生一个新的类,这就是代理类,具有目标的功能加上共有的功能。代理类是运行时的产物,实际上是看不见的…

例子1:
核心业务接口:

public interface Sport{    //运动    void sport();}

子类实现:

public class Run implements Sport{    //核心业务    @Override    public void sport() {        System.out.println("尽情的跑步");    }}
public class Swim implements Sport {    //核心业务    @Override    public void sport() {        System.out.println("欢乐的游泳");    }}

切面类:

//切面类public class SportHelper implements MethodBeforeAdvice,AfterReturningAdvice{    //前置通知    @Override    public void before(Method arg0, Object[] arg1, Object arg2)            throws Throwable {        System.out.println("运动前先热身啊");    }    //后置通知    @Override    public void afterReturning(Object arg0, Method arg1, Object[] arg2,            Object arg3) throws Throwable {        System.out.println("运动后会流汗啊");    }}

配置信息:

    <bean id="sportHelper" class="aop.SportHelper"></bean>    <bean id="run" class="intercept.impl.Run"></bean>    <bean id="swim" class="intercept.impl.Swim"></bean>    <!-- 定义切入点   这里是只要以sport结尾的方法都需要织入 -->    <bean id="sportPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">      <property name="pattern" value=".*sport"/>    </bean>    <!-- 定义切面  包含通知和切入点 -->    <bean id="sportHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">         <property name="advice" ref="sportHelper"/>         <property name="pointcut" ref="sportPointcut"/>    </bean>    <!-- 代理类          肯定需要指定目标对象,        指定接口(这里指定了接口,就肯定是用JDK的动态代理),        指定切面    -->    <bean id="RunProxy" class="org.springframework.aop.framework.ProxyFactoryBean">         <property name="target" ref="run"/>         <property name="interceptorNames" value="sportHelperAdvisor" />         <property name="proxyInterfaces" value="intercept.Sport" />    </bean>    <!-- 代理类          肯定需要指定目标对象,        指定接口(这里指定了接口,就肯定是用JDK的动态代理),        指定切面    -->    <bean id="SwimProxy" class="org.springframework.aop.framework.ProxyFactoryBean">         <property name="target" ref="swim"/>         <property name="interceptorNames" value="sportHelperAdvisor" />         <property name="proxyInterfaces" value="intercept.Sport" />    </bean>

如果前面的几个AOP术语看懂了,这些配置看懂是非常轻松的…

测试代码与结果:

    @Test    public void test() {       ApplicationContext appCtx = new ClassPathXmlApplicationContext("config/spring/applicationContext-transaction.xml");        Sport run = (Sport)appCtx.getBean("RunProxy");        run.sport();        System.out.println();        Sport swim = (Sport)appCtx.getBean("SwimProxy");        swim.sport();    }
运动前先热身啊尽情的跑步运动后会流汗啊运动前先热身啊欢乐的游泳运动后会流汗啊

我只保留了核心的业务功能,其他共有的辅助功能交给SpringAOP去管理,每次我只需要关心核心业务的编写,其他的配置一遍都不需要管了..

例子2(注解的形式):

核心业务:

@Componentpublic class Run implements Sport{    //核心业务    @Override    public void sport() {        System.out.println("尽情的跑步");    }}
@Componentpublic class Swim implements Sport {    //核心业务    @Override    public void sport() {        System.out.println("欢乐的游泳");    }}

切面类:

@Component@Aspectpublic class SportHelper1 {    //切入点,以方法的形式存在    @Pointcut("execution(* *.sport())")    public void sportPoint(){}    //前置通知    @Before("sportPoint()")    public void beforeSport(){        System.out.println("运动前先热身啊");    }    //后置通知    @After("sportPoint()")    public void afterSport(){        System.out.println("运动后会流汗啊");    }}

配置信息:

    <!-- 启动对@AspectJ注解的支持 -->     <!-- proxy-target-class=true使用cglib代理,        proxy-target-class=false使用JDK动态代理        默认false    -->    <aop:aspectj-autoproxy proxy-target-class="false"/>    <!-- 注解扫描 -->    <context:component-scan base-package="aop.impl"/>

测试:

    @Test    public void test1() {        ApplicationContext appCtx = new ClassPathXmlApplicationContext("config/spring/applicationContext-transaction.xml");        Sport run = (Sport)appCtx.getBean("run");        run.sport();        System.out.println();        Sport swim = (Sport)appCtx.getBean("swim");        swim.sport();    }

结果:

运动前先热身啊尽情的跑步运动后会流汗啊运动前先热身啊欢乐的游泳运动后会流汗啊

例子3:
核心业务与例子2是一样的
切面类:

@Componentpublic class SportHelper1 {    public void beforeSport(){        System.out.println("运动前先热身啊");    }    public void afterSport(){        System.out.println("运动后会流汗啊");    }}

配置信息:

    <aop:aspectj-autoproxy/>     <!-- 注解扫描 -->    <context:component-scan base-package="aop.impl"/>    <aop:config>        <!-- 引入切面 -->        <aop:aspect ref="sportHelper1">            <!-- 配置通知与切入点 -->            <aop:before method="beforeSport" pointcut="execution(* *.sport(..))"/>            <aop:after method="afterSport" pointcut="execution(* *.sport(..))"/>        </aop:aspect>    </aop:config>

测试与结果与例子2是一模一样的。反正代码写来写去,都是围绕几个关键点来的

讲讲写代码时遇到的问题:

配置报错?
检查XML的命名空间,是否还有哪些没有配置

配置注解扫描获取不到对象?

<context:component-scan base-package="aop.impl"/>

配置了注解的类一定要放到指定的包及子包下
主动获取对象或者由IOC注入对象时,对象名默认是类名的首字母小写

配置不理解?
先搞懂AOP思想,再来学Spring的AOP时,理解关键术语,一定要理解

不懂JDK动态代理和CGLIB?
也许会再写一篇文章分析分析,也许不会。JDK动态代理这么重要的东西,设计模式之代理模式,必须掌握的好嘛

切入点表达式不理解?
这个还是有些东西可以讲的。。先放这

SpringAOP是Spring的一个很重要的模块,以上观点仅代表个人理解,作者没有能力阅读英文文档,╮(╯▽╰)╭,如有错误的地方,请及时指出…

原创粉丝点击