(三)Spring学习笔记-AOP

一、AOP概念和术语

概念

AOP即为Aspect Oriented Programming的缩写,意为:面向切面编程。AOP是OOP(面向对象编程)的扩展和延伸,用于解决OOP开发遇到的问题。

AOP思想是最早由AOP联盟组织提出的,Spring是目前使用这种思想最好的框架。Spring的AOP有自己的实现方式。因其比较繁琐,所以Spring引入AspectJ(一个AOP框架)作为自身AOP的开发。

术语理解

为了方便理解,假设现在定义有如下类:

public class UserDao {
	public void save(){}
	public void find(){}
	public void update(){}
	public void delete(){}
}
  • Advice:增强,通知。即在方法层面上进行的增强。 比如,现在需要对save方法进行权限校验,而该权限校验的方法(checkPri)便是增强。
  • Joinpoint:连接点,即可以被拦截到的点。所有可以进行增强的方法,如UserDao中的增删改查方法都可以被称为连接点。
  • Pointcut:切入点,即真正被拦截到的点。在实际开发过程中,我们不一定要对所有的方法都进行增强,而是只对save方法进行增强,那么save方法则是一个切入点。
  • Introduction:引介。不同于Advice是在方法层面上的增强,Introduction是在类层面上的增强。比如现在需要对UserDao类通过动态代理的方式丰富UserDao的功能,这就是引介。
  • Target:被增强的对象。比如我要对UserDao进行增强,那么UserDao类称为是Target。
  • Weaving:织入。指的就是将增强(Advice)应用到目标(Target)的过程。比如我现在需要将权限校验的方法的代码应用到UserDao的save方法上的过程便是织入。
  • Proxy:代理。 就是一个类被AOP织入增强后产生的一个结果代理类。
  • Aspect:切面。 是多个切入点和多个通知或引介的结合。

补充:Spring的底层原理 使用到了JDK动态代理和CGLIB动态代理,具体参考Java之动态代理

二、AOP开发(XML)

1. 创建web项目,引入jar包

  • 引入基本开发包
    在这里插入图片描述
  • 引入aop开发的相关jar包
    在这里插入图片描述
    分别为AOP联盟相关Jar包、Aspectj包(因为我们要使用Aspectj框架开发)、AOP包和Spring与Apspectj的整合包。
  • 引入日志打印相关包
    在这里插入图片描述

2. 引入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"> 
            <!-- bean definitions here -->
    
    </beans>
    

注意: 可在spring-framework-4.2.4.RELEASE-dist\spring-framework-4.2.4.RELEASE\docs\spring-framework-reference\html\xsd-configuration.html找到。

3. 编写目标类并完成配置

首先定义ProductDao接口,代码如下:

public interface ProductDao {
	public void save();
	public void update();
	public void delete();
	public void find();
}

添加实现类ProductDaoImpl,具体代码如下:

public class ProductDaoImpl implements ProductDao {

	@Override
	public void save() {
		System.out.println("保存商品");
	}

	@Override
	public void update() {
		System.out.println("更新商品");
	}

	@Override
	public void delete() {
		System.out.println("删除商品");
	}

	@Override
	public void find() {
		System.out.println("查询商品");
	}

}

最后在applicationContext.xml进行如下配置:

 <!-- 配置目标对象,即被增强的对象-->
 <bean id="productDao" class="com.shoto.spring.xmldemo.ProductDaoImpl"/>

4. 编写测试类

@RunWith(SpringJUnit4ClassRunner.class)	//Spring整合JUnit4单元测试
@ContextConfiguration("classpath:applicationContext.xml")	//加载配置文件
public class SpringDemo {
	
	@Resource(name="productDao")//属性注入
	private ProductDao productDao;
	
	@Test//需要JUnit4,不能使用JUnit5
	public void demo() {
		productDao.save();
		productDao.update();
		productDao.delete();
		productDao.find();
	}
}

注意: 这里使用到了Spring与JUnit4的整合,需要在web工程导入spring-test-4.2.4.RELEASE.jar,然后使用RunWith声明。另外我们也是了ContextConfiguration注解来加载配置文件,因此不用每次都以new的方式来获取ApplicationContext对象。

测试运行结果如下:
在这里插入图片描述

5. 编写一个切面类

  • 编写切面类,具体代码如下:
public class MyAspectXML {
	
	//权限校验
	public void checkPri() {
		System.out.println("权限校验...");
	}
}
  • 将切面类交由Spring管理,在applicationContext.xml进行如下配置:
