Java框架–Spring (三)(实战开发)
AOP
AOP(Aspect Oriented Programming)是OOP的延续,称为面向切面编程。AOP可以通过预编译方式或者运行时动态代理实现在不修改源代码的情况下给程序动态添加额外功能的技术。AOP是Spring的核心之一。
1.AspectJ表达式
切入点是用AspectJ表达式表示的,AspectJ中execution()是固定写法,在()内描述切入点的位置。AspectJ有多种写法“…”表示任何参数、任何子包,“*”表示任何返回值、包、类、方法。
2.体验AOP
第一步:创建项目并测试
1:创建项目
创面一个mavne项目,GroupID=“cn.it”,Artifact ID=“springaop”。
2:导入jar包
pom.xml配置如下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.itlaobing</groupId>
<artifactId>springaop</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>aop</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.10.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
</dependencies>
</project>
3:定义DeptDao类
package cn.it.aop.dao;
import org.springframework.stereotype.Repository;
@Repository
public class DeptDao {
public void save() {
System.out.println("dao 的 save()方法被调用");
}
}
4:定义DeptService类
package cn.it.aop.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import cn.it.aop.dao.DeptDao;
@Service
public class DeptService {
@Autowired private DeptDao deptDao = null;
public void save() {
deptDao.save();
}
}
5:定义配置文件
<?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"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan base-package="cn.it.aop"/>
</beans>
6:定义单元测试类
package cn.it.aop;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import cn.it.aop.service.DeptService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicatonContext.xml")
public class AopTest {
@Autowired
DeptService deptService = null;
@Test
public void testDeptService() {
deptService.save();
}
}
7:运行单元测试类
dao 的 save()方法被调用
这是一个比较简单的例子,在spring容器中创建了DeptService和DeptDao两个bean对象。在Spring Test中进行了单元测试,在单元测试中调用了DeptService的save方法,DeptService的save方法调用了DeptDao的save方法。
如果现在业务需求中要实现在DeptService调用DeptDao之前写入“准备添加部门”的业务日志,在调用方法结束后写入“保存部门完毕”的业务日志。在不更改原有代码的情况下能够实现吗?
一些业务方法执行前需要执行该业务之外的附加逻辑(比如:安全验证逻辑、日志处理、事务处理、错误检查等),但是为了功能模块之间解耦合、代码重用等要求,一个业务方法功能要尽可能独立,一些业务逻辑之外的附加逻辑就散落在应用程序主体业务之外,而这些的琐碎工作(又称Advice:通知)又是重要的。AOP技术就是在切面(Aspect:方面,切面)类中定义Advice方法,在主体业务程序执行过程中,在需要执行Advice方法的某个点上,将Aspect类中的Advice方法调用,从而在不修改主体业务逻辑代码的情况下,增强业务的功能。
第二步:体验AOP
体验AOP
1:重构pom.xml文件
使用AOP需要spring-aop、aspectjrt、aspectjweaver的jar包
<!--...................-->
<!-- AOP需要导入spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
<!-- AOP需要导入aspectjrt -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.10</version>
</dependency>
<!-- AOP需要导入aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
<!--...................-->
2:重构applicationContext.xml配置文件
使用AOP需要在spring配置文件中添加aop命名空间,并设置aop:aspectj-autoproxy/。
<?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"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan base-package="cn.it.aop"/>
<!-- 启用aspectj 扫描 -->
<aop:aspectj-autoproxy/>
</beans>
启用AOP后,在spring配置文件的启用AOP配置前有棕色箭头提醒
3:定义切面类
package cn.it.aop.advice;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
//定义切面@Aspect@Component
public class DeptServiceLog {
//定义切入点
@Pointcut("execution(* cn.it.aop.dao.DeptDao.save())")
//切入点的名称为save()
private void save() {}
@Before("save()")
public void saveBefore() {
System.out.println("准备添加部门");
}
@After("save()")
public void saveAfter() {
System.out.println("保存部门完毕");
}
}
分析:
-
@Aspect注解用于标注类,它所标注的类是Aspect类(切面类),Aspect类中包含两部分内容,分别是@Pointcut(切入点)和Advice方法
-
@Pointcut注解用于定义切入点,切入点是用AspectJ表达式指示在什么地方将Advice方法切入进去(执行Advice方法)。定义切入点时需要定义切入的地方和切入点的名称,如下图。
-
本例中的切入地方是调用cn.it.aop.dao.DeptDao类的save()方法处,本例中的切入点名称是save(),切入点名称的定义与定义方法类似。
-
@Before和@After注解标注在Advice方法上,表示Advice的类型,用于指示在什么时机执行Advice方法。@Before表示调用方法之前,@After表示调用方法之后。标注了@Before和@After的代码行前面会显示有方向的棕色图标
-
Advice方法的执行需要由@Pointcut和Advice的类型共同决定,如下图所示。在本例中的@Pointcut+@Before表示在调用DeptDao类的save()方法之前执行saveBefore()方法,@Pointcut+@After表示在调用DeptDao类的save()方法之后调用saveAfter()方法。
4:再次进行单元测试
运行AopTest类的testDeptService()单元测试方法,运行结果如下:
准备添加部门
dao 的 save()方法被调用
保存部门完毕
5:分析
运行结果输出了“准备添加部门”和“保存部门完毕”。根据运行结果可以得出结论,在DeptService调用DeptDao类的save()方法之前先调用了DeptServiceLog类的saveBefore()方法,在DeptService调用DeptDao类的save()方法之后调用了DeptServiceLog类的saveAfter ()方法。而saveBefore()方法与saveAfter()方法正是用于写日志的方法。如此一来就实现了在不更改DeptService类和DeptDao类源码的情况下,完成了写日志的功能。
eptService调用DeptDao类的save()方法之前先调用了DeptServiceLog类的saveBefore()方法,在DeptService调用DeptDao类的save()方法之后调用了DeptServiceLog类的saveAfter ()方法。而saveBefore()方法与saveAfter()方法正是用于写日志的方法。如此一来就实现了在不更改DeptService类和DeptDao类源码的情况下,完成了写日志的功能。
这正是AOP的神奇之处。AOP能够实现在不更改原有代码的情况下,在调用方法之前、调用之后、被调用方法抛出异常后、被调用方法返回值后去执行另外一个方法,以增强原有业务的功能。