springboot-AOP的介绍

本文介绍了SpringBoot中AOP的实现,包括@Before、@AfterThrowing、@AfterReturning和@After等通知类型的使用,并提供了详细的代码示例,展示了如何在方法执行前后进行拦截操作。同时解释了AOP的相关概念,如切面、连接点和通知。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      上一节中,因为反欺诈软件接口是通过aop的形式引入的,所以接下来介绍下aop的实现。其实再springboot中的实现aop是很简单的,仅仅需要知道几个注解就可以了。

      准备工作:首先要引入springboot的基础包和aop对应的包。如下:

	<dependencies>
		<!-- 引入springboot的基础配置,这里的容器使用undertow,不适用tomcat -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<!-- 移除自带的tomcat -->>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-tomcat</artifactId>
				</exclusion>
			</exclusions>
			<version>1.4.0.RELEASE</version>
		</dependency>
        <!-- 引入undertow -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-undertow</artifactId>
			<version>1.4.0.RELEASE</version>
		</dependency>

		<!--这个是aop需要引入jar包 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
			<version>1.4.0.RELEASE</version>
		</dependency>
	</dependencies>

  上述的undertow和tomcat一样,这里我会使用,是因为公司在用undertow,听公司大佬说某些场景下,这个性能要好。

然后创建以下的包结构:

  AopSpringApplication是springboot的启动类:代码很简单:


 @SpringBootApplication
 public class AopSpringApplication {
   
    public static void main(String[] args) {
        SpringApplication.run(AopSpringApplication.class, args);
    }
 }

 

1、@Before

对应的controller也如下:

@Controller
@RequestMapping("/v1")
public class AopController {
    //这个是springboot自带的日志打印类,这个很好用,建议使用
    //前段时间,公司日志的管理就是用logback管理的,提供按大小
    //切割和按天回滚各种功能。
	Logger logger = LoggerFactory.getLogger(this.getClass());
    @RequestMapping("/before")
	public ResponseEntity<?> aopBefore(@RequestParam String songName){
    	logger.info(String.format("这是输入参数%s", songName));
		return new ResponseEntity(null,HttpStatus.OK);
	}
}

对应的aop的前置通知如下:

package itouchtv.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
 * @author created by chenjianhui 2018/11/22
 * 这两个不陌生吧,刚学spring的时候,总会讲,这两个是spring的
 * 核心部分,一个是Aop和一个是Ioc,Aop表示切面,Ioc是依赖注入。
 */
@Aspect//声明为一个切面
@Component//注入到spring容器中
public class AopAspect {
	private final static int FIRST_ARG = 0;//第一个参数
	private final static int SENCOND_ARG = 1;//第二个参数
	Logger logger = LoggerFactory.getLogger(this.getClass());
    @Before("execution( * itouchtv.controller.v*.AopController.aopBefore(..))")
	public void beforeTest(JoinPoint join) {
		String songName =  (String)join.getArgs()[FIRST_ARG];
		logger.info(String.format("这是前置通知拦截的地方,拦截第一个参数第一个参数%s", songName));
	}
}

然后在启动类中,右键run as  - java application 就行了可以启动运用。在浏览器中输入:

http://localhost:8080/v1/before?songName=搁浅

就可以看到控制台中输出以下结果:

2018-11-22 22:38:56.501  INFO 7032 --- [  XNIO-2 task-1] itouchtv.aspect.AopAspect                : 这是前置通知拦截的地方,拦截第一个参数第一个参数搁浅
2018-11-22 22:38:56.510  INFO 7032 --- [  XNIO-2 task-1] itouchtv.controller.v1.AopController     : 这是输入参数搁浅

前面的是logback默认的日志格式。这可以看出前置通知起作用,也就是在调用实际接口的方法前,调用了切莫定义的那个方法。beforeTest(),同时还可以拿到实际调用函数的输入参数。

* itouchtv.controller.v*.AopController.aopBefore(..)

前面的一个*号代表任意返回值和访问类型,中间的*代表的是任意版本(公司里经常升级接口,需要个版本控制)。后面的两岸个点代表任意参数(注意不是两个任意参数,是任意参数,就是说,可以1个、2个。。。。任意个)

 

现在我在controller中加入一段代码异常代码:

