在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...");
}
}