预讲内容
1、讲解AOP的主要作用
2、Spring中AOP的操作实现
具体内容
Spring中两大核心组成:IOC&DI、AOP。AOP最大的用处在于事务处理。
6.1、AOP编程简介(理论)
在以后如果使用MyEclipse开发项目,对于所有的AOP几乎不需要做太多处理。由开发工具帮助用户完成,掌握AOP的编程的核心理念比实现更为重要。
在整个的开发之中曾经强调过,业务层在项目之中要负责一下的操作:
·调用数据层进行处理;
·进行事务的处理;
·关闭数据库的操作连接。
如果说现在以一个数据的增加操作为例
![](https://img.haomeiwen.com/i12314480/6f77e9b5e8187e4b.png)
这种的结构就相当于所有的操作都使用了硬编码的形式完成。类似于如下形式
范例:增加处理
public class MemberServiceImpl{
public boolean insert(){
//1、执行日志处理
//2、调用数据层操作
//3、执行事务的提交处理
//4、数据的关闭操作
return false;
}
}
在之前的代码都是这样完成的,但实际上这样并不好,因为整个业务层只关心处理的核心操作。
如果现在你的开发之中遇见了许多这样要编写非业务处理的功能的时候,那么我们首先想到的是代理模式,让代理模式完成所有辅助性操作,而后静态代理会有类型限制,应该使用动态代理机制完成,但是动态代理机制麻烦。在整个动态代理的操作过程中依然需要由用户自己处理对象的产生等操作。
![](https://img.haomeiwen.com/i12314480/96950fe6f7b63d2c.png)
而且同时在整个的设计过程之中并不只是一个简单的代理类就可以完成所有的操作功能,需要考虑如下几种情况:
·在数据层调用之前如何执行(执行数据层之前进行记录);
·在数据层调用之后如何执行(执行数据层之后手工进行事务的提交);
·在业务层操作完成之后返回结果上进行处理。
·还有可能出现异常之后需要进行记录。
要想准确的实现代理设计操作,至少需要四个处理类,这四个类的关系如何处理?于是最早的时候(JDK1.5之前)为了可以处理这样的问题,引入了一系列的Advide操作接口形式
![](https://img.haomeiwen.com/i12314480/774a40926439fe4e.png)
所有的类都写完之后利用Spring的配置文件将这些操作完整的融合在一起,这样当业务层调用时,会自动触发以上的操作子类依次执行。
这样编码的缺点:
·Spring本身就是一个容器,而以上的操作需要使用明确的接口及明确的实现子类来明确的定义,这样一来,结构又固定了,但又希望让定义这些代理操作的时候不受到接口的限制,于是在Spring接下来的版本之中开始使用Annotation的配置模式解决了此类问题,也就是说利用特定的注解来描述具体的代理操作方法的作用。
任何的开发都应该避免强烈的耦合性问题,尤其是在Spring之中,最大的特点在于String类操作上,所以为了解决硬编码的方式配置代理结构是不建议使用的,在Spring之中引入了Apache推出的AspectJ操作语法形式,以字符串的形式定义代理操作的切入点。并且使用一系列的通配符来找到需要进行切入的操作程序。
现在可以得出一个结论:如果要想实现一个优秀的代理设计模式,那么必须要使用面向方面的编程,即:将不同的切入代码淡入定义,而后组织在一个程序网上。所以这就构成了整个AOP编程的理论来源,而在整个AOP之中包含有如下几个重要的组成概念:
·切入点:可以理解为所有要操作的方法定义,要求业务层的方法必须统一风格;
·分离点:将一些不能再分的组件单独提取出去定义为单独的操作功能;
·横切关注点:指的是将所有与开发无关的程序组成类单独提取而后组织运行;
·织入:将所有的切入点、关注点的代码组成在一张完整的程序结构之中,而Spring就完成了这样的组织操作;
![](https://img.haomeiwen.com/i12314480/6e213747bd8b906a.png)
但是并不是意味着所有的操作都可以由用户任意放肆的去定义切入点,在整个Spring里面依然采用通知的形式完成,也就是说当触发到了某些操作之后那么久自然进行一些固定的处理,所以在整个SpringAOP之中包含有如如下的几类通知形式:
·前置通知(Before Advice):在某一操作执行之前负责处理;
·后置通知(After Advice):在某一操作执行之后负责处理,但是后置通知需要考虑一下几种子类:
|-后置返回通知(After Returning Advice):负责处理结果的时候进行拦截;
|-后置异常通知(After Throwing Advice):当出现异常之后进行拦截;
|-后置最终通知(After Finally Advice)不管执行到最后是否出现异常,都会执行拦截;
·环绕通知(Around Advice):可以在具体的执行操作之前、之后、异常出现处理等地方任意编写,可以说包含了如上的几种通知的综合体。
6.2、AOP基本操作
AOP是面向方面的编程,在实际开发之中,AOP都会工作在业务层;因为业务层要调用数据层,而业务层也要完成所有辅助性的数据层操作。
范例:定义业务层操作接口
package cn.mldn.service;
public interface IMemberService {
public boolean insert(Member member);
}
package cn.mldn.service.impl;
@Service
public class MemberServiceImpl implements IMemberService {
@Override
public boolean insert(Member member) {
System.out.println("【数据层调用】member"+ member);
return false;
}
}
package cn.mldn.vo;
public class Member {
private String mid;
private String name;
@Override
public String toString() {
return "Member{" +
"mid='" + mid + '\'' +
", name='" + name + '\'' +
'}';
}
}
此时的业务层里只关心有核心的业务功能,核心的功能就是调用了业务层的代码。
随后所有的辅助性操作功能都能通过Spring容器动态配置。
范例:为项目配置Annotation的扫描支持
<context:annotation-config/>
<context:component-scan base-package="cn.mldn"/>
但是这种操作并不严格,还需要准备出相应的辅助性功能,这些辅助性功能可以单独定义在一个切面的处理类之中。
范例:定义切面处理类
package cn.mldn.aop;
@Component
public class ServiceAspect {
public void serviceBefore(){
System.out.println("【AOP切面】执行日志记录操作");
}
public void serviceAfter(){
System.out.println("【AOP切面】执行事务处理操作");
}
}
默认情况下Spring之中并不会开启切面的操作模式,所以如果想要开启切面必须引入aop的命名空间。
范例:修改applicationContext.xml文件
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
下面的任务就是在applicationContext.xml文件之中利用配置文件组织切面
范例:定义引用切面
<aop:config>
<!--1.定义程序的切入点-->
<aop:pointcut id="pointcut" expression="execution(public * cn.mldn..*.*(..))"/>
<!--定义要使用的面向方面的处理类-->
<aop:aspect ref="serviceAspect">
<aop:before method="serviceBefore" pointcut-ref="pointcut"/>
<aop:after method="serviceAfter" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
此时的业务层没有出现任何的与业务层的核心操作有关的功能代码。而所有的辅助功能都会以切面的形式出现在项目的执行之中,那么这些切面就相当于最早的代理类。
输出结果
【AOP切面】执行日志记录操作
【数据层调用】member = Member{mid='mldn', name='hello'}
【AOP切面】执行事务处理操作
false
![](https://img.haomeiwen.com/i12314480/8008d788c45cefb1.png)
现在整个配置过程之中,其基本的操作意义都可以看明白,但是最为重要的是切入点的设置:
execution(* cn.mldn..*.*(..))
这种语法就是AspectJ定义的切入点操作语法。此语法结构如下:
execution(修饰符匹配? 返回值类型 操作类型匹配? 名称匹配(参数匹配) 抛出异常匹配)
对于以上的语法详细解释如下:
·修饰符:public、private,只能出现0次或1次(一般省略);
·返回值(execution(* cn.mldn..*.*(..)))):前面单独的*,如果使用的是*表示返回任何类型;
·名称匹配:(cn.mldn..*.*)表示的是具体要使用此切面的程序类,
“cn.mldn”则表示在这个包中
“..“表示在任意结构子包
“*.*“表示的是任意方法,第一个*表示类名称,第二个*表示方法名称
“(..)"方法参数,使用".."表示匹配任意多个参数,如果使用的是“*”表示匹配任意一个参数。
6.3、AOP深入操作
在之前知识进行了最简单的AOP的拦截处理,只在操作之前与之后进行拦截,但是在整个AOP中有一个参数问题要解决。
范例:定义参数拦截
public class ServiceAspect {
public void serviceBefore(){
System.out.println("【AOP切面】执行日志记录操作");
}
public void serviceBefore2(Object arg){
System.out.println("【AOP切面】执行增加前的操作,参数="+arg);
}
public void serviceAfter(){
System.out.println("【AOP切面】执行事务处理操作");
}
}
此时对于serviceBefore2()这个方法上由于有参数的定义了,那么久必须修改切入点表达式
范例:定义切入点表达式
<context:annotation-config/>
<context:component-scan base-package="cn.mldn"/>
<aop:config proxy-target-class="true"/>
<aop:config>
<aop:pointcut id="cut" expression="execution(* cn.mldn..*.*(..)) and args(vo))"/>
<aop:aspect ref="serviceAspect">
<aop:after method="serviceAfter" pointcut="execution(* cn.mldn..*.*(..))"/>
<aop:before method="serviceBefore2" pointcut-ref="cut" arg-names="vo"/>
</aop:aspect>
</aop:config>
args(vo)其中的参数“vo”要与arg-names="vo"中的“vo“一致
除了在操作之前的拦截,也可以针对于操作的返回结果进行拦截。
范例:针对返回结果拦截
@Component
public class ServiceAspect {
public void serviceBefore(){
System.out.println("【AOP切面】执行日志记录操作");
}
public void serviceBefore2(Object arg){
System.out.println("【AOP切面】执行增加前的操作,参数="+arg);
}
public void serviceAfter(){
System.out.println("【AOP切面】执行事务处理操作");
}
public void serviceAfterReturning(Object val){ //表示操作的结果
System.out.println("【AOP切面】操作完成返回结果:"+val);
}
}
但是此时依然需要在applicationContext.xml文件里面配置操作形式。
范例:修改applicationContext.xml文件
<aop:after-returning method="serviceAfterReturning" pointcut="execution(* cn.mldn..*.*(..))" returning="haha" arg-names="haha"/>
除了返回结果的拦截之外,还可以进行异常处理的拦截操作。
范例:修改MemberServiceImpl的方法
@Service
public class MemberServiceImpl implements IMemberService {
@Override
public boolean insert(Member member){
throw new NullPointerException();
}
}
随后增加新的拦截处理方法操作。
范例:修改ServiceAspect程序类
public void serviceAfterThrowing(Exception exp){ //表示操作的结果
System.out.println("【AOP切面】操作出现异常:"+exp);
}
配置文件
<aop:after-throwing method="serviceAfterThrowing" pointcut="execution(* cn.mldn..*.*(..))" arg-names="e" throwing="e"/>
"arg-names"与"throwing"的名称要统一(内容要统一)
以上的几个拦截起已经可以成功的覆盖了所有AOP可以处理的范畴,但是为了考虑简化问题,在整个AOP处理中,有提供一种环绕通知,即:可以一个方法处理所有AOP操作,这种操作更像代理结构。
范例:修改ServiceAspect程序类增加环绕处理
在进行环绕处理的时候需要注意一点,它必须考虑到接受参数的情况,而接受的参数类型只能是一种类型:ProceedingJoinPoint,通过此类型可以取得全部的提交参数信息。
public Object serviceAround(ProceedingJoinPoint point) throws Throwable{
System.out.println("【AOP切面】数据层方法调用之前,参数:"+ Arrays.toString(point.getArgs()));
Member member = new Member();
member.setMid("asd");
member.setName("123");
Object retVal = point.proceed(new Object[]{member});//
System.out.println("【AOP切面】数据层调用之后,返回值:"+retVal);
return true; //可以自己修改返回值
}
在整个环绕拦截之中,用户可以任意的修改传递的参数数据,也可以修改返回的结果。
范例:在applicationContext.xml文件之中配置环绕拦截
<aop:around method="serviceAround" pointcut="execution(* cn.mldn..*.*(..))"/>
在整个给出的AOP操作之中,环绕通知的操作是功能最强大的,其它的拦截都只能够做一些基础的信息记录,而环绕甚至可以对传入参数的返回结果进行控制。
6.4、利用Annotation配置AOP
以上所讲解的AOP操作如果在以后的开发之中基本上都是固定,但是如果是自己写的代码,通过配置文件编写就太麻烦了,所以可以使用基于Annotation的配置完成。
范例
配置文件
<aop:aspectj-autoproxy/>
随后的事情就是需要在ServiceAspect类中编写需要使用的Annotation
范例:修改ServiceAspect程序类
@Component
@Aspect
public class ServiceAspect2 {
@Before(value = "execution(* cn.mldn..*.*(..))")
public void serviceBefore(){
System.out.println("【AOP切面】执行日志记录操作");
}
@Before(value = "execution(* cn.mldn..*.*(..)) and args(param))",argNames = "param")
public void serviceBefore2(Object arg){
System.out.println("【AOP切面】执行增加前的操作,参数="+arg);
}
@After(value = "execution(* cn.mldn..*.*(..))")
public void serviceAfter(){
System.out.println("【AOP切面】执行事务处理操作");
}
@AfterReturning(value = "execution(* cn.mldn..*.*(..))",argNames = "ret",returning = "ret")
public void serviceAfterReturning(Object val){ //表示操作的结果
System.out.println("【AOP切面】操作完成返回结果:"+val);
}
@AfterReturning(value = "execution(* cn.mldn..*.*(..))",argNames = "exp",returning = "exp")
public void serviceAfterThrowing(Exception exp){ //表示操作的结果
System.out.println("【AOP切面】操作出现异常:"+exp);
}
@Around(value = "execution(* cn.mldn..*.*(..))")
public Object serviceAround(ProceedingJoinPoint point) throws Throwable{
System.out.println("【AOP切面】数据层方法调用之前,参数:"+ Arrays.toString(point.getArgs()));
Member member = new Member();
member.setMid("asd");
member.setName("123");
Object retVal = point.proceed(new Object[]{member});//
System.out.println("【AOP切面】数据层调用之后,返回值:"+retVal);
return true; //可以自己修改返回值
}
}
输出信息
【AOP切面】数据层方法调用之前,参数:[Member{mid='mldn', name='hello'}]
【AOP切面】执行日志记录操作
【AOP切面】执行增加前的操作,参数=Member{mid='asd', name='123'}
【数据层调用】member = Member{mid='asd', name='123'}
【AOP切面】数据层调用之后,返回值:false
【AOP切面】执行事务处理操作
【AOP切面】操作完成返回结果:true
true
如果在实际开发之中需要进行一些辅助功能编写的时候,建议使用Annotation的配置完成,这样的代码是最简化的也是最直观的。
网友评论