SpringBoot实现AOP 简单测试切面选择不同连接点

在AOP中我们使用切点表达式来定义一个切点,他们通常有如下类型

匹配注解 @target限制连接点匹配特定的执行对象,这些对象对应的类要有具体指定类型的注解
匹配注解 @args限制连接点匹配参数为指定注解类型的执行方法
匹配注解 @within限制连接点匹配指定注解所标注的类型
匹配注解 @annotation限制连接点匹配方法上的注解
匹配包或类型 within限制连接点匹配指定的表或类
匹配对象 this限制连接点匹配AOP代理的bean引用为指定类型的类
匹配对象 bean限制连接点匹配一个指定的bean,需要指出bean的名字
匹配对象 target限制连接点匹配目标对象为指定类型的类
匹配参数 args限制连接点匹配参数为指定类型的执行方法
匹配方法 execution()匹配连接点的执行方法

基于几种常用的匹配方式对他们做一个简单的演示

首先我们编写演示demo所需要的基本功能建立我们的maven工程


加入Springboot和aop的相关的pom依赖

这里说明一下:

aop_annotation包存储所有的自定义注解。

aop_aspect存储自定义的切面

login是我们的业务包,用来编写业务实现

AopApplication是我们的测试启动类

两个页面login和main分别代表登录页和主页。

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
</dependencies>

编写基本的登陆Controller

package aop_manage.login;

import aop_manage.aop_annotation.VerifyLogin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@RequestMapping("login")
@Controller
public class LoginController {
	private static final Logger logger = LoggerFactory.getLogger(LoginController.class);
	private static boolean login = false;

    /**
     * 登陆页面
     */

    @RequestMapping("/loginPage")
    public String loginPage() throws Exception {
        return "login";
    }
    
    /**
     * 访问主页
     */
    @RequestMapping("/doMainIndex")
    public String doMainIndex() throws Exception {
        return "main";
    }
}

下面我们尝试第一种简单的aop实现

一、within作为连接点

使用wihin他可以匹配一个包下的所有类或一个具体的类,使用的时候很简单,只需要增加注解

@Pointcut("within(aop_manage.login.LoginController)") //具体的一个类

或 @Pointcut("within(aop_manage.login.*)")   //通配符匹配的包下所有类

@Aspect
@Component
public class LoginAspectByWithin {
    @Pointcut("within(aop_manage.login.LoginController)")
    public void verifyLoginByWithin(){}

    @Before("verifyLoginByWithin()")
    public void beforeVerifyLoginByWithin(){
        System.out.println("执行校验登陆状态。。。");
    }
}

二、 Bean对象名作为连接点

使用bean的名称作为一个切点,然后织入我们的逻辑,使用的时候也很简单,只需要修改注解为

@Pointcut("bean(loginController)") //这里loginController为LoginController的beanname。他会拦截这个bean的全部方法

@Aspect
@Component
public class LoginAspectByBean {
    @Pointcut("bean(loginController)")
    public void verifyLoginByBean(){}

    @Before("verifyLoginByBean()")
    public void beforeVerifyLoginByBean(){
        System.out.println("执行校验登陆状态。。。");
    }
}

控制台日志不再赘述

三、this和target匹配连接点

使用this或target作为匹配对象的规则时,一般情况下他们是等效的,例如我们增加一点业务场景,编写一个商品服务类,增加新增商品和销售商品的方法。

/**
 * @program: aop_manage.login
 * @description:
 * @author: liujinghui
 * @create: 2019-02-10 16:18
 **/
@Service
public class ProductManage  {
    public void add() {
        System.out.println("添加商品");
    }
    public void sale() {
        System.out.println("卖出商品");
    }
}

之后编写切面

@Aspect
@Component
public class LoginAspectByThis {
    @Pointcut("this(aop_manage.login.ProductManage)")
    public void verifyLoginByThis(){}

    @Before("verifyLoginByThis()")
    public void beforeVerifyLoginByThis(){
        System.out.println("this...");
    }
}

这个时候运行程序,我们发现ProductManage所有方法都进行了增强,调用ProductManage的任何方法。当我页面访问增加商品逻辑时,控制台打印如下

