Spring(三)spring核心技术——aop
一、AOP简介
AOP是Aspect Orient Programming的缩写,即面向切面编程。基于动态代理的,可以使用jdk和cglib两种代理方式。
-
Aspect: 切面,给你的目标类增加的功能,就是切面。像上面用的日志,事务都是切面。切面的特点:一般都是非业务方法,独立使用的。
-
Orient:面向,对着。
-
Programming:编程
AOP就是动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式,使用动态代理。
作用:
-
在目标类不修改的情况下增加功能
-
减少代码的重复
-
使开发人员专注业务功能的实现
-
解耦合:业务功能和日志、事务等非业务功能的耦合
二、动态代理的实现方式
- jdk动态代理
使用jdk中的Proxy,Method,InvocaitonHanderl创建代理对象。jdk动态代理要求目标类必须实现接口
- cglib动态代理
第三方的工具库,创建代理对象,原理是继承。 通过继承目标类,创建子类。子类就是代理对象。要求目标类不能是final的,方法也不能是final的。
三、AOP中名词概念
-
aspect(切面):表示给业务方法增加的功能,一般为体制输出、事务、权限检查等
-
JoinPoint:连接点,连接业务方法和切面的位置,就某类中的业务方法
-
pointcut(切入点):是一个或多个JoinPoint的集合,表示切面功能执行的位置
-
目标对象:给哪个类的方法增加功能,这个类就是目标对象
-
advice(通知):也叫增强。表示切面执行的时间,在方法前或后
四、何时使用AOP
-
某项目功能类不完善,需要增加功能,但是没有源代码
-
给项目的多个类需要增加相同功能
-
为业务功能增加事务、日志输出
-
…
五、AOP的实现
1、Spring
Spring在内部实现了aop规范,能做aop的工作。spring主要在事务处理时使用aop,在项目开发中很少使用spring的aop实现,因为spring的aop比较笨重。
2、使用Aspectj框架实现AOP
一个开源的专门做aop的框架。spring框架中集成了aspectj框架,通过spring就能使用aspectj的功能。
aspectJ框架实现aop有两种方式:
1)使用xml的配置文件 : 配置全局事务
2)使用注解,项目中要做aop功能,一般都使用注解
-
使用相应注解确定切面执行的时间
-
使用切面表达式确定切面执行的位置
切面位置的切入点表达式:execution(修饰符 返回值 包名.类名.方法名(方法参数) 异常)。用来指定切面执行的位置。
aspectj中常用注解如下:
- @Aspect:声明该类是切面类
/* * @Aspect:是Aspectj框架中的注解 * 作用:声明该类是切面类 * 切面类:为目标类增加功能的类,包含切面的功能代码 * 位置:在类的上面 * */
@Aspect
public class MyAspect {
}
- @Before:前置通知,在目标方法之前执行切面的功能
@Aspect
public class MyAspect {
/* * 定义方法,是实现切面功能的方法 * 方法定义要求: * 1、方法时公共的 * 2、方法名自定义 * 3、方法没有返回值 * 4、可以有参数,也可以无参数 * 如果有参数,参数不是自定义的 * @Before:前置通知注解 * 属性:value,是切入点表达式,表明切面的功能执行的位置 * 位置:在方法的上面 * 特点: * 1、在目标方法前执行 * 2、不会改变和影响目标方法的执行 * */
@Before(value = "execution(public void cn.krain.ba01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore(){
System.out.println("前置通知,切面功能,在目标方法执行前输出执行的时间:"+new Date());
}
- @AfterReturning:后置通知,在目标方法之后执行切面的功能,能获取返回值
@Aspect
public class MyAspect {
/* * @AfterReturning定义方法,是实现切面功能的方法 * 方法定义要求: * 1、方法时公共的 * 2、方法名自定义 * 3、方法没有返回值 * 4、方法有参数 * @AfterReturning:后置通知 * 属性:1、value:切入点表达式 * 2、returning:自定义变量,目标方法的返回值 * 自定义变量名称和通知方法的形参名一致 * 位置:在方法定义的上面 * 特点: * 1、在目标方法之后执行 * 2、能获取目标方法的返回值 * 3、可修改这个返回值 * */
@AfterReturning(value = "execution(* cn.krain.ba02.SomeServiceImpl.doOther(..))",
returning="res")
public void myAfterReturning(Object res){
System.out.println("后置通知,切面功能,在目标方法执行前输出执行的时间:"+res);
if (res!=null){
res = "Hello";
}
}
}
- @Around:环绕通知,能在目标方法前后增强功能,能够控制目标方法的执行,修改返回值
@Aspect
public class MyAspect {
/* * 环绕通知的定义格式 * 方法定义要求: * 1、方法时公共的 * 2、方法名自定义 * 3、必须有返回值 * 4、方法有固定的参数:proceedingJoinPoint * @Around:环绕通知 * 属性:value 切入点表达式 * 位置:在方法的定义上面 * 特点: * 1、是功能最强的通知 * 2、能够在目标方法前后增强功能 * 3、控制目标方法是否被执行 * 4、修改原来目标方法的执行结果,影响最后的调用结果 * */
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))" )
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
//获取第一个参数的值
String name = null;
Object args [] = joinPoint.getArgs();
if (args!=null && args.length>=1){
Object arg = args[0];
name = (String) arg;
}
Object result = null;
//实现环绕通知
System.out.println("环绕通知,在目标方法之前,输出时间:"+new Date());
//1、目标方法调用
//控制目标方法的执行
if (name.equals("张三")){
result = joinPoint.proceed(); //等同于执行目标方法
}
System.out.println("环绕通知,在目标方法之后,提交事务");
//修改目标函数返回结果
if (result!=null){
result = "Hello Aop";
}
return result;
}
}
- @AfterThrowing:异常通知,在目标方法抛出异常后执行
@Aspect
public class MyAspect {
/* 异常通知的定义格式 * 方法定义要求: * 1、方法时公共的 * 2、方法名自定义 * 3、方法没有返回值 * 4、固定参数Exception,如果还有参数则是:JoinPoint * 5、throwing的值要与Exception的参数名相同 * @AfterThrowing:异常通知 * 属性:1、value 切入点表达式 * 2、throwing 自定义变量,表示目标方法抛出的异常对象 * 如果有异常,通过邮件、短信通知 * 特点: * 1、在目标方法出现异常时执行 * 2、监控目标方法是否存在异常 * * 执行过程: * try{ * SomeServiceImpl.doSecond(); * }catch(Exception e){ * myAfterThrowing(e); * } * */
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond())",
throwing = "ex")
public void myAfterThrowing(Exception ex){
System.out.println("异常通知,方法执行异常,执行:"+ex.getMessage());
//发送短信
}
}
- @After:最终通知,总是会执行
@Aspect
public class MyAspect {
/* 最终通知的定义格式 * 方法定义要求: * 1、方法时公共的 * 2、方法名自定义 * 3、方法没有返回值 * 4、没有参数,如果有参数则是:JoinPoint * */
/* * @After 最终通知 * 属性:value 切入表达式 * 位置:在方法的上面 * * 特点: * 1、总是会执行 * 2、在目标方法之后执行 * * 执行过程: * try{ * //目标方法 * }catch(){ * * }finally{ * myAfter(); * } * */
@After(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void myAfter(){
System.out.println("执行最终通知,总是会被执行的代码");
}
}
- @Pointcut:定义和管理切入点的辅助注解
@Aspect
public class MyAspect {
@After(value = "myPT()")
public void myAfter(){
System.out.println("执行最终通知,总是会被执行的代码");
}
@Before(value = "myPT()")
public void myBefore(){
System.out.println("执行前置通知");
}
/* * @Pointcut:定义和管理切入点表达式,如果项目中的切入点表达式是重复的,可使用Pointcut * 属性:value 切入点表达式 * 位置:在自定义方法上面 * 为切入点表达式定义别名,使用"方法名称()"代替切入点表达式 * */
@Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
private void myPT(){
//无功能代码
}
}
- 编写spring主配置文件
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--把对象交给spring,有spring统一创建和管理-->
<!--创建SomeService目标对象-->
<bean name="someService" class="cn.krain.ba.SomeServiceImpl" />
<!--创建aspect切面对象-->
<bean name="myAspect" class="cn.krain.ba.MyAspect" />
<!-- 声明自动代理生成器:使用aspectj的内部功能,创建目标对象的代理对象。 创建代理对象是在内存中实现的,修改目标对象在内存中的结构,创建成代理对象 因此,目标对象就是被修改后的代理对象 -->
<aop:aspectj-autoproxy />
<!-- 如果项目中有接口时,仍希望使用CGHLIB动态代理 proxy-target-class="true":告诉框架使用cglib动态代理 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
</beans>
- 测试类MyTest.java
public class MyTest {
public void Test(){
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//根据对象名获取代理对象
SomeService proxy = (SomeService) ac.getBean("someService");
//proxy."目标方法名"();
}
}