SpringAOP的学习笔记

问题:

在学习了SpringIOC之后,可以使用SpringIOC的知识完成责任链的解耦。方便了以后的代码的升级和维护,一个类的整体替换,我们只需要修改配置文件即可。但是因为业务需求只是升级一个方法的功能,保留原有功能的基础上增加新的功能处理,在没有掌握SpringAOP之前会直接修改方法的源码,这个时候要考虑能不能再不修改源码的基础上完成新功能的增加呢?因为有的时候无法直接对源码进行修改,比如升级mybatis中的方法。

方案一:

例:A类中有一个方法testA(),需要对testA()方法进行升级,可以创建一个类A2,里面也声明一个方法也叫testA(),然后再A2的testA()方法中声明的功能代码,同时将类A中的testA()调用一次。然后将调用A的testA()方法的代码替换为调用A2的testA()方法。

方案一的问题:

虽然实现了不修改原有代码的基础上完成了功能的升级,但是当升级的对象比较多时,就会造成一个问题:需要频繁的声明代理类和代理方法。而且还需要频繁的使用代理对象替换真实对象。而我们想要的只是写的扩展代码。这样造成了开发效率比较低。有没有更好的方法呢?

解决:

我们只需要写扩展功能的代码,代理对象和代理方法以及真实对象的替换都由其他人完成。这个(其他人)我们可以使用代码来声明。但是我们还是需要说给哪个类的哪个方法进行扩展,扩展代码增加到原有代码的那个地方?

实现:

SpringAOP

内容:

基于Schema-based的方式

基于AspectJ方式

AOP概念:面向切面的编程(动态创建代理对象和代理方法)

切点:要进行功能扩展的真实方法。

前置通知:在切点之前执行的扩展方法。

后置通知:在切点之后执行的扩展方法。

切面:前置通知+切点+后置通知形成的横向执行的面。

织入:形成切面的过程。

使用:

1.导入jar包

2.创建前置通知和后置通知的方法。

3.在配置文件中声明织入规则。

4.完成功能升级。

总结:

AOP是将代码的手动升级变成了半自动化升级,我们只需要提供升级的代码和升级规则,剩下的事情SpringAOP框架会自动完成,提高了开发效率。SpringAOP的使用是需要练习和记忆的。

基于Schema-based方式实现:

1.导入jar包:SpringIOC和SpringAOP的jar包

2.声明扩展代码:

(1)前置通知类:

public class BankServiceImplBefore implements MethodBeforeAdvice {
    /**
     * 前置通知类
     * @param method 切点的方法对象
     * @param objects 接受的实参的数组
     * @param o 切点的真实对象
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("我是前置方法:");
    }
}

(2)后置通知类:

public class BankServiceImplAfter implements AfterReturningAdvice {
    /**
     * 后置通知:增加日志信息
     * @param o 真实方法的返回值
     * @param method 真是方法的对象
     * @param objects 参数的Object的数组
     * @param o1 真实对象
     * @throws Throwable
     */
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        if((boolean)o){
            Logger logger = Logger.getLogger(BankServiceImplAfter.class);
            logger.debug(objects[0]+"给"+objects[1]+"转了"+objects[2]+"元");
        }
    }
}

(3)环绕通知类:

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class BankServiceImplAround implements MethodInterceptor {
    /**
     * 环绕通知
     * @param methodInvocation :封存了所有相关信息
     *          methodInvocation.proceed() 放行执行真实方法
     *          methodInvocation.getArguments() 获得实参数组
     *          methodInvocation.getMethod() 获得切点的方法对象
     *          methodInvocation.getThis() 获取真实对象
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("环绕-前");
        Object o = methodInvocation.proceed();//放行:调真实方法的切点(相当于过滤器)
        System.out.println("环绕-后");
        return o;
    }
}

(4)异常通知类:

public class BankServiceImplException implements ThrowsAdvice {
     /**
     * 异常通知类
     * @param e 接收代理方法的异常信息
     * @throws Throwable
     */
    public void aferThrowing(Exception e) throws Throwable{
        System.out.println("我是异常通知:");
    }
}

3.声明配置文件,并声明组装规则

(1)配置真实对象的bean

(2)配置前置通知的bean

(3)配置后置通知的bean

(4)配合组装规则

代码示例:

<!--配置业务层的bean:已使用注解的方式获取-->
    <!--后置通知的bean-->
    <bean id="after" class="com.bjsxt.advice.BankServiceImplAfter"></bean>
    <bean id="before" class="com.bjsxt.advice.BankServiceImplBefore"></bean>
    <bean id="around" class="com.bjsxt.advice.BankServiceImplAround"></bean>
    <bean id="throw" class="com.bjsxt.advice.BankServiceImplException"></bean>
    <!--配置注入规则-->
    <aop:config>
       <aop:pointcut id="myPointCut" expression="execution(* com.bjsxt.service.impl.BankServiceImpl.updateInfo(..))" ></aop:pointcut>
        <aop:advisor advice-ref="after" pointcut-ref="myPointCut"></aop:advisor>
        <aop:advisor advice-ref="before" pointcut-ref="myPointCut"></aop:advisor>
        <aop:advisor advice-ref="around" pointcut-ref="myPointCut"></aop:advisor>
        <aop:advisor advice-ref="throw" pointcut-ref="myPointCut"></aop:advisor>
    </aop:config>

