Spring之AOP基本概念及配置AOP.docx
- 文档编号:13496069
- 上传时间:2023-06-14
- 格式:DOCX
- 页数:16
- 大小:139.59KB
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
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Spring AOP 基本概念 配置