Spring框架学习

文章40 |   阅读 19328 |   点赞0

来源:https://blog.csdn.net/yerenyuan_pku/category_6457009.html

Spring入门第七讲——Spring AOP的注解开发

x33g5p2x  于2021-12-18 转载在 其他  
字(12.9k)|赞(0)|评价(0)|浏览(503)

在前一讲中,我已讲过Spring使用基于AspectJ的XML配置文件的方式进行AOP开发,现在我就来讲讲怎样使用基于AspectJ的注解的方式进行AOP开发。

使用基于AspectJ的注解的方式进行AOP开发

创建web项目,引入jar包

首先创建一个动态web项目,例如spring_demo03_aop,然后导入Spring框架相关依赖jar包,要导入哪些jar包呢?这里不废话,直接给出要导入的jar包。

引入相关的配置文件

首先,引入Spring的配置文件,在该文件中应引入aop约束,这样一开始applicationContext.xml文件的内容就应该是下面的样子。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

</beans>

然后,还要记得在src目录下引入Log4j的配置文件(log4j.properties),也就是日志记录文件,该文件内容如下:

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c\:mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###
# error warn info debug trace
log4j.rootLogger= info, stdout

编写目标类并配置

首先,在src目录下创建一个com.meimeixia.spring.demo03包,并在该包下创建一个名为OrderDao的类。

package com.meimeixia.spring.demo01;

public class OrderDao {
	
	public void save() {
		System.out.println("保存订单...");
	}
	
	public void update() {
		System.out.println("修改订单...");
	}
	
	public void delete() {
		System.out.println("删除订单...");
	}
	
	public void find() {
		System.out.println("查询订单...");
	}
	
}

然后,在Spring配置文件中对以上目标类进行配置。

编写切面类并配置

现在有这样一个需求:我们想在OrderDao类的save方法执行之前,就简简单单地执行一个操作,那该咋怎呢?这时我们可以编写一个切面类,并在切面类中随便编写一个方法,例如下面的before方法。待会,我们就使用注解对该切面类进行增强,让切面类中的before方法在目标类的save方法执行之前执行。

package com.meimeixia.spring.demo01;

public class MyAspectAnno {

	public void before() {
		System.out.println("前置增强--------------------");
	}
	
}

接着,在Spring配置文件中对以上切面类进行配置。

使用注解对目标类进行增强

首先,我们需要在Spring的配置文件中开启注解的AOP开发。

然后,我们就可以在切面类上使用注解了。

package com.meimeixia.spring.demo01;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/** * 切面类(注解的切面类) * @author liayun * */
@Aspect //Spring会把这个类识别成一个切面
public class MyAspectAnno {

	//@Before注解代表前置增强,还要告诉Spring在哪个类上的哪个方法上应用前置增强
	@Before(value="execution(* com.meimeixia.spring.demo01.OrderDao.save(..))")
	public void before() {
		System.out.println("前置增强--------------------");
	}
	
}

温馨提示:@Aspect注解用于定义切面类,@Before注解代表的是前置通知。

编写测试类并进行测试

首先,在com.meimeixia.spring.demo01包下创建一个SpringDemo01的单元测试类,其内容如下,可以看出我们是通过Spring来整合JUnit进行单元测试的。

package com.meimeixia.spring.demo01;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/** * Spring的AOP的注解开发 * @author liayun * */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo01 {
	
	@Resource(name="orderDao")
	private OrderDao orderDao;

	@Test
	public void demo01() {
		orderDao.save();//作断点调试,可以看到使用到的是Cglib的动态代理:CglibAopProxy
		orderDao.update();
		orderDao.delete();
		orderDao.find();
	}
	
}

然后,运行以上demo01单元测试方法,Eclipse控制台就会打印出如下内容。

Spring AOP注解开发中的通知类型

Spring AOP注解开发入门之后,咱就来看一下AOP注解开发中的通知类型。

前置通知

前置通知是指在目标方法执行之前进行操作。上面我演示的就是前置通知,这里不再赘述。

后置通知

后置通知是指在目标方法执行之后进行操作。它除了可以获得切入点的信息以外,还可以获得方法的返回值。为了验证这一点,首先,将OrderDao类修改成下面这个样子(主要是修改了一下delete方法,让其返回一个字符串)。

package com.meimeixia.spring.demo01;

public class OrderDao {
	
	public void save() {
		System.out.println("保存订单...");
	}
	
	public void update() {
		System.out.println("修改订单...");
	}
	
	public String delete() {
		System.out.println("删除订单...");
		return "删除订单成功";
	}
	
	public void find() {
		System.out.println("查询订单...");
	}
	
}

然后,在MyAspectAnno切面类中添加一个afterReturning方法,并在该方法上使用@AfterReturning注解,告诉Spring要在OrderDao类中的delete方法上应用后置通知。

package com.meimeixia.spring.demo01;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/** * 切面类(注解的切面类) * @author liayun * */
@Aspect //Spring会把这个类识别成一个切面
public class MyAspectAnno {

