一、基本概念
1.AOP简介
DI能够让相互协作的软件组件保持松散耦合;而面向切面编程(aspect-oriented programming,AOP)允许你把遍布应用各处的功能分离出来形成可重用的组件。把这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的问题
常见场景:日志、安全、事物、缓存
2.AOP用到的一些术语
项目中每个模块的核心功能都是为特定业务领域提供服务,但是这些模块都需要类似的辅助功能,例如安全和事务管理,这时候需要引入AOP的概念。
通知定义了切面是什么以及何时使用, Spring切面可以应用5种类型的通知:
- 前置通知(Before):在目标方法被调用之前调用通知功能;
- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
- 返回通知(After-returning):在目标方法成功执行之后调用通知;
- 异常通知(After-throwing):在目标方法抛出异常后调用通知;
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
连接点(join potint)是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为
切点(poincut)的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点
二、准备service模块
1.service bean
public class CategoryService1 { public void add(int id) { System.out.println("CategoryService1.add()"); } } public class CategoryService2{ public void add(int id) { System.out.println("CategoryService2.add()"); } }
2.配置bean
<?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:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"> <bean id="categoryServiceImpl" class="service.CategoryService1"></bean> <bean id="CategoryServiceImpl2" class="service.CategoryService2"></bean> </beans>
3.单元测试
@Test public void test(){ ApplicationContext context=new ClassPathXmlApplicationContext("aop.xml"); CategoryService1 service1=context.getBean(CategoryService1.class); service1.add(1); CategoryService2 service2=context.getBean(CategoryService2.class); service2.add(2); }
运行结果:
CategoryService1.add()
CategoryService2.add()
三、XML方式声明AOP
Spring所创建的通知都是用标准的Java类编写的, 定义通知所应用的切点通常会使用注解或在Spring配置文件里采用XML来编写,这两种语法对于Java开发者来说都是相当熟悉的。
注意Spring只支持方法级别的连接点。
切入点表达式
execution指示器是我们在编写切点定义时最主要使用的指示器
Demo
我们要实现的一个简单示例是:在service方法调用前和调用后打印日志“write log”。
public class LogHandler { public void log(){ System.out.println("write log."); } }
aop.xml添加配置:
<bean id="logHandler" class="pointcut.LogHandler"></bean> <aop:config> <aop:aspect id="log" ref="logHandler"> <aop:pointcut id="addLog" expression="execution(* service.*.*(..))"></aop:pointcut> <aop:before method="log" pointcut-ref="addLog"></aop:before> <aop:after method="log" pointcut-ref="addLog"></aop:after> </aop:aspect> </aop:config>
单元测试:
public class AopTests { @Test public void test() { ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml"); CategoryService1 service1 = context.getBean(CategoryService1.class); service1.add(1); CategoryService2 service2 = context.getBean(CategoryService2.class); service2.add(2); } }
运行报错:
org.aspectj.weaver.reflect.ReflectionWorld$ReflectionWorldException
原来是忘了pom依赖:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency>
运行结果:
write log.
CategoryService1.add()
write log.
write log.
CategoryService2.add()
write log.
<?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>DemoStore</groupId> <artifactId>DemoAOP</artifactId> <version>1.0-SNAPSHOT</version> <properties> <spring.version>4.3.5.RELEASE</spring.version> </properties> <dependencies> <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-aspects</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>RELEASE</version> </dependency> </dependencies> </project>
四、aop:around
通过使用环绕通知,可以实现前置通知和后置通知所实现的功能,而且只需要在一个方法中实现。
public class LogTimeHandler { public void log(ProceedingJoinPoint jp) throws Throwable { try { System.out.println("1.before log "+new Date().getTime());//记录开始时间 jp.proceed(); System.out.println("2.after log "+new Date().getTime());//记录结束时间 }catch (Exception e){ System.out.println("log fail "); } } }
在aop1.xml中配置aop:round通知
<?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 id="categoryService" class="service.CategoryService1"></bean> <bean id="logHanlder" class="pointcut.LogTimeHandler"></bean> <aop:config> <aop:aspect id="log" ref="logHanlder"> <aop:pointcut id="addlog" expression="execution(* service.*.*(..))"></aop:pointcut> <aop:around method="log" pointcut-ref="addlog"></aop:around> </aop:aspect> </aop:config> </beans>
单元测试:
public class AopTest1 { @Test public void test(){ ApplicationContext context=new ClassPathXmlApplicationContext("aop1.xml"); CategoryService1 service1=context.getBean(CategoryService1.class); service1.add(1); } }
运行结果:
1.before log 1489990832246 CategoryService1.add() 2.after log 1489990832263
五、注解方式创建AOP
定义切面需要给类添加@Aspect注解。然后需要给方法添加注解来声明通知方法,各通知类型对应的注解:
- @After 通知方法会在目标方法返回或抛出异常后
- @AfterReturning 通知方法会在目标方法返回后调用
- @AfterThrowing 通知方法会在目标方法抛出异常后调用
- @Around 通知方法会将目标方法封装起来
- @Before 通知方法会在目标方法调用之前执行
@Component @Aspect public class LogHelper3 { @Before("execution(* service.*.*(..))") public void logStart(){ System.out.println("log start "+new Date().getTime()); } }
然后定义JavaConfig类,注意需要给类添加@EnableAspectJAutoProxy注解启用自动代理功能。
@Configuration @EnableAspectJAutoProxy @ComponentScan(basePackageClasses = {service.CategoryService3.class,pointcut.LogHelper3.class}) public class BeanConfig { }
单元测试:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = BeanConfig.class) public class AopTest3 { @Autowired CategoryService3 service; @Test public void testConfigAop(){ service.add(100); } }
运行结果:
log start 1489990977264 add category id=100
结尾:
参考:《spring实战》
源码下载:https://github.com/cathychen00/learnjava/tree/master/DemoAOP
作者:陈敬(公众号:敬YES)
出处:http://www.cnblogs.com/janes/
博客文章仅供交流学习,请勿用于商业用途。如需转载,请务必注明出处。