@Controller
@RequestMapping("/v1")
public class AopController {
	Logger logger = LoggerFactory.getLogger(this.getClass());
    @RequestMapping("/before")
	public ResponseEntity<?> aopBefore(@RequestParam String songName){
    	int i = 10;
    	i = i/0;
    	logger.info(String.format("这是输入参数%s", songName));
		return new ResponseEntity(null,HttpStatus.OK);
	}
}

然后继续执行,此时的输出结果为:

2018-11-22 22:45:51.879  INFO 14144 --- [  XNIO-2 task-1] itouchtv.aspect.AopAspect                : 这是前置通知拦截的地方,拦截第一个参数第一个参数搁浅
2018-11-22 22:45:51.892 ERROR 14144 --- [  XNIO-2 task-1] io.undertow.request                      : UT005023: Exception handling request to /v1/before

尽管方法已经异常了,但是前置通知还是顺利执行了,没有任何影响,而且参数也可以拿到。

 

2、@AfterThrowing

异常通知主要是调用的方法出现异常的时候会触发。

实际调用的代码:

package itouchtv.controller.v1;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/v1")
public class AopController {
	Logger logger = LoggerFactory.getLogger(this.getClass());

	@RequestMapping(value = "/aopAfterThrowing",method = RequestMethod.GET)
	public ResponseEntity<?> aopAfterThrowing(){
		int i = 10;
		i = i/0;
		return new ResponseEntity(null,HttpStatus.OK);
	}
}

切面信息:

package itouchtv.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
 * @author created by chenjianhui 2018/11/22
 * 这两个不陌生吧,刚学spring的时候,总会讲,这两个是spring的
 * 核心部分,一个是Aop和一个是Ioc,Aop表示切面,Ioc是依赖注入。
 */
@Aspect//声明为一个切面
@Component//注入到spring容器中
public class AopAspect {
	Logger logger = LoggerFactory.getLogger(this.getClass());
   

	@AfterThrowing(value = "execution( * itouchtv.controller.v*.AopController.aopAfterThrowing(..))",throwing = "ex")
	public void throwTest(Exception ex) {
		logger.info(String.format("这是异常通知拦截的地方,出现的异常是:", ex));
	}
}

然后在浏览器中访问:

http://localhost:8080/v1/aopAfterThrowing

便会在控制台打印以下信息:

2018-11-28 23:56:00.882  INFO 25708 --- [  XNIO-2 task-3] itouchtv.aspect.AopAspect                : 这是异常通知拦截的地方,出现的异常是:
2018-11-28 23:56:00.884 ERROR 25708 --- [  XNIO-2 task-3] io.undertow.request                      : UT005023: Exception handling request to /v1/aopAfterThrowing

 

这里由于比较少介绍就顺便介绍下aop相关的几个概念:

上述标记有@Aspect这个注解的类就是一个切面,切面里面有连接点、通知等概念

连接点:excution对应的路径就是一个连接点:

* itouchtv.controller.v*.AopController.aopAfterThrowing(..)

第一个*代表任意公共类型和返回值,第二个*表示以v开头的字符,后面两个点表示任意参数。

通知在spring中,叫advice,有多种,这里介绍的是其中一种,即:@AfterThrowing,叫做后置异常通知。

上一节中的是前置通知@Before。(后面还有@AfterReturning,@After,@Around三个通知)

 

3、@AfterReturning

这个是后置返回通知,在发生异常的时候是不执行的。(奇怪,不是finally中吗?应该执行啊,不过我测试了,确实是不执行的)

具体代码如下:

package itouchtv.controller.v1;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/v1")
public class AopController {
	Logger logger = LoggerFactory.getLogger(this.getClass());
	@RequestMapping("/aopAfterReturn")
	public ResponseEntity<?> aopAfterReturn(@RequestParam String songName){

		return new ResponseEntity(songName,HttpStatus.OK);
	}
	
}
package itouchtv.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
/**
 * @author created by chenjianhui 2018/11/22
 * 这两个不陌生吧,刚学spring的时候,总会讲,这两个是spring的
 * 核心部分,一个是Aop和一个是Ioc,Aop表示切面,Ioc是依赖注入。
 */
@Aspect//声明为一个切面
@Component//注入到spring容器中
public class AopAspect {
	private final static int FIRST_ARG = 0;//第一个参数
	private final static int SENCOND_ARG = 1;//第二个参数
	Logger logger = LoggerFactory.getLogger(this.getClass());

