本文共 6096 字,大约阅读时间需要 20 分钟。
一、基本概念
DI能够让相互协作的软件组件保持松散耦合;而面向切面编程(aspect-oriented programming,AOP)允许你把遍布应用各处的功能分离出来形成可重用的组件。把这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的问题
常见场景:日志、安全、事物、缓存
项目中每个模块的核心功能都是为特定业务领域提供服务,但是这些模块都需要类似的辅助功能,例如安全和事务管理,这时候需要引入AOP的概念。
通知定义了切面是什么以及何时使用, Spring切面可以应用5种类型的通知:
连接点(join potint)是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为
切点(poincut)的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点
1 2 3 4 5 6 7 8 9 10 11 | 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()" ); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 | <?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> |
1 2 3 4 5 6 7 8 9 10 | @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()
Spring所创建的通知都是用标准的Java类编写的, 定义通知所应用的切点通常会使用注解或在Spring配置文件里采用XML来编写,这两种语法对于Java开发者来说都是相当熟悉的。
注意Spring只支持方法级别的连接点。
切入点表达式
execution指示器是我们在编写切点定义时最主要使用的指示器
Demo
我们要实现的一个简单示例是:在service方法调用前和调用后打印日志“write log”。
1 2 3 4 5 | public class LogHandler { public void log(){ System.out.println( "write log." ); } } |
aop.xml添加配置:
1 2 3 4 5 6 7 8 | <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> |
单元测试:
1 2 3 4 5 6 7 8 9 10 | 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依赖:
1 2 3 4 5 | <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.
通过使用环绕通知,可以实现前置通知和后置通知所实现的功能,而且只需要在一个方法中实现。
1 2 3 4 5 6 7 8 9 10 11 | 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通知
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <?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> |
单元测试:
1 2 3 4 5 6 7 8 | public class AopTest1 { @Test public void test(){ ApplicationContext context= new ClassPathXmlApplicationContext( "aop1.xml" ); CategoryService1 service1=context.getBean(CategoryService1. class ); service1.add( 1 ); } } |
运行结果:
1 2 3 | 1 .before log 1489990832246 CategoryService1.add() 2 .after log 1489990832263 |
定义切面需要给类添加@Aspect注解。然后需要给方法添加注解来声明通知方法,各通知类型对应的注解:
1 2 3 4 5 6 7 8 9 | @Component @Aspect public class LogHelper3 { @Before ( "execution(* service.*.*(..))" ) public void logStart(){ System.out.println( "log start " + new Date().getTime()); } } |
然后定义JavaConfig类,注意需要给类添加@EnableAspectJAutoProxy注解启用自动代理功能。
1 2 3 4 5 | @Configuration @EnableAspectJAutoProxy @ComponentScan (basePackageClasses = {service.CategoryService3. class ,pointcut.LogHelper3. class }) public class BeanConfig { } |
单元测试:
1 2 3 4 5 6 7 8 9 10 11 12 | @RunWith (SpringJUnit4ClassRunner. class ) @ContextConfiguration (classes = BeanConfig. class ) public class AopTest3 { @Autowired CategoryService3 service; @Test public void testConfigAop(){ service.add( 100 ); } } |
运行结果:
1 2 | log start 1489990977264 add category id= 100 |
结尾:
参考:《spring实战》
源码下载:https://github.com/cathychen00/learnjava/tree/master/DemoAOP
本文转自 陈敬(Cathy) 博客园博客,原文链接:http://www.cnblogs.com/janes/p/6873732.html,如需转载请自行联系原作者