Spring 面向切面,简单入门(AOP)
2017-07-06 10:49
411 查看
AOP:Aspect Oriented Programing 的简称,中文翻译为面向向切面编程。
按照软件重构的思想,如果类的中出现项目的代码,应该考虑定义一个共同的抽象类,将这些相同的代码提取到抽象类中。例如最初学习 java 的时候,老师都很喜欢用 Horse、Pig、Dog 举例子,这些动物都用 run()、eat() 方法。而这些方法可以通过父类 Animal 继承,但是实际应用中并没有那么简单。
在写这边文章的时候,我看过不少的资料和书籍。书上一般都会说很多概念,和需要 aop 的术语,什么连接点、切点、增强、目标对象等等等。。。然后再把这些术语统一解释一次,接着加几副图片。不知道你们什么感觉,反正我觉得乱七八糟的,特别是新手,心好累的好么。然而今天我写的这边文章,我只介绍什么时候用,怎么用,专业术语,会简单说明一下。其他想深入理解的,可以查看其他资料,或者等我后续的文章(应该会更新,我也不知道我会不会偷懒)。
什么时候用aop?
例如,在修改新增的时候,常常需要添加事务,如果自己写的话,代码中会不停重复出现 conn.setAutoCommit(false)、conn.commit()、conn.rollback() 这样子的代码。
还有例如,在查询大数据的报表时,需要计算查询所用的时间,要获取 (查询后的毫秒数 - 查询前的毫秒数) 。
当需要写这些重复的代码,而这些代码也程序的业务逻辑又没有明显的关系时,为了是让代码更加清晰明了。则需要用到了aop。
这个是我个人理解,因为我个人认为即使没有 aop 也可以让代码正常的运行,至于什么计算时间,事务管理什么的,一样也可以正常得到结果。以下是比较专业一点的说法,不知道你们什么感觉,反正我当初刚学 aop 的时候是不懂的,一副要死的样子。
在程序正常的业务流中间像切面一样插入很多其他需要执行的代码
AOP 怎么用?
以餐厅的服务生为例子。假设服务生只做两件事情。第一、欢迎顾客,第二、对顾客提供服务
实现类如下
例如每个服务生在履行职责的时候都打印工作记录。如果没有 aop 的话,就在上面的代码两个方法最开始执行的地方加入记录的代码。而使用 aop 首先要定义这个打印工作记录这个方法。 spring 提供了一个在执行方法之前会自动调用的接口:org.springframework.aop.MethodBeforeAdvice
现在已经把服务生的代码和记录工作的代码写好。而且想要记录工作的代码正常运行起来,需要使用 spring 给我们准备好的代理工厂。
由上面的代码可以见,使用 spring 提供的代理后,我们直接执行,就自动会有操作记录打印出来。
有人或者会问,可以不可以定义两个方法呢? 我可以很负责的告诉你,是可以的。而且两个方法的执行顺序是根据加如到代理的顺序是一致的。把上面代码的注释打开,然后加增一个类。
又有人会问,目标执行前有接口继承,执行后呢?或者
4000
说前后都需要呢?答案是:都有!!
顺便提一下,这里有两个专业术语,服务生的那两个主要业务逻辑的方法,叫切点。而操作记录打印叫做增强,因为是在业务逻辑前执行的,所有叫前置增强。
还有其他的增强,用法都差不多的,如下:
前置增强 org.springframework.aop.MethodBeforeAdvice
后置增强 org.springframework.aop.AfterReturningAdvice
环绕增强(前后都有执行的代码是使用) org.aopalliance.intercept.MethodInterceptor
异常抛出增强:org.springframework.aop.ThrowsAdvice
引介增强(在目标类中添加新方法和属性,这个一直没用过): org.springframework.aop.IntroductionInterceptor
现在要说说怎么在 xml 中配置,毕竟如果切点(服务生)很多的话,应该尽可能的少动代码。
xml 配置 AOP
保留上面的代码 NavieWaiterBeforeAdvice、Waiter 、NaiveWaiter 不动。然后加入一个切面(切面可以理解成切点和增强连接的地方)。
然后加入xml配置,以下提供三种配置方式,只需要选择一种即可。
这个是最原始的配置方式。
正则表达式的配置方式
以 bean 的名字模糊匹配
main 方法代码
在 jdk 5.0之后,又加入了注解的方式。
以下例子为了避免混淆,和上面的代码没有关系(全部重新写!!)。依然用 Waiter 为例。
execution这个为切点表达式
*:匹配任意字符,但只能匹配上写问的一个元素
..:匹配任意字符,可以匹配上下问的所有元素,但是在表示类是要和* 一起用。例如 ..*
+:表示按类型匹配指定类的所有类,必须跟在类名的后面,如 com.smart.spring.aspectj.Waiter+。继承或扩展指定类的所有类,同是包括指定类本身。
还可以用上逻辑运算符。and 、or 、not。例如aop 的事务要在Service层,并且有 Transactional 注解才生效的情况可以这么配置:(execution(* com.smart.service..*Service (..))) and (@annotation(org.springframework.transaction.annotation.Transactional))
继续代码,xml配置
main 方法
另外还有利用 Schema 的配置,上面的代码不变。
新增一个类,这里利用后置增强。
xml 中加入一下配置:
到这里就完事了,大家如果跟着代码一直走,做完这些例子应该是没有问题的。个人觉得使用 Schema 配置的方式会比较好。不过我在工作中,一般都是只是用来处理一下事务,和计算大数据查询。有的人也说可以用在记录日志上,我暂时没有碰到。无论是在在平安,华为还是360,我都没有遇到过。。一般都是logger.info()直接写方法里面,毕竟业务逻辑比较复杂的情况,经常需要打印日志,排查问题是使用,这点 aop 是没办法做到的。如果在面试上不建议说 aop 用来打印日志(个人看法)。后续我会补上对 aop 的深入理解和其他框架的文章。
另外,觉得我写的还行的,请顶一下,谢谢!!!
按照软件重构的思想,如果类的中出现项目的代码,应该考虑定义一个共同的抽象类,将这些相同的代码提取到抽象类中。例如最初学习 java 的时候,老师都很喜欢用 Horse、Pig、Dog 举例子,这些动物都用 run()、eat() 方法。而这些方法可以通过父类 Animal 继承,但是实际应用中并没有那么简单。
在写这边文章的时候,我看过不少的资料和书籍。书上一般都会说很多概念,和需要 aop 的术语,什么连接点、切点、增强、目标对象等等等。。。然后再把这些术语统一解释一次,接着加几副图片。不知道你们什么感觉,反正我觉得乱七八糟的,特别是新手,心好累的好么。然而今天我写的这边文章,我只介绍什么时候用,怎么用,专业术语,会简单说明一下。其他想深入理解的,可以查看其他资料,或者等我后续的文章(应该会更新,我也不知道我会不会偷懒)。
什么时候用aop?
例如,在修改新增的时候,常常需要添加事务,如果自己写的话,代码中会不停重复出现 conn.setAutoCommit(false)、conn.commit()、conn.rollback() 这样子的代码。
还有例如,在查询大数据的报表时,需要计算查询所用的时间,要获取 (查询后的毫秒数 - 查询前的毫秒数) 。
当需要写这些重复的代码,而这些代码也程序的业务逻辑又没有明显的关系时,为了是让代码更加清晰明了。则需要用到了aop。
这个是我个人理解,因为我个人认为即使没有 aop 也可以让代码正常的运行,至于什么计算时间,事务管理什么的,一样也可以正常得到结果。以下是比较专业一点的说法,不知道你们什么感觉,反正我当初刚学 aop 的时候是不懂的,一副要死的样子。
在程序正常的业务流中间像切面一样插入很多其他需要执行的代码
AOP 怎么用?
以餐厅的服务生为例子。假设服务生只做两件事情。第一、欢迎顾客,第二、对顾客提供服务
public interface Waiter { String greetTo(String name); String serveTo(String name); }
实现类如下
public class NaiveWaiter implements Waiter { public String greetTo(String name) { String result = "greetTo " + name; System.out.println(result); return result; } public String serveTo(String name) { String result = "serveTo " + name; System.out.println(result); return result; } }
例如每个服务生在履行职责的时候都打印工作记录。如果没有 aop 的话,就在上面的代码两个方法最开始执行的地方加入记录的代码。而使用 aop 首先要定义这个打印工作记录这个方法。 spring 提供了一个在执行方法之前会自动调用的接口:org.springframework.aop.MethodBeforeAdvice
public class NavieWaiterBeforeAdvice implements MethodBeforeAdvice { /** * method 调用的方法。 * args 入参 * target 执行方法的对象 */ public void before(Method method, Object[] args, Object target) throws Throwable { String name = (String) args[0]; String methodName = method.getName(); System.out.println("Work Start, customer:" + name); } }
现在已经把服务生的代码和记录工作的代码写好。而且想要记录工作的代码正常运行起来,需要使用 spring 给我们准备好的代理工厂。
public class BeforeAdviceTest { private BeforeAdvice beforeAdvice; // private BeforeAdvice beforeAdvice2; private Waiter target; private ProxyFactory pf; @Before public void init() { target = new NaiveWaiter(); beforeAdvice = new NavieWaiterBeforeAdvice(); // beforeAdvice2 = new NavieWaiterBeforeAdvice2(); pf = new ProxyFactory(); pf.setTarget(target); // pf.addAdvice(beforeAdvice2); pf.addAdvice(beforeAdvice); } @Test public void beforeAdvice() { Waiter proxy = (Waiter) pf.getProxy(); proxy.serveTo("111"); proxy.greetTo("222"); } }
由上面的代码可以见,使用 spring 提供的代理后,我们直接执行,就自动会有操作记录打印出来。
有人或者会问,可以不可以定义两个方法呢? 我可以很负责的告诉你,是可以的。而且两个方法的执行顺序是根据加如到代理的顺序是一致的。把上面代码的注释打开,然后加增一个类。
public class NavieWaiterBeforeAdvice2 implements MethodBeforeAdvice { public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("the second before advice..."); } }
又有人会问,目标执行前有接口继承,执行后呢?或者
4000
说前后都需要呢?答案是:都有!!
顺便提一下,这里有两个专业术语,服务生的那两个主要业务逻辑的方法,叫切点。而操作记录打印叫做增强,因为是在业务逻辑前执行的,所有叫前置增强。
还有其他的增强,用法都差不多的,如下:
前置增强 org.springframework.aop.MethodBeforeAdvice
后置增强 org.springframework.aop.AfterReturningAdvice
环绕增强(前后都有执行的代码是使用) org.aopalliance.intercept.MethodInterceptor
异常抛出增强:org.springframework.aop.ThrowsAdvice
引介增强(在目标类中添加新方法和属性,这个一直没用过): org.springframework.aop.IntroductionInterceptor
现在要说说怎么在 xml 中配置,毕竟如果切点(服务生)很多的话,应该尽可能的少动代码。
xml 配置 AOP
保留上面的代码 NavieWaiterBeforeAdvice、Waiter 、NaiveWaiter 不动。然后加入一个切面(切面可以理解成切点和增强连接的地方)。
public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor { private static final long serialVersionUID = 7622885210552374524L; public boolean matches(Method method, Class<?> targetClass) { return Objects.equals(method.getName(), "greetTo"); } @Override public ClassFilter getClassFilter() { return new ClassFilter() { public boolean matches(Class<?> clazz) { return Waiter.class.isAssignableFrom(clazz); } }; } }
然后加入xml配置,以下提供三种配置方式,只需要选择一种即可。
这个是最原始的配置方式。
<bean id="navieWaiterTarget" class="com.smart.spring.demo2.NaiveWaiter"></bean> <bean id="navieWaiterBeforeAdvice" class="com.smart.spring.demo2.NavieWaiterBeforeAdvice"></bean> <!-- 在切面处配置一个前置增强 --> <bean id="greetingAdvisor" class="com.smart.spring.demo2.GreetingAdvisor"> <property name="advice" ref="navieWaiterBeforeAdvice"></property> </bean> <!-- 配置 proxyFractory 的公共信息 --> <bean id="parent" abstract="true" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="interceptorNames"> <array> <value>greetingAdvisor</value> </array> </property> <property name="proxyTargetClass" value="true"></property> </bean> <!-- 配置代理 --> <bean id="waiter" parent="parent"> <property name="target" ref="navieWaiterTarget"></property> </bean>
正则表达式的配置方式
<bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice" ref="navieWaiterBeforeAdvice"></property> <property name="patterns"> <list> <value>.*greet.*</value> </list> </property> </bean> <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="interceptorNames"> <array> <value>regexpAdvisor</value> </array> </property> <property name="target" ref="navieWaiterTarget"></property> <property name="proxyTargetClass" value="true"></property> </bean>
以 bean 的名字模糊匹配
<bean id="navieWaiter" class="com.smart.spring.demo2.NaiveWaiter"></bean> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>*Waiter</value> </list> </property> <property name="interceptorNames"> <array> <value>greetingAdvisor</value> </array> </property> <property name="optimize" value="true"></property> </bean>
main 方法代码
public class Demo2Test { public static void main(String[] args) { ApplicationContext cxt = new ClassPathXmlApplicationContext("applicationContext.xml"); // 如果是第一种或者第二种 xml 配置,改成,waiter,第三种用 navieWaiter Waiter waiter = (Waiter) cxt.getBean("navieWaiter"); waiter.greetTo("aaa"); waiter.serveTo("bbb"); } }
在 jdk 5.0之后,又加入了注解的方式。
以下例子为了避免混淆,和上面的代码没有关系(全部重新写!!)。依然用 Waiter 为例。
public interface Waiter { String greetTo(String name); String serveTo(String name); }
public class NaiveWaiter implements Waiter { public String greetTo(String name) { String result = "greetTo " + name; System.out.println(result); return result; } public String serveTo(String name) { String result = "serveTo " + name; System.out.println(result); return result; } }
// 创建切面 @Aspect public class PreGreetintAspect { // 前置增强 @Before("execution(* greetTo(..))") public void beforeGreeting() { System.out.println("before advice for greeting..."); } }
execution这个为切点表达式
*:匹配任意字符,但只能匹配上写问的一个元素
..:匹配任意字符,可以匹配上下问的所有元素,但是在表示类是要和* 一起用。例如 ..*
+:表示按类型匹配指定类的所有类,必须跟在类名的后面,如 com.smart.spring.aspectj.Waiter+。继承或扩展指定类的所有类,同是包括指定类本身。
还可以用上逻辑运算符。and 、or 、not。例如aop 的事务要在Service层,并且有 Transactional 注解才生效的情况可以这么配置:(execution(* com.smart.service..*Service (..))) and (@annotation(org.springframework.transaction.annotation.Transactional))
继续代码,xml配置
<!-- 扫描含有 @Aspect 注解的类,然后自动加入到代理中 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <bean id="waiter1" class="com.smart.spring.aspectj.NaiveWaiter"></bean> <bean id="preGreetintAspect" class="com.smart.spring.aspectj.PreGreetintAspect"></bean>
main 方法
String path = "/com/smart/spring/aspectj/applicationContext.xml"; ApplicationContext cxt = new ClassPathXmlApplicationContext(path); Waiter waiter1 = (Waiter) cxt.getBean("waiter1"); waiter1.greetTo("aaa"); waiter1.serveTo("bbb");
另外还有利用 Schema 的配置,上面的代码不变。
新增一个类,这里利用后置增强。
public class AfterServingAspect { public void afterServing(String result) { System.out.println("after advice for serving, result: " + result); } }
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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <bean id="waiter1" class="com.smart.spring.aspectj.NaiveWaiter"></bean> <bean id="preGreetintAspect" class="com.smart.spring.aspectj.PreGreetintAspect"></bean> <!-- 这里是新增的部分 --> <bean id="afterServingAspect" class="com.smart.spring.aspectj.AfterServingAspect"></bean> <aop:config> <aop:pointcut expression="execution(* serveTo(..))" id="servingPointcut"/> <aop:aspect ref="afterServingAspect"> <aop:after-returning method="afterServing" pointcut-ref="servingPointcut" returning="result"/> </aop:aspect> </aop:config> </beans>
到这里就完事了,大家如果跟着代码一直走,做完这些例子应该是没有问题的。个人觉得使用 Schema 配置的方式会比较好。不过我在工作中,一般都是只是用来处理一下事务,和计算大数据查询。有的人也说可以用在记录日志上,我暂时没有碰到。无论是在在平安,华为还是360,我都没有遇到过。。一般都是logger.info()直接写方法里面,毕竟业务逻辑比较复杂的情况,经常需要打印日志,排查问题是使用,这点 aop 是没办法做到的。如果在面试上不建议说 aop 用来打印日志(个人看法)。后续我会补上对 aop 的深入理解和其他框架的文章。
另外,觉得我写的还行的,请顶一下,谢谢!!!
相关文章推荐
- Spring in Action 入门之面向切面编程AOP
- SpringAOP入门-面向切面编程
- spring中面向切面编程AOP的简单应用
- Spring AOP面向切面编程一个简单例子和在配置过程中出现错误
- Spring in Action 入门之面向切面编程AOP
- Spring:AOP面向切面
- Spring学习笔记3--面向切面(AOP)的例子
- Spring AOP 面向切面编程相关注解
- 一个简单的Spring的AOP例子(JAVA面向切面编程)
- 解释通知Spring AOP 面向切面编程
- 黑马程序员--Spring Aop 面向切面编程,实现前置通知
- spring心得7--spring第二大特点AOP(面向切面)讲解
- Spring面向切面编程AOP的个人理解
- Spring 面向切面开发(AOP)
- Spring AOP(面向切面编程)【AOP快速入门】
- Spring面向切面的简单示例(基于XML文件配置)
- [Spring]面向切面编程AOP【学习笔记】
- Spring AOP 简单入门示例
- AOP入门的简单例子(Spring AOP实现)
- Spring_AOP_简单入门示例