AOP思想(面向切面编程)、注解版通知示例
AOP介绍
AOP(Aspect Oriented Programming,即面向切面编程),是OOP(Object Oriented Programming,面向对象编程)的补充和完善。AOP利用一种称为"横切"的技术,剖开封装的对象,将那些影响了多个类的公共行为封装到一个可重用模块,减少系统的重复代码,降低模块之间的耦合度。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志打印等,如下图:

示意图一

示意图二

示意图三
AOP是在不改变原程序的基础上为代码段增加新的功能,底层使用的是代理设计模式实现的。
AOP术语
1.target:目标类,需要被代理的类,例如:UserServiceImpl
2.Joinpoint:连接点,哪些方法可以被拦截
3.PointCut:切入点,被增强的连接点(通过切入点表达式选择),也就是指我们要对哪些Joinpoint进行拦截
4.advice:通知/增强,拦截到Joinpoint之后所要做的事情就是通知,是一段代码
5.Weaving:织入,是指把aspect应用到目标对象target来创建新的代理对象proxy的过程,是一个动作
6.proxy:融合了原来类和增强逻辑的代理类
7.Aspect(切面):由切入点pointcut和通知advice组成

通知类型
前置通知:目标方法运行之前调用 后置通知:在目标方法运行之后调用;如果出现异常不会调用 环绕通知:在目标方法之前和之后都调用 异常拦截通知:如果出现异常,就会调用 最终通知:在目标方法运行之后调用;无论是否出现异常,都会调用
注解版通知环境配置
pom
<?xml version="1.0" encoding="UTF-8"?>
<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>com.qzcsbj.myspring</groupId>
<artifactId>myspring</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<spring.version>4.3.14.RELEASE</spring.version>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
</properties>
<dependencies>
<!-- spring需要的jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--<dependency>-->
<!--<groupId>org.springframework</groupId>-->
<!--<artifactId>spring-context-support</artifactId>-->
<!--<version>${spring.version}</version>-->
<!--</dependency>-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<!--aop相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<!--日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<!--Spring测试模块-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
实体类
package com.qzcsbj.bean;
/**
* @公众号 : 全栈测试笔记
* @博客 : www.cnblogs.com/uncleyong
* @微信 : ren168632201
* @描述 : <>
*/
public class User {
private String name;
private String sex;
public User() {
}
public User(String name, String sex) {
this.name = name;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
dao层
dao接口
package com.qzcsbj.dao;
import com.qzcsbj.bean.User;
/**
* @公众号 : 全栈测试笔记
* @博客 : www.cnblogs.com/uncleyong
* @微信 : ren168632201
* @描述 : <>
*/
public interface UserDao {
public int addUser(User user);
public int deleteUser(int id);
public int updateUser(User user);
}
dao实现类(这里只是演示,没有真的操作数据库,而且,mybatis也不需要实现类)
package com.qzcsbj.dao.impl;
import com.qzcsbj.bean.User;
import com.qzcsbj.dao.UserDao;
import org.springframework.stereotype.Repository;
/**
* @公众号 : 全栈测试笔记
* @博客 : www.cnblogs.com/uncleyong
* @微信 : ren168632201
* @描述 : <>
*/
@Repository
public class UserDaoImpl implements UserDao {
public int addUser(User user){
System.out.println("============新增用户:" + user);
// System.out.println(1/0); // 异常
return 1;
}
public int deleteUser(int id){
System.out.println("============删除用户:" + id);
return 1;
}
public int updateUser(User user){
System.out.println("============更新用户:" + user);
return 1;
}
}
日志:log4j.properties
log4j.rootLogger = INFO,console,file
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern = %d{HH:mm:ss SSS} [%t] %-5p method: %l----%m%n
log4j.appender.file = org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.File = target/qzcsbj.log
log4j.appender.file.Append = true
log4j.appender.file.Threshold = warn
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} method: %l - [ %p ] ----%m%n
配置文件:applicationContext2.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:context="http://www.springframework.org/schema/context"
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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--扫描包-->
<context:component-scan base-package="com.qzcsbj.*"/>
<!--配置使用注解的方式将增强织入目标对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
注解版示例:前置通知、后置通知、异常通知、最终通知
通知类:添加@Component和@Aspect
package com.qzcsbj.adviser;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @公众号 : 全栈测试笔记
* @博客 : www.cnblogs.com/uncleyong
* @微信 : ren168632201
* @描述 : <>
*/
@Component
@Aspect // 表示这是一个切面类
public class TransactionAdviser {
// 前置通知
@Before("execution(* com.qzcsbj.dao.impl.*.*(..))")
public void openTx(){
System.out.println("================开启事务");
}
// 后置通知
@AfterReturning(value = "execution(* com.qzcsbj.dao.impl.*.*(..))", returning = "val")
public void afterRetrunAdviser(Object val){
System.out.println("================提交事务,方法返回值是:" + val);
}
// 异常通知
@AfterThrowing(value = "execution(* com.qzcsbj.dao.impl.*.*(..))", throwing = "ex")
public void exceptionAdviser(Exception ex){
System.out.println("================回滚事务,异常信息是:" + ex.getMessage());
}
// 最终增强
@After(value = "execution(* com.qzcsbj.dao.impl.*.*(..))")
public void commitTx(){
System.out.println("================最终增强");
}
}
运行顺序:
无异常,最终增强在后置增强前面

有异常,最终增强在异常增强前面
public int addUser(User user){
System.out.println("============新增用户:" + user);
System.out.println(1/0); // 异常
return 1;
}
结果:只执行了新增方法

调整dao实现类:在deleteUser方法中加异常,addUser放最后
package com.qzcsbj.dao.impl;
import com.qzcsbj.bean.User;
import com.qzcsbj.dao.UserDao;
import org.springframework.stereotype.Repository;
/**
* @公众号 : 全栈测试笔记
* @博客 : www.cnblogs.com/uncleyong
* @微信 : ren168632201
* @描述 : <>
*/
@Repository
public class UserDaoImpl implements UserDao {
public int deleteUser(int id){
System.out.println("============删除用户:" + id);
System.out.println(1/0);
return 1;
}
public int updateUser(User user){
System.out.println("============更新用户:" + user);
return 1;
}
public int addUser(User user){
System.out.println("============新增用户:" + user);
// System.out.println(1/0); // 异常
return 1;
}
}
结果:虽然上面addUser放最下面,但是下面先执行addUser,然后执行deleteUser,说明方法执行顺序是按照ascii来排序的

注解版示例:环绕通知
package com.qzcsbj.adviser;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @公众号 : 全栈测试笔记
* @博客 : www.cnblogs.com/uncleyong
* @微信 : ren168632201
* @描述 : <>
*/
@Component
@Aspect // 表示这是一个切面类
public class TransactionAdviser {
// 环绕增强
@Around(value = "execution(* com.qzcsbj.dao.impl.*.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint) { // 连接点
Object result = null;
try {
System.out.println("--------------开启事务------------");
//调用目标方法
result = joinPoint.proceed();
System.out.println("--------------提交事务--------方法的返回值:" + result);
} catch (Throwable throwable) {
System.out.println("--------------回滚事务----------异常信息是:" + throwable.getMessage());
//throwable.printStackTrace();
} finally {
System.out.println("--------------最终增强------------");
}
return result;
}
}
package com.qzcsbj.dao.impl;
import com.qzcsbj.bean.User;
import com.qzcsbj.dao.UserDao;
import org.springframework.stereotype.Repository;
/**
* @公众号 : 全栈测试笔记
* @博客 : www.cnblogs.com/uncleyong
* @微信 : ren168632201
* @描述 : <>
*/
@Repository
public class UserDaoImpl implements UserDao {
public int deleteUser(int id){
System.out.println("============删除用户:" + id);
// System.out.println(1/0);
return 1;
}
public int updateUser(User user){
System.out.println("============更新用户:" + user);
return 1;
}
public int addUser(User user){
System.out.println("============新增用户:" + user);
// System.out.println(1/0); // 异常
return 1;
}
}
package com.qzcsbj.dao.impl;
import com.qzcsbj.bean.User;
import com.qzcsbj.dao.UserDao;
import org.springframework.stereotype.Repository;
/**
* @公众号 : 全栈测试笔记
* @博客 : www.cnblogs.com/uncleyong
* @微信 : ren168632201
* @描述 : <>
*/
@Repository
public class UserDaoImpl implements UserDao {
public int deleteUser(int id){
System.out.println("============删除用户:" + id);
System.out.println(1/0);
return 1;
}
public int updateUser(User user){
System.out.println("============更新用户:" + user);
return 1;
}
public int addUser(User user){
System.out.println("============新增用户:" + user);
// System.out.println(1/0); // 异常
return 1;
}
}
总结:执行顺序
无异常,最终增强在后置增强前面,环绕在最终增强前面
有异常,最终增强在异常增强前面,环绕在最终增强前面

补充:测试类
package com.qzcsbj.test;
import com.qzcsbj.bean.User;
import com.qzcsbj.dao.UserDao;
import com.qzcsbj.dao.impl.UserDaoImpl;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
/**
* @公众号 : 全栈测试笔记
* @博客 : www.cnblogs.com/uncleyong
* @微信 : ren168632201
* @描述 : <>
*/
@RunWith(SpringJUnit4ClassRunner.class) // 表示Spring和JUnit整合测试
@ContextConfiguration("classpath:applicationContext2.xml")
public class Test {
// @Autowired // 对象只有一个,所以这里直接写@Resource也可以
@Resource
UserDao userDao;
@org.junit.Test
public void test(){
userDao.addUser(new User("jack","男"));
userDao.deleteUser(1);
userDao.updateUser(new User("jack","女"));
}
}
说明:以上非运行结果图片来自百度图片
原文会持续更新,原文地址:https://www.cnblogs.com/uncleyong/p/17023866.html
__EOF__
关于博主:擅长性能、全链路、自动化、企业级自动化持续集成(DevTestOps)、测开等
面试必备:项目实战(性能、自动化)、简历笔试,https://www.cnblogs.com/uncleyong/p/15777706.html
测试提升:从测试小白到高级测试修炼之路,https://www.cnblogs.com/uncleyong/p/10530261.html
欢迎分享:如果您觉得文章对您有帮助,欢迎转载、分享,也可以点击文章右下角【推荐】一下!

浙公网安备 33010602011771号