	//@Before注解代表前置增强,还要告诉Spring在哪个类上的哪个方法上应用前置增强
	@Before(value="execution(* com.meimeixia.spring.demo01.OrderDao.save(..))")
// @Before(value="MyAspectAnno.pointcut2()")
	public void before() {
		System.out.println("前置增强--------------------");
	}
	
	//后置通知
	@AfterReturning(value="execution(* com.meimeixia.spring.demo01.OrderDao.delete(..))", returning="result")
	public void afterReturning(Object result) {//这儿的result一定得跟returning="result"中的result对应上
		System.out.println("后置增强--------------------" + result);
	}
	
}

最后,运行SpringDemo01单元测试类中的demo01方法,你就会看到Eclipse控制台打印出了如下内容。

环绕通知

环绕通知是功能最强的一个通知,它是指在目标方法执行之前和之后进行操作。很重要的一点就是它可以阻止目标方法的执行。为了验证这一点,我们可以在MyAspectAnno切面类中添加一个如下的around方法,并在该方法上使用@Around注解,告诉Spring要在OrderDao类中的update方法上应用环绕通知。

package com.meimeixia.spring.demo01;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/** * 切面类(注解的切面类) * @author liayun * */
@Aspect //Spring会把这个类识别成一个切面
public class MyAspectAnno {

	//@Before注解代表前置增强,还要告诉Spring在哪个类上的哪个方法上应用前置增强
	@Before(value="execution(* com.meimeixia.spring.demo01.OrderDao.save(..))")
	public void before() {
		System.out.println("前置增强--------------------");
	}
	
	//后置通知
	@AfterReturning(value="execution(* com.meimeixia.spring.demo01.OrderDao.delete(..))", returning="result")
	public void afterReturning(Object result) {//这儿的result一定得跟returning="result"中的result对应上
		System.out.println("后置增强--------------------" + result);
	}
	
	//环绕通知
	@Around(value="execution(* com.meimeixia.spring.demo01.OrderDao.update(..))")
	public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
		System.out.println("环绕前增强--------------------");
		Object obj = joinPoint.proceed();//执行我们的目标方法,它会返回一个Object
		System.out.println("环绕后增强--------------------");
		return obj;
	}
	
}

接着,运行SpringDemo01单元测试类中的demo01方法,你就会看到Eclipse控制台打印出了如下内容。

异常抛出通知

异常抛出通知是指在程序出现异常的时候而进行的操作,而且它还可以获得异常的信息。异常抛出通知能想到的一个应用场景就是在事务管理的时候会用到。为了验证这一点,首先,修改一下OrderDao类中的find方法,使其抛出一个除零异常。

package com.meimeixia.spring.demo01;

public class OrderDao {
	
	public void save() {
		System.out.println("保存订单...");
	}
	
	public void update() {
		System.out.println("修改订单...");
	}
	
	public String delete() {
		System.out.println("删除订单...");
		return "删除订单成功";
	}
	
	public void find() {
		System.out.println("查询订单...");
		int i = 1 / 0;
	}
	
}

然后,在MyAspectAnno切面类中添加如下一个afterThrowing方法,并在该方法上使用@AfterThrowing注解,告诉Spring要在OrderDao类中的find方法上应用异常抛出通知。

package com.meimeixia.spring.demo01;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/** * 切面类(注解的切面类) * @author liayun * */
@Aspect //Spring会把这个类识别成一个切面
public class MyAspectAnno {

	//@Before注解代表前置增强,还要告诉Spring在哪个类上的哪个方法上应用前置增强
	@Before(value="execution(* com.meimeixia.spring.demo01.OrderDao.save(..))")
	public void before() {
		System.out.println("前置增强--------------------");
	}
	
	//后置通知
	@AfterReturning(value="execution(* com.meimeixia.spring.demo01.OrderDao.delete(..))", returning="result")
	public void afterReturning(Object result) {//这儿的result一定得跟returning="result"中的result对应上
		System.out.println("后置增强--------------------" + result);
	}
	
	//环绕通知
	@Around(value="execution(* com.meimeixia.spring.demo01.OrderDao.update(..))")
	public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
		System.out.println("环绕前增强--------------------");
		Object obj = joinPoint.proceed();//执行我们的目标方法,它会返回一个Object
		System.out.println("环绕后增强--------------------");
		return obj;
	}
	
	//异常抛出通知
	@AfterThrowing(value="execution(* com.meimeixia.spring.demo01.OrderDao.find(..))", throwing="e")
	public void afterThrowing(Throwable e) {//这儿的e一定得跟throwing="e"中的e对应上
		System.out.println("异常抛出增强--------------------" + e.getMessage());
	}
	
}

最后,运行SpringDemo01单元测试类中的demo01方法,你就会看到Eclipse控制台打印出了如下内容。

最终通知

无论目标方法是否出现异常,最终通知都会执行。此时,我们已经知道了OrderDao类中的find方法(也即目标方法)抛出了一个除零异常。现在,咱就是要看看find方法(也即目标方法)出现了异常,最终通知会不会执行。为了验证这一点,我们在MyAspectAnno切面类中添加如下的一个after方法,并在该方法上使用@After注解,告诉Spring要在OrderDao类中的find方法上应用最终通知。