	@AfterReturning(value = "execution( * itouchtv.controller.v*.AopController.aopAfterReturn(..))",returning = "result")
	public String afterReturnTest(JoinPoint join,Object result) {
		String songName =  (String)join.getArgs()[FIRST_ARG];
		logger.info(String.format("这是后置返回通知拦截的地方,拦截第一个参数第一个参数%s", result));
        ResponseEntity responseEntity = (ResponseEntity) result;
		logger.info("这个是返回的状态"+responseEntity.getStatusCodeValue());
		logger.info("这个是返回的结果"+(String) responseEntity.getBody());
		return songName;//返回结果是没有用的,因为方法已经返回结果了才调用这个方法,所以这个方法可以拿到结果,却无法改变结果。
	}
}

返回的结果:

2018-11-29 22:24:23.243  INFO 30644 --- [  XNIO-2 task-1] itouchtv.aspect.AopAspect                : 这是后置返回通知拦截的地方,拦截第一个参数第一个参数<200 OK,搁浅,{}>
2018-11-29 22:24:23.243  INFO 30644 --- [  XNIO-2 task-1] itouchtv.aspect.AopAspect                : 这个是返回的状态200
2018-11-29 22:24:23.243  INFO 30644 --- [  XNIO-2 task-1] itouchtv.aspect.AopAspect                : 这个是返回的结果搁浅

看到我将result强制成了ResponseEntity了吗?

其实我是debug后才发现的,如下图:

同样的,看到我将body转成了String了吗,因为它本来就是String类型,也就是我controller返回的是什么就可以转成什么。

最后还要强调一下:方法发生异常时是无法拦截到的;还有就是无法改变返回的结果(网上有些说可以,我不太清楚,反正我是修改不成功,用@Around就可以修改)。

 

4、@After

这个是后置最终通知,这个即使是方法发生异常,也是可以拦截的,代码如下:

package itouchtv.controller.v1;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/v1")
public class AopController {
	Logger logger = LoggerFactory.getLogger(this.getClass());
	@RequestMapping("/after")
	public ResponseEntity<?> aopAfter(@RequestParam String songName){
		int i = 10;
		i = i/0;
		logger.info(String.format("这是输入参数%s", songName));
		return new ResponseEntity(null,HttpStatus.OK);
	}

}
package itouchtv.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
/**
 * @author created by chenjianhui 2018/11/22
 * 这两个不陌生吧,刚学spring的时候,总会讲,这两个是spring的
 * 核心部分,一个是Aop和一个是Ioc,Aop表示切面,Ioc是依赖注入。
 */
@Aspect//声明为一个切面
@Component//注入到spring容器中
public class AopAspect {
	private final static int FIRST_ARG = 0;//第一个参数
	private final static int SENCOND_ARG = 1;//第二个参数
	Logger logger = LoggerFactory.getLogger(this.getClass());

	@After("execution( * itouchtv.controller.v*.AopController.aopAfter(..))")
	public void afterTest(JoinPoint join) {
		String songName =  (String)join.getArgs()[FIRST_ARG];
		logger.info(String.format("这是后置通知拦截的地方,拦截第一个参数第一个参数%s", songName));
	}
}

返回的结果为:

2018-11-29 22:37:22.741  INFO 30644 --- [  XNIO-2 task-3] itouchtv.aspect.AopAspect                : 这是后置通知拦截的地方,拦截第一个参数第一个参数搁浅
2018-11-29 22:37:22.751 ERROR 30644 --- [  XNIO-2 task-3] io.undertow.request                      : UT005023: Exception handling request to /v1/after

这个同样是可以执行获取参数的操作的,方式都一样,这几个通知。

 

5、@Around

这个叫环绕通知,比较强大,可以修改返回的结果,如果要对结果做增强处理,建议使用。

这个通知的使用参数也不太一样,上面的获取参数都是使用JoinPoint连接点。这个是使用

package itouchtv.controller.v1;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/v1")
public class AopController {
	Logger logger = LoggerFactory.getLogger(this.getClass());

	@RequestMapping(value = "/aopAround",method = RequestMethod.GET)
	public ResponseEntity<?> aopAround(@RequestParam String songName){

		return new ResponseEntity(songName,HttpStatus.OK);
	}
}

 

package itouchtv.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
/**
 * @author created by chenjianhui 2018/11/22
 * 这两个不陌生吧,刚学spring的时候,总会讲,这两个是spring的
 * 核心部分,一个是Aop和一个是Ioc,Aop表示切面,Ioc是依赖注入。
 */
