Spring之AOP基本概念及配置AOP.docx
《Spring之AOP基本概念及配置AOP.docx》由会员分享,可在线阅读,更多相关《Spring之AOP基本概念及配置AOP.docx(16页珍藏版)》请在冰点文库上搜索。
Spring之AOP基本概念及配置AOP
Spring之AOP基本概念及配置AOP
为什么使用AOP
传统方法
AOP前前奏
首先考虑一个问题,假设我们要设计一个计算器,有如下两个需求:
-在程序运行期间追踪正在放生的活动-希望计算器只能处理正数的运算通常我们会用如下代码进行实现:
定义一个接口:
publicinterfaceArithmeticCalculator{
intadd(inti,intj);
intsub(inti,intj);
intmul(inti,intj);
intdiv(inti,intj);
}
实现类(在实现类中加入具体方法的实现,即正数的操作和日志功能,通过System.out.println输出实现):
publicclassArithmeticCalculatorLoggingImplimplementsArithmeticCalculator{
@Override
publicintadd(inti,intj){
System.out.println("Themethodaddbeginswith["+i+","+j+"]");
intresult=i+j;
System.out.println("Themethodaddendswith"+result);
returnresult;
}
@Override
publicintsub(inti,intj){
System.out.println("Themethodsubbeginswith["+i+","+j+"]");
intresult=i-j;
System.out.println("Themethodsubendswith"+result);
returnresult;
}
@Override
publicintmul(inti,intj){
System.out.println("Themethodmulbeginswith["+i+","+j+"]");
intresult=i*j;
System.out.println("Themethodmulendswith"+result);
returnresult;
}
@Override
publicintdiv(inti,intj){
System.out.println("Themethoddivbeginswith["+i+","+j+"]");
intresult=i/j;
System.out.println("Themethoddivendswith"+result);
returnresult;
}
}
传统方法存在的问题
-代码混乱:
越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀.每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点.-代码分散:
以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码.如果日志需求发生变化,必须修改所有模块.
使用动态代理解决上述问题
代理设计模式的原理:
使用一个代理将对象包装起来,然后用该代理对象取代原始对象.任何对原始对象的调用都要通过代理.代理对象决定是否以及何时将方法调用转到原始对象上.
/**
*动态代理
*@authorMegustas
*
*/
publicclassArithmeticCalculatorLoggingProxy{
//要代理的对象
privateArithmeticCalculatortarget;
publicArithmeticCalculatorLoggingProxy(ArithmeticCalculatortarget){
super();
this.target=target;
}
//返回代理对象
publicArithmeticCalculatorgetLoggingProxy(){
ArithmeticCalculatorproxy=null;
//代理对象由哪一个类加载器加载
ClassLoaderloader=target.getClass().getClassLoader();
//代理对象的类型,即其中有哪些方法
Class[]interfaces=newClass[]{ArithmeticCalculator.class};
//当调用代理对象其中的方法时,该执行的代码
InvocationHandlerh=newInvocationHandler(){
/**
*proxy:
正在返回的那个代理对象,一般情况下,在invoke方法中不使用该对象
*method:
正在被调用的方法
*args:
调用方法传入的参数
*/
@Override
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)
throwsThrowable{
StringmethodName=method.getName();
//打印日志,此种方式对日志进行维护和更改就十分简洁
System.out.println("[before]Themethod"+methodName+"beginswith"+Arrays.asList(args));
//调用目标方法
Objectresult=null;
try{
//前置通知
//invoke:
通过函数名反射相应的函数
result=method.invoke(target,args);
//返回通知,可以访问到方法的返回值
}catch(NullPointerExceptione){
e.printStackTrace();
//异常通知,可以访问到方法出现的异常
}
//后置通知.因为方法可以能会出异常,所以访问不到方法的返回值
//打印日志
System.out.println("[after]Themethodendswith"+result);
returnresult;
}
};
/**
*loader:
代理对象使用的类加载器。
*interfaces:
指定代理对象的类型.即代理代理对象中可以有哪些方法.
*h:
当具体调用代理对象的方法时,应该如何进行响应,实际上就是调用InvocationHandler的invoke方法
*以下是代理对象,不同于基本对象通过new生成
*/
proxy=(ArithmeticCalculator)Proxy.newProxyInstance(loader,interfaces,h);
returnproxy;
}
}
但是写动态代理,难度却不小,不是很容易掌握。
AOP简介
AOP(Aspect-OrientedProgramming,面向切面编程):
是一种新的方法论,是对传统OOP(Object-OrientedProgramming,面向对象编程)的补充.
AOP的主要编程对象是切面(aspect),而切面模块化横切关注点(即对象里放入的是一个个横切关注点的方法).
在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类.这样一来横切关注点就被模块化到特殊的对象(切面)里.
AOP的好处:
每个事物逻辑位于一个位置,代码不分散,便于维护和升级业务模块更简洁,只包含核心业务代码.
这里的一个个需求,例如验证参数、日志功能等都是横切关注点,我们将横切关注点抽取出来
通过切面和业务逻辑的结合实现目标功能,即为面向切片编程。
AOP术语
切面(Aspect):
横切关注点(一个个具体需求)(跨越应用程序多个模块的功能)被模块化的特殊对象
通知(Advice):
切面必须要完成的工作(比如说切面需要完成验证,即切面中的每一个方法)
目标(Target):
被通知的对象(即业务逻辑)
代理(Proxy):
向目标对象应用通知之后创建的对象(将切面和目标混合)
连接点(Joinpoint):
程序执行的某个特定位置:
如类某个方法调用前、调用后、方法抛出异常后等。
连接点由两个信息确定:
方法表示的程序执行点;相对点表示的方位。
例如ArithmethicCalculator#add()方法执行前的连接点,执行点为ArithmethicCalculator#add();方位为该方法执行前的位置
切点(pointcut):
每个类都拥有多个连接点:
例如ArithmethicCalculator的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。
AOP通过切点定位到特定的连接点。
类比:
连接点相当于数据库中的记录,切点相当于查询条件。
切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。
AOP配置
用注解方式声明切面
首先介绍一个AOP框架,AspectJ,是Java社区里最完整最流行的AOP框架,在Spring2.0以上版本中,可以使用基于AspectJ注解来配置AOP。
要在spring应用中使用AspectJ注解,必须在classpath下包含AspectJ类库:
aopalliance.jar、aspectj.weaver.jar和spring-aspects.jar,即导入包:
com.springsource.NET.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
commons-logging-1.1.1.jar
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
将aopSchema添加到根元素中.
要在SpringIOC容器中启用AspectJ注解支持,只要在Bean配置文件中定义一个空的XML元素aspectj-autoproxy>
当SpringIOC容器侦测到Bean配置文件中的aspectj-autoproxy>元素时,会自动为与AspectJ切面匹配的Bean创建代理.
要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为Bean实例.当在SpringIOC容器中初始化AspectJ切面之后,SpringIOC容器就会为那些与AspectJ切面相匹配的Bean创建代理.在AspectJ注解中,切面只是一个带有@Aspect注解的Java类.通知是标注有某种注解的简单的Java方法.
AspectJ支持的5中类型的通知注解
@Before:
前置通知,在方法执行之前执行
@After:
后置通知,在方法执行之后执行
@AfterRunning:
返回通知,在方法返回结果之后执行
@AfterThrowing:
异常通知,在方法抛出异常之后
@Around:
环绕通知,围绕着方法执行
/**
*日志切面
*@authorMegustas
*
*/
//把这个类声明为一个切面:
首先需要把该类放入到IOC容器中,通过注解@Component、再声明为一个切面,通过注解@Aspect,并且在配置文件中加入配置
//通过Order注解来指定切面的优先级,优先级数字越小代表优先级越高,越先执行
@Order
(2)
@Aspect
@Component
publicclassLoggingAspect{
//这个方法在哪些类的哪些方法前执行,通过注解来规定
//声明该方法是一个前置通知,在目标方法开始之前执行,".add"方法说明在add方法之前执行,".*"则表示在包下所有方法之前执行
@Before("execution(publicintcom.atguigu.spring.aop.impl.ArithmeticCalculatorImpl.add(int,int))")
publicvoidbeforeMethod(JoinPointjoinPoint){
StringmethodName=joinPoint.getSignature().getName();
List
System.out.println("Themethod"+methodName+"beginswith"+args);
}
//后置通知:
在目标方法执行后(无论是否发生异常),执行的通知
//在后置通知中还不能访问目标目标方法执行的结果,执行结果在返回通知中进行访问
@After("execution(*com.atguigu.spring.aop.impl.ArithmeticCalculatorImpl.*(int,int))")
publicvoidafterMethod(JoinPointjoinPoint){
StringmethodName=joinPoint.getSignature().getName();
System.out.println("Themethod"+methodName+"end");
}
/*
*返回通知:
在方法正常结束后执行的代码,返回通知是可以访问到方法的返回值的
*/
@AfterReturning(value="execution(publicintcom.atguigu.spring.aop.impl.ArithmeticCalculatorImpl.add(int,int))",
returning="result")
publicvoidafterReturning(JoinPointjoinPoint,Objectresult){
StringmethodName=joinPoint.getSignature().getName();
System.out.println("Themethod"+methodName+"endswith"+result);
}
/**
*在目标方法出现异常时会执行的代码
*可以访问到异常对象,且可以指定在出现特定异常时再执行通知代码
*例如Exceptionex,NullPointerExceptionex,可以指定不同种类的异常,当是指定的异常种类时执行
*@paramjoinPoint
*@paramex
*/
@AfterThrowing(value="execution(publicintcom.atguigu.spring.aop.impl.ArithmeticCalculatorImpl.div(int,int))",
Twing="ex")
publicvoidafterThrowing(JoinPointjoinPoint,Exceptionex){
StringmethodName=joinPoint.getSignature().getName();
System.out.println("Themethod"+methodName+"occursexcetion"+ex);
}
/**
*环绕通知需要携带ProceedingJoinPoint类型的参数
*环绕通知类似于动态代理的全过程:
ProceedingJoinPoint类型的参数可以决定是否执行目标方法
*并且环绕通知必须有返回值,返回值即为目标方法的返回值(类似于动态代理)
*最强的,前置、后置、返回与异常通知都可以,但是并不代表是最常用的
*@parampjd
*/
@Around("execution(*com.atguigu.spring.aop.impl.ArithmeticCalculatorImpl.add(int,int))")
publicObjectaroundMethod(ProceedingJoinPointpjd){
Objectresult=null;
StringmethodName=pjd.getSignature().getName();
//执行目标方法
try{
//前置通知
System.out.println("Themethod"+methodName+"beginswith"+Arrays.asList(pjd.getArgs()));
result=pjd.proceed();
//后置通知
System.out.println("Themethod"+methodName+"endswith"+result);
}catch(Throwablee){
//异常通知
System.out.println("Themethod"+methodName+"occursexcetion"+e);
}
//后置通知
System.out.println("Themethod"+methodName+"ends");
returnresult;
}
}
Bean的配置:
--自动扫描的包-->
component-scanbase-package="com.atguigu.spring.aop">
component-scan>
--使AspectJ的注解起作用-->
aspectj-autoproxy>
aspectj-autoproxy>
验证方法:
publicclassMain{
publicstaticvoidmain(String[]args){
//1.创建IOC容器
ClassPathXmlApplicationContextctx=newClassPathXmlApplicationContext("applicationContext.xml");
//2.从IOC容器中获取bean实例
ArithmeticCalculatorarithmeticCalculator=(ArithmeticCalculator)ctx.getBean(ArithmeticCalculator.class);
//3.调用bean的方法
intresult1=arithmeticCalculator.add(3,6);
System.out.println("result1:
"+result1);
//intresult2=arithmeticCalculator.div(1000,0);//通过异常通知显示
//System.out.println("result2:
"+result2);
//4.关闭容器
ctx.close();
}
}
指定切面的优先级及重用切入点
指定切面的优先级
实例可以参照上诉代码的@Order
在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的.
切面的优先级可以通过实现Ordered接口或利用@Order注解指定.
实现Ordered接口,getOrder()方法的返回值越小,优先级越高.
若使用@Order注解,序号出现在注解中
@Aspect
@Order(0)
publicclassCalculaotorValidationAspect{}
@Aspect
@Order
(1)
publicclassCalculaotorLoggingAspect{}
重用切入点
在编写AspectJ切面时,可以直接在通知注解中书写切入点表达式.但同一个切点表达式可能会在多个通知中重复出现.
在AspectJ切面中,可以通过@Pointcut注解将一个切入点声明成简单的方法.切入点的方法体通常是空的,因为将切入点定义与应用程序逻辑混在一起是不合理的.
切入点方法的访问控制符同时也控制着这个切入点的可见性.如果切入点要在多个切面中共用,最好将它们集中在一个公共的类中.在这种情况下,它们必须被声明为public.在引入这个切入点时,必须将类名也包括在内.如果类没有与这个切面放在同一个包中,还必须包含包名.
其他通知可以通过方法名称引入该切入点.
(总结就是一句话:
将切入点的表达式“封装”为一个方法,通过方法调用来实现)
同类中使用:
/**
*实现重用切面表达式
*定义一个方法,用于声明切入点表达式。
一般该方法中不需要再填入其他的代码
*使用@Pointcut来声明切入点表达式
*/
@Pointcut("execution(publicintcom.atguigu.spring.aop.impl.ArithmeticCalculatorImpl.add(int,int))")
publicvoiddeclareJointPointExpression(){
}
@Before("declareJointPointExpression()")
publicvoidbeforeMethod1(JoinPointjoinPoint){
StringmethodName=joinPoint.getSignature().getName();
Lis