package com.meimeixia.spring.demo01;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/** * 切面类(注解的切面类) * @author liayun * */
@Aspect //Spring会把这个类识别成一个切面
public class MyAspectAnno {

	//@Before注解代表前置增强,还要告诉Spring在哪个类上的哪个方法上应用前置增强
	@Before(value="execution(* com.meimeixia.spring.demo01.OrderDao.save(..))")
	public void before() {
		System.out.println("前置增强--------------------");
	}
	
	//后置通知
	@AfterReturning(value="execution(* com.meimeixia.spring.demo01.OrderDao.delete(..))", returning="result")
	public void afterReturning(Object result) {//这儿的result一定得跟returning="result"中的result对应上
		System.out.println("后置增强--------------------" + result);
	}
	
	//环绕通知
	@Around(value="execution(* com.meimeixia.spring.demo01.OrderDao.update(..))")
	public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
		System.out.println("环绕前增强--------------------");
		Object obj = joinPoint.proceed();//执行我们的目标方法,它会返回一个Object
		System.out.println("环绕后增强--------------------");
		return obj;
	}
	
	//异常抛出通知
	@AfterThrowing(value="execution(* com.meimeixia.spring.demo01.OrderDao.find(..))", throwing="e")
	public void afterThrowing(Throwable e) {//这儿的e一定得跟throwing="e"中的e对应上
		System.out.println("异常抛出增强--------------------" + e.getMessage());
	}
	
	//最终通知
	@After(value="execution(* com.meimeixia.spring.demo01.OrderDao.find(..))")
	public void after() {
		System.out.println("最终增强--------------------");
	}
	
}

接着,运行SpringDemo01单元测试类中的demo01方法,你就会看到Eclipse控制台打印出了如下内容。

从上面输出的结果中,我们就证明了即使目标方法出现了异常,最终通知也会执行。

Spring AOP注解开发中的切入点的配置

假设切面类里面有很多种通知,但它们都需要作用于目标类的同一个方法上,比如说find方法,假如说我有一天不想作用在find方法上了,而想作用在save方法或者update方法上,那该咋办呢?这时,我们得到切面类里面一个一个改,如果切面类这里面的通知很多,那么我们是不是得改好几个啊!这势必会很麻烦!为了解决这种问题,咱可以使用@Pointcut注解来定义切入点。例如,咱可以在MyAspectAnno切面类里面定义如下一些切入点。

那么,我们怎么把之前写好的各种类型的通知应用在这些切入点上呢?也就是说MyAspectAnno切面类定义了以上一系列切入点之后,可以修改成下面这个样子。

package com.meimeixia.spring.demo01;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

/** * 切面类(注解的切面类) * @author liayun * */
@Aspect //Spring会把这个类识别成一个切面
public class MyAspectAnno {

	//@Before注解代表前置增强,还要告诉Spring在哪个类上的哪个方法上应用前置增强
	@Before(value="MyAspectAnno.pointcut2()")
	public void before() {
		System.out.println("前置增强--------------------");
	}
	
	//后置通知
	@AfterReturning(value="MyAspectAnno.pointcut4()", returning="result")
	public void afterReturning(Object result) {//这儿的result一定得跟returning="result"中的result对应上
		System.out.println("后置增强--------------------" + result);
	}
	
	//环绕通知
	@Around(value="MyAspectAnno.pointcut3()")
	public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
		System.out.println("环绕前增强--------------------");
		Object obj = joinPoint.proceed();//执行我们的目标方法,它会返回一个Object
		System.out.println("环绕后增强--------------------");
		return obj;
	}
	
	//异常抛出通知
	@AfterThrowing(value="MyAspectAnno.pointcut1()", throwing="e")
	public void afterThrowing(Throwable e) {//这儿的e一定得跟throwing="e"中的e对应上
		System.out.println("异常抛出增强--------------------" + e.getMessage());
	}
	
	//最终通知
	@After(value="MyAspectAnno.pointcut1()")
	public void after() {
		System.out.println("最终增强--------------------");
	}
	
	/* * 这里面有一个问题:假设我这里面有很多种通知,但都需要作用于同一个方法上,比如说find()方法,假如说我有一天不想作用在find()方法上了, * 我想作用在save()方法或者update()方法上,咋办呢?你是不是得到这边一个一个改啊!那如果我这里面的通知很多,但都 * 作用于同一个方法上,你是不是得改好几个啊?很麻烦! * */
	//切入点的注解
	@Pointcut(value="execution(* com.meimeixia.spring.demo01.OrderDao.find(..))")
	private void pointcut1() {}//该方法没有特殊的意义,私有就行
	@Pointcut(value="execution(* com.meimeixia.spring.demo01.OrderDao.save(..))")
	private void pointcut2() {}
	@Pointcut(value="execution(* com.meimeixia.spring.demo01.OrderDao.update(..))")
	private void pointcut3() {}
	@Pointcut(value="execution(* com.meimeixia.spring.demo01.OrderDao.delete(..))")
	private void pointcut4() {}
	
}

相关文章