同理将上述ProductManage进行改造,让它实现自ProductManageInterface接口。同时我们将this改造成Target,我们看看效果

public interface ProductManageInterface {
    public void add();
    public void sale();
}

@Service
public class ProductManage implements ProductManageInterface {
    @Override
    public void add() {
        System.out.println("添加商品");
    }

    @Override
    public void sale() {
        System.out.println("卖出商品");
    }
}
@Aspect
@Component
public class LoginAspectByTarget {
    @Pointcut("target(aop_manage.login.ProductManageInterface)")
    public void verifyLoginByTarget(){}

    @Before("verifyLoginByTarget()")
    public void beforeVerifyLoginByTarget(){
        System.out.println("Target...");
    }
}

这次我们的切面中切点是一个接口,那么预想一下该接口中所有的方法都会被代理。

同理,交换一下位置的话他们的作用也是一样的。

四、@annotation匹配连接点

这个匹配方式是检查所有带有你规定注解的方法作为连接点。

定义一个注解类,比如我们要实现校验登陆状态的逻辑,那么我们可以定义一个注解,把它加载我们希望校验登陆状态的方法上。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface VerifyLogin {
}

继续改造我们的Controller,增加@VerifyLogin 注解

    /**
     * 访问主页
     */
    @VerifyLogin
    @RequestMapping("/doMainIndex")
    public String doMainIndex() throws Exception {
        productManage.add();
        productManage.sale();
        return "main";
    }

编写我们的切面 

    @Pointcut("@annotation(aop_manage.aop_annotation.VerifyLogin)")
    public void verifyLoginByAnnotation(){
        System.out.println("登陆校验切点");
    }
    /**
     * 你想要在什么时候植入 5种
     */
    @Before("verifyLoginByAnnotation()")
    public void beforeCheck(){
        System.out.println("使用注解方式校验方法执行校验登陆状态。。。");
    }

五、arg匹配连接点

arg就是参数匹配,当参数符合指定类型时去代理执行方法。继续改造我们的Controller,增加两个不同参数的方法,我们只对这个Controller的String类型的参数做匹配

    /**
     * 访问主页
     */
    @RequestMapping("/doMainIndex")
    public String doMainIndex(String id) throws Exception {
        System.out.println("执行访问主页方法"+id);
        return "main";
    }
    /**
     * 退出主页
     */
    @RequestMapping("/doQuit")
    public String doQuit(int id) throws Exception {
        System.out.println("执行退出主页方法"+id);
        return "main";
    }

编写切面类

@Aspect
@Component
public class LoginAspectByArg {
    @Pointcut("args(String) && within(aop_manage.login.LoginController)")
    public void verifyArgByArg(){}

    @Before("verifyArgByArg()")
    public void beforeVerifyArgByArg(){
        System.out.println("发现参数为String的类型。。。。");
    }
}

可以看到 args(String) && within(aop_manage.login.LoginController) 表示对第一个参数为String并且文件时LoginController的方法做织入。如果匹配多个参数可以用args(String,Long,Double)

所以当我分别在浏览器输入不同参数时,可以看到只有String参数的方法被增强。

六、execution匹配连接点

重点放在最后,execute作为使用最多的表达式,它的操作还是很灵活的。它的规则如下

execution(<可见性修饰符> <返回值类型> <方法全限定名>(<参数>) <异常>)  

其中可见性修饰符和异常不是必须的,其余是必须的。

先上一个例子@Pointcut("execution( * aop_manage.login.LoginController.doMainIndex(String))")

第一个*表示任意的返回值类型,然后我们定义了全限定类名和方法名,之后的String表示String参数,如果是(..)则表示任意参数

@Aspect
@Component
public class LoginAspectByExecution {
    @Pointcut("execution( public * aop_manage.login.LoginController.doMainIndex(Integer))")
    public void verifyLoginByExecution(){}

    @Before("verifyLoginByExecution()")
    public void beforeVerifyLoginByExecution(){
        System.out.println("LoginAspectByExecution...");
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值