@Aspect//声明为一个切面
@Component//注入到spring容器中
public class AopAspect {
	private final static int FIRST_ARG = 0;//第一个参数
	private final static int SENCOND_ARG = 1;//第二个参数
	Logger logger = LoggerFactory.getLogger(this.getClass());

	@Around(value = "execution( * itouchtv.controller.v*.AopController.aopAround(..))")
	public Object aroundTest(ProceedingJoinPoint proceedingJoinPoint) throws  Throwable {
    	//proceed()方法,其实会去执行目标方法,就是我这里控制器对对应的aopAround方法
		//这里可能会出现异常的情况,其实这个结果跟动态代理是一样的。
		Object result = null;
		try{
			logger.info("输入参数为:"+proceedingJoinPoint.getArgs()[0]+" 调用方法前,可以做其他事情,比如我要修改参数:");
			Object[] args = proceedingJoinPoint.getArgs();
			args[0] = "蒲公英的约定";
			result  = proceedingJoinPoint.proceed(args);
			logger.info("返回的结果为:"+result+" 调用方法后,且不发生异常时,可以做其他事情,包括改变结果");
			result = new ResponseEntity("夜的第七章", HttpStatus.OK);
		}catch (Exception e){
            logger.info("这里出现异常了",e);
		}finally {
			logger.info("不管异常与否,都可以做一些事情");
		}
		return result;
	}
}

输出结果:

2018-11-29 23:20:34.085  INFO 23952 --- [  XNIO-2 task-1] itouchtv.aspect.AopAspect                : 输入参数为:搁浅 调用方法前,可以做其他事情,比如我要修改参数:
2018-11-29 23:20:34.093  INFO 23952 --- [  XNIO-2 task-1] itouchtv.aspect.AopAspect                : 返回的结果为:<200 OK,蒲公英的约定,{}> 调用方法后,且不发生异常时,可以做其他事情,包括改变结果
2018-11-29 23:20:34.093  INFO 23952 --- [  XNIO-2 task-1] itouchtv.aspect.AopAspect                : 不管异常与否,都可以做一些事情

这里可以实现修改参数,比如一开始是搁浅输入进来,然后别修改成蒲公英的约定,proceed(args)完后,先返回<200 OK,蒲公英的约定,{}> ,所以可以知道,参数被修改了。然后修改结果,从浏览器返回的结果可以看出:

结果被修改了。Around够强大吧,你还可以进行异常的处理,finally的关闭,除了复杂点外,还可以跟动态代理结构对照学习。

实在是想不到有什么理由不学习环绕通知。

 

最后讲些抽象的概念:

  切面:就是被@Aspect标记的类(这个类里面有切入点啊,连接点啊等等)

  目标对象:实际要执行的那个方法的对象。看看我前面的动态代理,就是那个Object对象

  代理对象:代理类的对象。就是加了功能,通过调用自己的方法来实现调用目标方法的对象。也就是动态代理中Proxy通过newInstance方法创建出来的对象。

  连接点:对应被执行的目标方法(实际执行的方法,我这是controller中的方法)

  切入点:连接点集合,其实就是pointcut,只不过我这里没有使用而已。看下代码:

@Pointcut("execution( * itouchtv.controller.v*.AopController.*(..))")
	public void myPointCut(){}
	
    @Before("myPointCut()")
	public void test1(JoinPoint join) {
		String songName =  (String)join.getArgs()[FIRST_ARG];
		logger.info(String.format("这是前置通知拦截的地方,拦截第一个参数第一个参数%s", songName));
	}

	@After("myPointCut()")
	public void test2(JoinPoint join) {
		String songName =  (String)join.getArgs()[FIRST_ARG];
		logger.info(String.format("这是后置通知拦截的地方,拦截第一个参数第一个参数%s", songName));
	}

用@PointCut定义了一个切入点,然后就可以代表原来的那些连接点来使用,这里知道为什么是连接点的集合了吧。

当然,你也可以写两个连接点,不过PointCut这样可以省些代码罗,但它不是必须的。

织入:这个其实是目标对象生产代理对象的过程,也就是将需要添加的功能和实际的功能,一起放入到代理对象中,就是个织人的过程。

通知:就是上述的各种通知了,英文是Advice。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值