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
欢迎分享:如果您觉得文章对您有帮助,欢迎转载、分享,也可以点击文章右下角【推荐】一下!