<!-- 将切面类交由Spring管理 -->
<bean id="myAspect" class="com.shoto.spring.xmldemo.MyAspectXML"/>

6. 通过AOP配置来引用切面类

在applicationContext.xml进行如下配置:

<!-- 通过AOP的配置完成对目标对象类ProductDaoImpl产生代理 -->
<aop:config>
	<!-- 配置切入点。
	expression表达式配置哪些类的哪些方法需要进行增强 ,这里是ProductDaoImpl的save方法。
	其中*表示任意返回值,..表示任意参数
	此时save方法为一个pointcut,即切入点-->
	<aop:pointcut expression="execution(* com.shoto.spring.xmldemo.ProductDaoImpl.save(..))" 
		id="pointcut1"/>
	<!-- 配置切面 -->
	<aop:aspect ref="myAspect">
		<!-- 配置前置增强,简单说就是在切入点save之前先执行的增强即checkPri权限校验方法 -->
		<aop:before method="checkPri" pointcut-ref="pointcut1"/>
	</aop:aspect>
</aop:config>

此时再次运行测试类SpringDemo的测试方法,其测试结果如下:
在这里插入图片描述
也就是在save方法执行之前进行了权限校验,即对save进行了增强。

三、Spring增强类型(XML)

前置增强:在目标方法执行之前进行操作

比如上面配置的内容,即给save方法设置前置增强checkPri方法,如下所示:
在这里插入图片描述
另外,我们可以通过JoinPoint类来获取切入点信息:

修改切面类MyAspectXML的checkPri方法如下:
在这里插入图片描述
运行测试输出如下结果:
在这里插入图片描述

后置增强:在目标方法执行之后进行操作

下面我们给ProductDaoImpl的delete方法添加一个后置增强writeLog方法,即在删除后进行日志记录。

在applicationContext.xml进行delelet切入点的配置,具体如下:
在这里插入图片描述
然后配置后置通知,具体配置如下:
在这里插入图片描述
此时再次运行测试类SpringDemo的测试方法,其测试结果如下:
在这里插入图片描述
也就是在删除商品后进行日志记录操作。

另外,<aop:after-returning>标签有retruning属性,我们可以使用它来获取切入点delete方法的返回值。下面修改ProductDao的实现类ProductDaoImpl的delete方法如下:

@Override
public String delete() {
	System.out.println("删除商品");
	return "删除成功!";
}

同时,修改切面类MyAspect的writeLog方法如下:

//日志记录
public void writeLog(Object result) {
	System.out.println("日志记录...");
	System.out.println("delete方法的返回值为:" + result);
}

applicationContext.xml的配置如下:
在这里插入图片描述
returning的内容result即为writeLog方法的result参数。该result用于接收切入点方法delete的返回值, 这里即是"删除成功"。

运行测试的结果如下:
在这里插入图片描述

环绕增强:在目标方法执行前后进行操作

下面以ProductDaoImpl的update方法为切入点,演示环绕增强的使用。

在applicationContext.xml进行update切入点的配置,具体如下:

<aop:pointcut expression="execution(* com.shoto.spring.xmldemo.ProductDaoImpl.update(..))" 
	id="pointcut3"/>

在增强类添加如下方法:

/**
* 定义一个环绕的增强方法
 * @throws Throwable 
 */
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
	System.out.println("环绕输出...");
	//调度ProceedingJoinPoint的proceed方法就可以调用原有的方法
	Object object = joinPoint.proceed();
	System.out.println("环绕输出...");
	return object;
}

在applicationContext.xml进行后置增强配置,具体如下:

<!-- 配置环绕增强 -->
<aop:around method="around" pointcut-ref="pointcut3"/>

此时再次运行测试类SpringDemo的测试方法,其测试结果如下:
在这里插入图片描述

异常抛出增强:在程序出现异常时进行的操作

下面以find方法为切入点来演示异常抛出增强的使用。

同样的,在applicationContext.xml进行update切入点的配置,具体如下:

<aop:pointcut expression="execution(* com.shoto.spring.xmldemo.ProductDaoImpl.find(..))" 
	id="pointcut4"/>

在增强类添加如下方法:

/**
 * 异常抛出增强,ex封装有异常信息
 */
public void afterThrowing(Throwable ex) {
	System.out.println("异常抛出增强..." + ex.getMessage());
}

在applicationContext.xml进行异常抛出增强配置,具体如下:

