面向切面编程(AOP)是一种通过预编译方式和运行期 动态代理 实现在不修改源代码的情况下给程序动态添加功能的技术,是对传统的面向对象编程(OOP)的一个补充。他家复杂的需求分解出不同方法,将散布在系统中的公共功能集中解决,使每个事物逻辑位于一个位置,使代码不分散,业务模块更简洁,只包含核心业务代码,便于维护和升级。其实现方法就是动态代理设计模式,通过代理对象来调用原对象的方法,代理对象方法前后都可以插入代码,这些代码就是增强处理。一下将首先介绍使用动态代理模式实现AOP,然后介绍Spring实现AOP的两种方式。
首先先了解关于AOP的一些相关术语,增强(通知)(Advice):切面必须完成的工作;切入点(Pointcut):通过切入点定位到连接点;连接点(Joinpoint):程序执行的某个特定位置(连接点相当于数据库中的记录,切入点相当于查询条件);切面(Aspect):横切关注点,跨越应用程序多个模块的功能,被模块化的特殊对象;代理(Proxy):向目标对象应用增强之后创建的对象;目标对象(Target):被增强的对象;织入(Weaving):将增强的切面应用到目标对象中的过程。
一,利用动态代理实现AOP
有一个接口及其实现类:
package cn.pb.testAOP;
public interface Calculator {
public int add(int i,int j);
public int mul(int i,int j);
}
package cn.pb.testAOP;
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
return i+j;
}
@Override
public int mul(int i, int j) {
return i*j;
}
}
现在要实现在调用add方法输出结果前在控制台输出:Begining–>Parameter:[1, 2];Method:add;同时在输出结果后还要输出:Endding–>Result:3。同时在调用mul方法时也需要输出同样的格式,你可能会想到在所有的方法前后添加同样的输出语句,但当方法增多,或者在其他的类中也需要这个结果,逐条的写将会使代码重复繁琐。此时可以使用代理模式为此类创建一个代理,在代理类中写这些重复的代码而不需要改动上面原始的类,通过代理类调用该类的方法就能够实现在每一个方法被调用时都可以输出想要的结果。代理类相当于对该类进行了包装并添加了额外的方法。
创建动态代理CalaultatorProxy:
package cn.pb.testAOP;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class CalculatorProxy {
//要代理的对象
private Calculator target;
public CalculatorProxy(Calculator target) {
this.target=target;
}
public Calculator getProxy(){
Calculator proxy=null;
//代理对象由哪一个类加载器负责加载
ClassLoader loader=target.getClass().getClassLoader();
//代理对象的类型,即其中有哪些方法
Class[] interfaces=new Class[]{Calculator.class};
//当调用代理对象的方法时,该执行的方法
InvocationHandler h=new InvocationHandler() {
/**
* proxy:正在返回的那个代理对象
* method:正在被调用的方法
* args:调用方法时传入的参数
* */
@Override
public Object invoke(Object proxy, Method method, Object[] args){
String methodName=method.getName();
Object result=null;
try {
System.out.println("Begining-->Parameter:"+Arrays.asList(args)+";Method:"+methodName);//写与此位置的是一个前置增强
result = method.invoke(target, args);
System.out.println("Endding-->Result:"+result);//写与此位置的是一个后置增强
} catch (Exception e) {
//写与此位置的是一个异常增强
e.printStackTrace();
}
//写与此位置的是一个返回增强
return result;
}
};
proxy=(Calculator) Proxy.newProxyInstance(loader, interfaces, h);
return proxy;
}
}
增强类型时根据增强位置决定的,已在代码中指出,此外还有环绕增强是指所有的增强类型同时存在的情况,此种情况较为少见。
编写测试类:
package cn.pb.testAOP;
public class Test {
public static void main(String[] args) {
Calculator cal=new CalculatorImpl();
Calculator proxy=new CalculatorProxy(cal).getProxy();
int result=proxy.add(1, 2);
System.out.println(result);
int result1=proxy.mul(2, 3);
System.out.println(result1);
}
}
输出结果:
Begining-->Parameter:[1, 2];Method:add
Endding-->Result:3
3
Begining-->Parameter:[2, 3];Method:mul
Endding-->Result:6
6
说明在调用两个方法前后都执行了两个输出语句,在这里只是简单的用两个输出语句演示动态代理的功能,其实他代表着一个增强的功能,即可以在执行主要方法的前后添加额外的功能。
第一种方法揭示了利用Java提供的接口如何实现动态代理实现AOP,Spring对他进行了封装,提供了自己的方法:基于配置文件的方式配置AOP和基于注解的方式配置AOP。
二,基于注解驱动的方式配置AOP
一般步骤:
1)导入jar包,配置文件中加入aop的命名空间
2)在配置文件中加入如下配置:
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
3)把横切关注点代码抽象到切面的类中
切面首先是一个IOC中的bean,即加入@Componenet注解
切面还需要加入@Aspect注解
4)在类中申明各种增强(通知)
声明一个方法
在方法前加入@Before注解(前置通知)
@Before(“execution(public int cn.pb.testAOP.Calculator.*(int, int))”) 表达式规则如前
5)同理在方法中加入JoinPoint类型的参数,可以访问被增强类的细节
如下是一个切面类:
package cn.pb.testAOP;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
//把这个类申明为一个切面:需要把该类放入到IOC容器中,再声明为一个切面
@Component
@Aspect
public class LoggingAspect {
/**
* 定义一个方法,用于声明切入点表达式,一般的,该方法中不需要添加其他的代码
* */
@Pointcut("execution(public int cn.pb.testAOP.Calculator.*(int, int))")
public void pointcutExpression(){}
//声明该方法是一个前置增强(通知)
@Before("pointcutExpression()")
public void beforeMethod(JoinPoint jp){
System.out.println("Begining-->Parameter:"+Arrays.asList(jp.getArgs())+";Method:"+jp.getSignature().getName());
}
//声明该方法是一个后置增强(通知),此处不能获得返回的结果
@After("pointcutExpression()")
public void afterMethod(JoinPoint jp){
System.out.println("Endding-->Result:");
}
//声明该方法是一个返回增强(通知)
@AfterReturning(value="pointcutExpression()",returning="result")
public void returnMethod(JoinPoint jp,Object result){
System.out.println("Endding-->Method:"+jp.getSignature().getName()+",Result:"+result);
}
//声明该方法是一个异常增强(通知)
@AfterThrowing(value="pointcutExpression()",throwing="ex")
public void throwingMethod(JoinPoint jp,Object ex){
System.out.println("The method <"+jp.getSignature().getName()+"> throw exception:"+ex);
}
}
需要注意的是此切面类和被增强的类都应该处于Spring容器的管理之下。代码中重用了切点表达式,因此将表达式提出并放在了@Pointcut注解下,切点表达式匹配规则举例:
》public * addUser(User) 《
“*”表示匹配所有类型的返回值
匹配所有修饰符为public的addUser方法,且参数类型为User
》public void * (User) 《
“*”表示匹配所有方法名
匹配所有修饰符为public且无返回值的所有方法,且参数类型为User
》public void addUser(..) 《
“..”表示匹配所有参数个数和类型
》* com.pb.service.* .*(..) 《
匹配com.pb.service包下所有类的所有方法
》* com.pb.service..*(..) 《
匹配com.pb.service包及子包下所有类的所有方法
测试类:
package cn.pb.testAOP;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext atx=new ClassPathXmlApplicationContext("ConfigurationAOP.xml");
Calculator cal=(Calculator) atx.getBean("calculatorImpl");
int result=cal.add(1, 2);
System.out.println(result);
int result1=cal.mul(2, 3);
System.out.println(result1);
}
}
输出结果:
Begining-->Parameter:[1, 2];Method:add
Endding-->Result:
Endding-->Method:add,Result:3
3
Begining-->Parameter:[2, 3];Method:mul
Endding-->Result:
Endding-->Method:mul,Result:6
6
三,基于配置文件的方式配置AOP
编写切面类:
package cn.pb.testAOP;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
public class LoggingAspect {
public void beforeMethod(JoinPoint jp){
System.out.println("Begining-->Parameter:"+Arrays.asList(jp.getArgs())+";Method:"+jp.getSignature().getName());
}
public void afterMethod(JoinPoint jp){
System.out.println("Endding-->Result:");
}
public void returnMethod(JoinPoint jp,Object result){
System.out.println("Endding-->Method:"+jp.getSignature().getName()+",Result:"+result);
}
public void throwingMethod(JoinPoint jp,Object ex){
System.out.println("The method <"+jp.getSignature().getName()+"> throw exception:"+ex);
}
}
编写配置文件:
<bean id="calculator" class="cn.pb.testAOP.CalculatorImpl"></bean>
<!-- 配置切面的bean -->
<bean id="aopTest" class="cn.pb.testAOP.LoggingAspect"></bean>
<aop:config>
<!-- 定义切入点 -->
<aop:pointcut id="pointcut" expression="execution(public * cn.pb.testAOP.Calculator.*(..))"/>
<!-- 创建切面 -->
<aop:aspect ref="aopTest">
<!-- 前置增强 -->
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
<!-- 后置增强 -->
<aop:after method="afterMethod" pointcut-ref="pointcut"/>
<!-- 返回增强 -->
<aop:after-returning method="returnMethod" pointcut-ref="pointcut" returning="result"/>
<!-- 异常增强 -->
<aop:after-throwing method="throwingMethod" pointcut-ref="pointcut" throwing="ex"/>
</aop:aspect>
</aop:config>
此时的返回值结果是与上述方法是一致的。若存在多个切面,可以指定切面的优先级,若使用注释方法,可在切面类前添加注释@Order(i),i的值越小表示优先级越高;若使用xml文件的方式,可在aspect节点中添加属性order=”i”定义优先级。