4.完成扩展:正常获取对象调用方法即可

基于AspectJ方式实现:

1.问题:Schema-based方式要求前置通知,后置通知等扩展代码,分别声明一个类型文件来实现对相应的接口来完成。比较麻烦。我们希望可以将扩展声明的代码在一个类中完成。

2.解决:Schema-based方式需要通过接口来区分扩展代码的执行顺序,所以每个扩展代码都需要独立声明类并实现相应的接口。我们也不可以将扩展功能代码声明在一个类文件中,在配置文件中配置好前置通知和后置通知等扩展代码。

3.实现:AspectJ方式

4.本质:Schema-based方式:接口区分,AspectJ方式:配置文件区分。

5.使用:

(1)导入jar包(同Schema-based的jar包)

(2)创建通知类,将所有的扩展代码声明在一个类中

public class TeacherAspectj {
    public void before(String name,int age){
        System.out.println("我是前置通知:");
    }
    public void after(){
        System.out.println("我是后置通知:下面是老师");
    }
    public Object around(ProceedingJoinPoint p){
        System.out.println("环绕通知--前");
        Object proceed = null;
        try {
            proceed = p.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("环绕通知--后");
        return proceed;
    }
    public void afterThrowing(Exception e){
        System.out.println("我是异常通知:");
    }
}

(3)在配置文件中声明组装规则:需要声明代码的执行顺序

    <!--为teacher类配置bean:真实方法-->
    <bean id="teacher" class="com.bjsxt.pojo.Teacher"></bean>
    <!--为通知类配置bean-->
    <bean id="advice" class="com.bjsxt.advice.TeacherAspectj"></bean>
    <!--配置aspectj的注入规则-->
    <aop:config>
        <aop:aspect ref="advice">
            <aop:pointcut id="my" expression="execution(* com.bjsxt.pojo.Teacher.textTea(String,int)) and args(name,age)"></aop:pointcut>
            <aop:before method="before" arg-names="name,age" pointcut-ref="my"></aop:before>
            <aop:after method="after" pointcut-ref="my"></aop:after>
            <aop:around method="around" pointcut-ref="my"></aop:around>
            <aop:after-throwing method="afterThrowing" pointcut-ref="my" throwing="e"></aop:after-throwing>
        </aop:aspect>
    </aop:config>

4.正常获取容器中的对象,完成扩展代码的执行。

总结:

在使用AspectJ方式如果通知方法中需要获取参数的话,配置会比较麻烦,但是不涉及参数时,使用AspectJ方式会比较方便,层次比较清晰。建议涉及参数使用Schema-Based方式。

SpringAOP中AspectJ的注解:

1.注意:SpringAOP只针对AspectJ方式提供了注解。

2.使用:

(1)在配置文件中声明注解扫描。

<!--注解扫描-->
    <context:component-scan base-package="com.bjsxt.advice,com.bjsxt.pojo"></context:component-scan>

(2)配置AOP注解的开启

<!--AOP注解开启-->
    <aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy>

(3)使用AspectJ的注解配置

@Component//作用:在类上声明,相当于配置bean标签
public class Teacher {
    /**
     * 作用:在切点方法上使用,声明切点
     * 内容:切点的表达式
     */
    @Pointcut("execution(* com.bjsxt.pojo.Teacher.textTea())")
    public void textTea(){
        //int i = 5/0;
        System.out.println("真实方法:我是一名老师");
    }
}
@Component//作用:在类上声明,相当于配置bean标签
@Aspect //作用:声明在通知类上,表示该类为通知类,用来功能扩展的
public class TeacherAspectj {
    /**
     * 作用:声明在前置通知方法上
     * 内容:切点的全限定路径
     */
    @Before("com.bjsxt.pojo.Teacher.testTea()")
    public void before(){
        System.out.println("我是前置通知:");
    }
    /**
     * 作用:声明在后置通知方法上
     * 内容:切点的全限定路径
     */
    @After("com.bjsxt.pojo.Teacher.textTea()")
    public void after(){
        System.out.println("我是后置通知:下面是老师");
    }
    /**
     * 作用:声明在环绕通知方法上
     * 内容:切点的全限定路径
     */
    @Around("com.bjsxt.pojo.Teacher.textTea()")
    public Object around(ProceedingJoinPoint p){
        System.out.println("环绕通知--前");
        Object proceed = null;
        try {
            proceed = p.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("环绕通知--后");
        return proceed;
    }
    /**
     * 作用:声明在异常通知方法上
     * 内容:切点的全限定路径
     */
    @AfterThrowing("com.bjsxt.pojo.Teacher.testTea()")
    public void afterThrowing(){
        System.out.println("我是异常通知:");
    }
}

 

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值