Spring实战笔记三(aop)

一、什么是面向对象编程

        将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

二、AOP术语

        通知(Advice):切面的工作被称为通知。定义了切面是什么,何时使用。Spring切面有5中类型的通知(前before,后after,返回after-retuning,异常after-throwing,环绕around);

        连接点(Join Point):连接点,也就是可以进行横向切入的位置;

        切点(Poincut):通过匹配,确定切面在什么地方执行;

        切面(Aspect):切入系统的一个切面。比如事务管理是一个切面,权限管理也是一个切面;

        织入(Weaving):把切面应用到目标对象并创建代理对象的过程。可以在编译器、类加载期、运行期。(Spring AOP,就是在与运行期方式织入切面的)。

三、Spring AOP

        Spring AOP构建在动态代理(JDK动态代理【默认】,CGLib动态代理【没有接口时使用】)之上,因此,局限于方法拦截。

        切点用于准确定位在什么地方应用切面的通知,通知和切点是切面的基本元素

    1、SpringAOP所支持的AspectJ切点指示器Spring中使用execution指示器来进行实际匹配。还有一些指示器来限制匹配如下。

        48381d18d1889691ae28357968adbfa3408.jpg

    2、编写切点

        对于一个接口UserService:

public interface UserService {

    boolean add(User user);

}

    切点表达时可以表示如下

            61d7804a0a355a788d0e7a6fb3696241775.jpg

    3、注解创建切面

        ①使用@Aspect声明一个类为切面,@Before、@After、@AfterReturning、@AfterThrowing、@Around来声明通知方法,使用@Pointcut来统一命名切点表达式。如下所示:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect //声明一个切面
//@Component
public class UserServiceAop {

    //定义一个命名的切点(统一配置)
    @Pointcut(value = "execution(* com.caofanqi.service.UserService.add(..))")
    public void pointcut(){}

    //在执行方法之前执行,可以写,表达式,也可以直接使用pointcut
    @Before(value = "execution(* com.caofanqi.service.UserService.add(..))")
    public void beforeMethod() {
        System.out.println("before...");
    }

    //目标方法返回或抛出异常调用
    @After("pointcut()")
    public void afterMethod() {
        System.out.println("after...");
    }

    //目标方法返回后调用
    @AfterReturning("pointcut()")
    public void aterReturningMethod() {
        System.out.println("aterReturning...");
    }
    
    //目标方法出异常调用
    @AfterThrowing("pointcut()")
    public void afterThrowingMethod() {
        System.out.println("afterThrowing...");
    }

    
    //将目标方法封装(环绕)起来
    @Around("pointcut()")
    public boolean AroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("AroundBefore...");
        boolean proceed = (Boolean) joinPoint.proceed();
        System.out.println("AroundAfter...");
        return proceed;
    }
}

        ②启动AspectJ注解的自动代理

        JavaConfig方式:

@Configuration
@ComponentScan(basePackages = "com.caofanqi")
@PropertySource("classpath:app.properties")
@EnableAspectJAutoProxy //启动自动代理
public class JavaConfig {

    //声明切面bean
    @Bean
    public UserServiceAop userServiceAop(){
        return  new UserServiceAop();
    }
}

        如果使用XML来装配bean的话,需要使用Spring aop命名空间的<aop:aspectj-autoproxy /> 开启自动代理。

        AspectJ自动代理会为使用@Aspect注解的bean创建一个代理,这个代理会围绕着所有该切面的切点所匹配的bean。

4、  切面的执行顺序

        如果我们配置了多个切面,而且想要控制先后的执行顺序该怎么办呢?这里可以使用@Order注解指定切面顺序。

        我们添加一个LogAop,记录我们调用的方法,如下:

@Aspect
@Component
@Order(1) //切面的执行顺序,数字越小,环绕通知(前)、前置通知先执行,其他相反
public class LogAop {

    @Pointcut(value = "execution(* com.caofanqi.service.*.*(..))")
    public void logPointcut(){};


    @Before("logPointcut()")
    public void beforeMethod() {
        System.out.println("logAop before...");
    }

    @After("logPointcut()")
    public void afterMethod() {
        System.out.println("logAop after...");
    }

    @AfterReturning("logPointcut()")
    public void aterReturningMethod() {
        System.out.println("logAop aterReturning...");
    }

    @AfterThrowing("logPointcut()")
    public void afterThrowingMethod() {
        System.out.println("logAop afterThrowing...");
    }


    @Around("logPointcut()")
    public boolean AroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("logAop AroundBefore...");
        boolean proceed = (Boolean) joinPoint.proceed();
        System.out.println("logAop AroundAfter...");
        return proceed;
    }

}

    然后设置UserServiceAop如下:

        db27a478f2a8ee4c524aa11aabd0e7bed0f.jpg

这时我们测试add方法结果如下:

            6adcafe132bf1d055b8ff3961fdef1255cc.jpg

    确实是order越小越是最先执行,但更重要的是最先执行的最后结束。

    如果是配置文件的话,可以在  <aop:aspect ref="logAop" order="1">    上添加order属性。

  5、处理通知中的参数

       如果我们要在通知中使用被代理方法的参数,要怎么做呢?如下:

被代理方法:

        32c014795602edc420f2e9f9bd6a60b55f0.jpg

切面:

        1a00ac6d62afcaf8c797ce30f29157510ab.jpg

我们需要关注的是args(friendName)限定符。它表明传递给meetFriend()方法的String类型的参数也会传递到通知中去。参数的名称friendName也与切点方法签名中的参数相匹配。

    6、通过注解引入新的功能

        如果想在此目标类中再增加一个目标方法是,该怎么办呢? 
        最简单的办法就是在建立此目标类的时候,增加此方法。但是如果原目标类非常复杂,动一发而牵全身。我们可以为需要添加的方法建立一个类,然后建一个代理类,同时代理该类和目标类。用一个图来表示 
         a8b088bca4e8d1b6cc888de48cd91691c01.jpg
图中,A就是原目标类,B就是新添加的方法所在的类,通过建立一个代理类同时代理A和B,调用者调用该代理时,就可以同时A和B中的方法了。 通过@DeclareParents注解就可以实现该功能。

       拓展接口和实现类:

public interface PersonService {
    void show();
}

@Service
public class PersonServiceImpl implements PersonService {

    public void show() {
        System.out.println("person ...");
    }
}

        切面:

@Aspect
@Component
public class PersonInteroducer {

    @DeclareParents(value = "com.caofanqi.service.UserService+",defaultImpl = PersonServiceImpl.class)
    public static PersonService personService;

}

    @DeclareParents由三部分组成:value,指定了那种类型的bean要引入接口;+表示UserService所有子类型。defaultImpl指定为引入功能提供实现的类。静态属性表示要引入的接口。

    测试:

        c86e30d0e6566d5f8940e3003839aa4944c.jpg

 

使用注解和自动代理让我们创建切面很简单,但是必须有源码,如果没有源码,就要使用XML方式了。如果各位需要用到的话,可以自行百度啦。很简单

源码:https://gitee.com/itcaofanqi/CaoFanqiStudyRepository/tree/master/stuspring

转载于:https://my.oschina.net/caofanqi/blog/3008621

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值