<!-- 配置异常抛出增强 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="ex"/>

注意: throwing属性中的ex即为afterThrowing方法的参数名

此时find方法若发生了除0异常,运行测试会输出如下结果:
在这里插入图片描述

最终增强:无论代码是否有异常,总会执行

在上面演示异常抛出增强的基础上来演示最终增强的使用。

在增强类添加如下方法:

/**
 * 定义一个最终增强方法
 */
public void after() {
	System.out.println("最终通知增强...");
}

在applicationContext.xml进行最终增强配置,具体如下:

<!-- 配置最终增强 -->
<aop:after method="after" pointcut-ref="pointcut4"/>

运次测试结果如下:
在这里插入图片描述

四、切入点表达式语法

基于execution的函数完成的,其结构如下所示:

[访问修饰符]  方法返回值   包名.类名.方法名(参数)

示例如下:
在这里插入图片描述
第一个表示访问修饰符是public,返回值是void,然后就是com.itheima.spring.CustomerDao.save方法,其中参数用…表示任意参数;

第二个省略了访问修饰符(后面几个同理),表示任意返回值,任意包下的所有以Dao结尾的类的save方法;

第三个的+号表示CustomerDao的当前类和其子类有效;

第四个表示com.itheima.spring包的所有子包的所有子类的所有方法。

五、AOP开发(注解)

1. 创建web项目,引入Jar包,并引入配置文件

具体参考基于XML的AOP开发的内容

2. 编写目标类并配置

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("查询订单...");
	}
}

在applicationContext.xml进行如下配置:

<bean id="orderDao" class="com.shoto.spring.demo.OrderDao"></bean>

3. 编写切面类并配置

/**
 * 切面类:注解的切面类
 * @author 郑松涛
 *
 */
@Aspect	//声明该类为切面类
public class MyAspectAnno {

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

注意:需要使用@Aspect注解来声明该类为切面类。

同样,在applicationContext.xml进行如下配置:

<bean id="myAspect" class="com.shoto.spring.demo.MyAspectAnno"></bean>

4. 使用注解对AOP对象目标类进行增强

  • 要使用注解,必须在配置中开启注解的AOP开发
    <!-- 在配置文件中开启注解的AOP开发 -->
    <aop:aspectj-autoproxy />
    
  • 然后下面以前置增强为例来在切面类上使用注解,具体代码如下:
    在这里插入图片描述

5. 编写测试类

@RunWith(SpringJUnit4ClassRunner.class)	//Spring整合JUnit单元测试
@ContextConfiguration("classpath:applicationContext.xml")	//加载配置文件
public class SpringDemo {
	
	@Resource(name="orderDao")//属性注入
	private OrderDao orderDao;
	
	@Test//需要JUnit4,不能使用JUnit5
	public void demo() {
		orderDao.save();
		orderDao.update();
		orderDao.delete();
		orderDao.find();
	}
}

运行结果如下:
在这里插入图片描述

六、Spring增强类型(注解)

下面讲一下Spring的基于注解的AOP的各种增强使用,这里只给出增强的使用的核心内容(切面类MyAspectAnno中)。

@Before:前置增强

//通过注解的方式给save配置前置增强
@Before(value="execution(* com.shoto.spring.demo.OrderDao.save(..))")
public void before() {
	System.out.println("前置增强===========");
}

@AfterReturning:后置增强

//后置通知
@AfterReturning(value="execution(* com.shoto.spring.demo.OrderDao.delete(..))",returning="result")
public void afterReturning(Object result) {
	System.out.println("后置增强===========" + result);
	//输出  后置增强===========删除成功!
}

@Around:环绕增强

//环绕通知
@Around(value="execution(* com.shoto.spring.demo.OrderDao.update(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
	System.out.println("环绕前增强==========");
	Object object = joinPoint.proceed();
	System.out.println("环绕后增强==========");
	return object;	
}

@AfterThrowing:异常抛出增强

//异常抛出异常
@AfterThrowing(value="execution(* com.shoto.spring.demo.OrderDao.find(..))",throwing="ex")
public void afterThrowing(Throwable ex) {
	System.out.println("异常抛出增强==========" + ex.getMessage());
}

@After:最终增强

//最终增强
@After(value="execution(* com.shoto.spring.demo.OrderDao.find(..))")
public void after() {
	System.out.println("最终增强==========");
}

猜你喜欢

转载自blog.csdn.net/weixin_40374341/article/details/86650325
今日推荐