您的位置:首页 > 编程语言 > Java开发

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 怎么用?

以餐厅的服务生为例子。假设服务生只做两件事情。第一、欢迎顾客,第二、对顾客提供服务

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 aop 面向切面 java