spring的使用- aop
Posted liuxuelin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring的使用- aop相关的知识,希望对你有一定的参考价值。
spring的aop编程:
1.OOP和AOP
OOP:面向对象编程,主要关注的是一个类有哪些属性,要实现哪些功能。将这些属性和功能进行封装。
AOP(Aspect Oriented Programming):面向切面编程:关注的是不同类的重复的代码,将重复代码提取,在需要的时候插入进去,实现功能增强
OOP( 面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
AOP 则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。
spring整合junit测试:
Spring整合junit4可以方便我们的测试。
1. 需要导入一个spring-test-4.2.4.RELEASE.jar包
2. 在测试类上配置:
@RunWith(SpringJUnit4ClassRunner.class) //spring整合junit
@ContextConfiguration(locations = "classpath:applicationContext.xml") //指定配置文件的位置
public class AnnotationTest {
@Autowired
private IUserService userService; //将工厂中的对象注入到变量中
@Test
public void test2() {
userService.add();
}
}
2.AOP专业名词:
目标对象target: 指的是需要被增强的对象,由于spring aop是通过代理模式实现,从而这个对象永远是被代理对象。
连接点(join point):所谓连接点是指那些被拦截到的点,在spring中这些点指的是方法,因为spring只支持方法类型的连接点
切入点(pointcut): 表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方,简单说切入点是指我们要对哪些连接点进行拦截的定义
通知(advice): 所谓通知是指拦截到连接点之后所要做的事情就是通知,通知分为前置通知,后置通知,异常通知,最终通知,环绕通知,Advice 定义了在 pointcut 里面定义的程序点具体要做的操作
引介introduction: 引介是一种特殊的通知,在不修改类代码的前提下,introduction可以在运行期为类动态地添加一些方法或属性
切面aspect: 是切入点和通知的结合
织入weaving: 是将切面应用到目标对象从而创建出AOP代理对象的过程,织入可以在编译期,类装载期,运行期进行。Spring采用动态织入,而aspectj采用静态织入
代理Proxy: 一个类被AOP织入增强后,就产生一个结果代理类
3.AOP底层实现介绍
分为静态AOP和动态AOP
静态AOP:是指AspectJ实现的AOP,他是将切面代码直接编译到Java类文件中。(编译时)
动态AOP:是指将切面代码进行动态织入实现的AOP(运行时)
spring 采用动态AOP,实现的技术为:
JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术)
4动态代理的实现
a.JDK动态代理:只针对于接口操作,采用jdk proxy对象实现,获取到的代理对象与目标对象实现了相同的接口
b.CGLIB:可以为没有实现接口的类去做代理 //代理对象本质上是被代理对象的子类
1. spring框架的spring-core.jar包中已经集成了cglib与asm,不需要导入jar包
2.代码:
private Object target; //定义成员变量,存放目标对象
public CGLIBProxy(Object target){ //定义构造方法,传入目标对象
this.target=target;
}
public Object createProxy(){ //定义获取代理对象的方法
Enhancer enhancer=new Enhancer(); //创建增强对象
enhancer.setSuperclass(target.getClass()); //设置目标对象的字节码对象
enhancer.setCallback(new MethodInterceptor() { //设置回调方法
@Override
public Object intercept(Object arg0, Method method, Object[] arg2,
MethodProxy arg3) throws Throwable {
System.out.println("cglibproxy"); //通知,增强的代码
return method.invoke(target, arg2); //调用目标对象
}
});
return enhancer.create(); //返回代理对象
}
问题:spring采用的是哪一种动态机制:
如果目标对象有接口,优先使用jdk动态代理
如果目标对象无接口,使用cglib动态代理。
cglib代理:目标对象和代理对象是父子关系(代理对象为被代理对象的子类)
jdk代理: 目标对象和代理对象是兄弟关系
注意:
配置<aop:config proxy-target-class="true"> 后始终使用cglib代理
4.AOP开发
必须导入AOP联盟包:com.springsource.org.aopalliance-1.0.0.jar
在传统的spring aop开发中它支持增强(advice)有五种:
1. 前置通知 : 目标方法执行前增强 org.springframework.aop.MethodBeforeAdvice
2. 后置通知: 目标方法执行后增强 org.springframework.aop.AfterReturningAdvice
3. 环绕通知: 目标方法执行前后进行增 org.aopalliance.intercept.MethodInterceptor
4. 异常抛出通知: 目标方法抛出异常后的增 org.springframework.aop.ThrowsAdvice
5. 引介通知: 在目标类中添加一些新的方法或属性(不讲解)
AOP开发步骤:
1. 定义目标
2. 定义通知
3. 定义切点
4. 定义切面(通知+切点)
5. 生成代理
5.基于AspectJ切点表达式的传统aop开发--半自动aop
需要导入jar包com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
定义目标
public class OrderServiceImpl implements IOrderService {
定义通知:
public class OrderHelper implements MethodBeforeAdvice, AfterReturningAdvice, MethodInterceptor { //不同的接口对应不同的通知类型
xml配置文件: //注意要引入aop名称空间
<bean id="orderService" class="cn.itheima.aop.OrderServiceImpl"></bean> //定义目标
<bean id="orderServiceAdvice" class="cn.itheima.aop.OrderHelper"></bean> //定义通知
使用<aop:xxx>标签来完成切面与切点声明:
<aop:config>
<aop:pointcut id="orderServicePointCut" expression="execution(* cn.itheima.aop.IOrderService.*(..))"/> //定义切点
<aop:advisor advice-ref="orderServiceAdvice" pointcut-ref="orderServicePointCut" /> //定义切面,通知的类型,由通知类实现的接口决定
</aop:config>
切点表达式:返回值 包名 类名 方法名(..)
1. execution(public * *()) 所有的public的方法
2. execution(* cn.itheima.aop..*(..)) 所有的aop包及其子包下的所有类的方法
3. execution(* cn.itheima.aop.IOrderService.*(..)) IOrderService接口中定义的所有方法
4. execution(* cn.itheima.aop.IOrderService+.*(..)) 匹配实现特定接口所有类的方法
5. execution(* save*(..)) 区配所有的以save开头的方法
缺点:
通知类需要实现相应的接口,在其要实现的方法中定义不同的通知方法:
org.springframework.aop.MethodBeforeAdvice; //前置通知
org.springframework.aop.AfterReturningAdvice; //后置通知
org.aopalliance.intercept.MethodInterceptor; //环绕通知
所有的通知方法必须同时去增强一个目标方法
6全自动AOP--xml方式
AspectJ框架它定义的通知类型有6种:
1. 前置通知Before 相当于BeforeAdvice
2. 后置通知AfterReturning 相当于AfterReturningAdvice
3. 环绕通知 Around 相当于MethodInterceptor
4. 抛出通知AfterThrowing 相当于ThrowAdvice
5. 引介通知DeclareParents 相当于IntroductionInterceptor
6. 最终通知After 不管是否异常,该通知都会执行
相比spring 的传统AOP Advice多了一个最终通知
定义目标,定义通知与半自动方式相同
<aop:config> //定义切面
<aop:aspect ref="userServiceAdvice"> //通知
<aop:pointcut expression="execution(* *..UserService Impl.add(..))" id="pointCut1"/> //切点,这个切点可以定义在aspect标签外,切点定义在标签外后,可以被所有的通知共
<aop:before method="before" pointcut-ref="pointCut1"/> //指定通知类型,通知方法,引用切点
<aop:after-returning method="afterReturning" pointcut-ref="pointCut1"/> //后置通知
<aop:around method="around" pointcut-ref="pointCut1"/> //环绕通知
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCut1"/> //异常通知,抛出异常才会执行
<aop:after method="after" pointcut-ref="pointCut1"/> //最终通知,无论是否出现异常,都会执行
</aop:aspect>
<aop:aspect ref="userServiceAdvice2"> //每一个通知的bean对应一个切面
。。。
</aop:aspect>
</aop:config>
注意上面的标签中
全自动aop和半自动aop定义切面的区别:
半自动中与切点结合的通知为整个通知类,即通知类中所有的方法会同时作用在切点上
全自动aop的通知为通知类中的方法,即可以指定某个方法作用在某个切点上,更加灵活
7.通知上的参数介绍:
1. 通知上可以添加JoinPoint参数,通过它可以获取目标相关的信息
public void before(JoinPoint jp) { //JoinPoint对象可用于获取被增强对象的信息
System.out.println("拦截的目标类:" +jp.getSignature().getDeclaringTypeName()); //通过参数jp可以获取目标类的名称 signature:署名;签名;信号
System.out.println("拦截的方法名称:" + jp.getSignature().getName()); //通过参数jp可以获取目标方法的名称
2. 后置通知获取目标方法返回值
public void afterReturning(JoinPoint jp,Object val) { //也可以传入joinpoint,同前置通知中的joinpoint
Object[] args = jp.getArgs(); //获取目标方法的参数列表
System.out.println("目标方法返回值:" + val);
System.out.println("后置通知");
}
注意:需要配置returning="val" 和参数对应
<aop:after-returning method="afterReturning" pointcut-ref="pointCut1" returning="val"/>
3. 环绕通知必须传入连接点参数:ProceedingJoinPoint //环绕通知可以实现其他所有的通知
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前");
Object result = pjp.proceed(); //此处调用执行被增强的方法
System.out.println("环绕后");
return result;
}
3. 异常通知获取异常信息
public void afterThrowing(JoinPoint jp,Throwable ex) { //也可以传入joinpoint,同前置通知中的joinpoint
System.out.println("发现了异常。。。。"+ex);
}
注意:需要配置throwing="ex" 和参数对应
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCut1" throwing="ex"/>
8.注解方式实现代理:
第一步:定义目标类,通知类
第二步:开启注解扫描,把通知和目标纳入Spring的管理当中,并配置aspectj自动代理aspectj-autoproxy:
a. 开启注解扫描
<context:component-scan base-package="cn.itheima"/>
b. 将通知和目标类加入spring管理 //配置@Component
c.配置aspectj自动代理
<aop:aspectj-autoproxy proxy-target-class="true"/> //proxy-target-class默认为false,如果设置为true,表示始终采用cglib代理
第三步:定义切面
a. 在通知的类上添加注解@aspect,声明这个类为切面
b. 根据不同的通知,在不同的方法上添加注解并定义切点表达式
声明切面 //在类上
@Aspect
public class CustomerServiceHelper {
前置通知
@Before("execution(* *.save(..))") //括号中传入切点表达式
public void before() {
System.out.println("前置通知...");
}
后置通知
@AfterReturning(value = "execution(* *.update(..))", returning = "value") //声明接收参数需要配置
public void afterReturning(JoinPoint jp, Object value) { //value 为目标方法返回的参数
System.out.println("后置通知,目标方法的返回是" + value);
}
环绕通知
@Around("execution(* *.s*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable { //必须传入ProceedingJoinPoint参数
System.out.println("环绕前...");
Object value = pjp.proceed();
System.out.println("环绕后");
return value;
}
抛异常通知
@AfterThrowing(value="execution(* *.s*(..))", throwing="tx") //需要配置异常参数
public void afterThrowing(Throwable tx) { //tx接收异常参数
System.out.println("异常抛出通知:" + tx);
}
最终通知
@After("execution(* *.s*(..))")
public void after() {
System.out.println("最终通知");
}
公用切点的定义和使用
@Pointcut("execution(* *.s*(..))") //在通知的类中可以这种方式定义切点,这样的切点可以被所用通知公用
private void mypointcut() {
}
使用方式:
@Before("mypointcut()") //直接通过 方法名() 引用切点,也可以 mypointcut()||mypointcut2() 表示引用两个切点
public void before() {
System.out.println("前置通知...");
}
spring的aop编程:
1.OOP和AOP
OOP:面向对象编程,主要关注的是一个类有哪些属性,要实现哪些功能。将这些属性和功能进行封装。
AOP(Aspect Oriented Programming):面向切面编程:关注的是不同类的重复的代码,将重复代码提取,在需要的时候插入进去,实现功能增强
OOP( 面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
AOP 则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。
spring整合junit测试:
Spring整合junit4可以方便我们的测试。
1. 需要导入一个spring-test-4.2.4.RELEASE.jar包
2. 在测试类上配置:
@RunWith(SpringJUnit4ClassRunner.class) //spring整合junit
@ContextConfiguration(locations = "classpath:applicationContext.xml") //指定配置文件的位置
public class AnnotationTest {
@Autowired
private IUserService userService; //将工厂中的对象注入到变量中
@Test
public void test2() {
userService.add();
}
}
2.AOP专业名词:
目标对象target:指的是需要被增强的对象,由于spring aop是通过代理模式实现,从而这个对象永远是被代理对象。
连接点(join point):所谓连接点是指那些被拦截到的点,在spring中这些点指的是方法,因为spring只支持方法类型的连接点
切入点(pointcut): 表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方,简单说切入点是指我们要对哪些连接点进行拦截的定义
通知(advice): 所谓通知是指拦截到连接点之后所要做的事情就是通知,通知分为前置通知,后置通知,异常通知,最终通知,环绕通知,Advice 定义了在 pointcut 里面定义的程序点具体要做的操作
引介introduction: 引介是一种特殊的通知,在不修改类代码的前提下,introduction可以在运行期为类动态地添加一些方法或属性
切面aspect: 是切入点和通知的结合
织入weaving: 是将切面应用到目标对象从而创建出AOP代理对象的过程,织入可以在编译期,类装载期,运行期进行。Spring采用动态织入,而aspectj采用静态织入
代理Proxy: 一个类被AOP织入增强后,就产生一个结果代理类
3.AOP底层实现介绍
分为静态AOP和动态AOP
静态AOP:是指AspectJ实现的AOP,他是将切面代码直接编译到Java类文件中。(编译时)
动态AOP:是指将切面代码进行动态织入实现的AOP(运行时)
spring 采用动态AOP,实现的技术为:
JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术)
4动态代理的实现
a.JDK动态代理:只针对于接口操作,采用jdk proxy对象实现,获取到的代理对象与目标对象实现了相同的接口
b.CGLIB:可以为没有实现接口的类去做代理 //代理对象本质上是被代理对象的子类
1. spring框架的spring-core.jar包中已经集成了cglib与asm,不需要导入jar包
2.代码:
private Object target; //定义成员变量,存放目标对象
public CGLIBProxy(Object target){ //定义构造方法,传入目标对象
this.target=target;
}
public Object createProxy(){ //定义获取代理对象的方法
Enhancer enhancer=new Enhancer(); //创建增强对象
enhancer.setSuperclass(target.getClass()); //设置目标对象的字节码对象
enhancer.setCallback(new MethodInterceptor() { //设置回调方法
@Override
public Object intercept(Object arg0, Method method, Object[] arg2,
MethodProxy arg3) throws Throwable {
System.out.println("cglibproxy"); //通知,增强的代码
return method.invoke(target, arg2); //调用目标对象
}
});
return enhancer.create(); //返回代理对象
}
问题:spring采用的是哪一种动态机制:
如果目标对象有接口,优先使用jdk动态代理
如果目标对象无接口,使用cglib动态代理。
cglib代理:目标对象和代理对象是父子关系(代理对象为被代理对象的子类)
jdk代理: 目标对象和代理对象是兄弟关系
注意:
配置<aop:config proxy-target-class="true"> 后始终使用cglib代理
4.AOP开发
必须导入AOP联盟包:com.springsource.org.aopalliance-1.0.0.jar
在传统的spring aop开发中它支持增强(advice)有五种:
1. 前置通知 : 目标方法执行前增强 org.springframework.aop.MethodBeforeAdvice
2. 后置通知: 目标方法执行后增强 org.springframework.aop.AfterReturningAdvice
3. 环绕通知: 目标方法执行前后进行增 org.aopalliance.intercept.MethodInterceptor
4. 异常抛出通知: 目标方法抛出异常后的增 org.springframework.aop.ThrowsAdvice
5. 引介通知: 在目标类中添加一些新的方法或属性(不讲解)
AOP开发步骤:
1. 定义目标
2. 定义通知
3. 定义切点
4. 定义切面(通知+切点)
5. 生成代理
5.基于AspectJ切点表达式的传统aop开发--半自动aop
需要导入jar包com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
定义目标
public class OrderServiceImpl implements IOrderService {
定义通知:
public class OrderHelper implements MethodBeforeAdvice, AfterReturningAdvice, MethodInterceptor { //不同的接口对应不同的通知类型
xml配置文件: //注意要引入aop名称空间
<bean id="orderService" class="cn.itheima.aop.OrderServiceImpl"></bean> //定义目标
<bean id="orderServiceAdvice" class="cn.itheima.aop.OrderHelper"></bean> //定义通知
使用<aop:xxx>标签来完成切面与切点声明:
<aop:config>
<aop:pointcut id="orderServicePointCut" expression="execution(* cn.itheima.aop.IOrderService.*(..))"/> //定义切点
<aop:advisor advice-ref="orderServiceAdvice" pointcut-ref="orderServicePointCut" /> //定义切面,通知的类型,由通知类实现的接口决定
</aop:config>
切点表达式:返回值 包名 类名 方法名(..)
1. execution(public * *()) 所有的public的方法
2. execution(* cn.itheima.aop..*(..)) 所有的aop包及其子包下的所有类的方法
3. execution(* cn.itheima.aop.IOrderService.*(..)) IOrderService接口中定义的所有方法
4. execution(* cn.itheima.aop.IOrderService+.*(..)) 匹配实现特定接口所有类的方法
5. execution(* save*(..)) 区配所有的以save开头的方法
缺点:
通知类需要实现相应的接口,在其要实现的方法中定义不同的通知方法:
org.springframework.aop.MethodBeforeAdvice; //前置通知
org.springframework.aop.AfterReturningAdvice; //后置通知
org.aopalliance.intercept.MethodInterceptor; //环绕通知
所有的通知方法必须同时去增强一个目标方法
6全自动AOP--xml方式
AspectJ框架它定义的通知类型有6种:
1. 前置通知Before 相当于BeforeAdvice
2. 后置通知AfterReturning 相当于AfterReturningAdvice
3. 环绕通知 Around 相当于MethodInterceptor
4. 抛出通知AfterThrowing 相当于ThrowAdvice
5. 引介通知DeclareParents 相当于IntroductionInterceptor
6. 最终通知After 不管是否异常,该通知都会执行
相比spring 的传统AOP Advice多了一个最终通知
定义目标,定义通知与半自动方式相同
<aop:config> //定义切面
<aop:aspect ref="userServiceAdvice"> //通知
<aop:pointcut expression="execution(* *..UserService Impl.add(..))" id="pointCut1"/> //切点,这个切点可以定义在aspect标签外,切点定义在标签外后,可以被所有的通知共
<aop:before method="before" pointcut-ref="pointCut1"/> //指定通知类型,通知方法,引用切点
<aop:after-returning method="afterReturning" pointcut-ref="pointCut1"/> //后置通知
<aop:around method="around" pointcut-ref="pointCut1"/> //环绕通知
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCut1"/> //异常通知,抛出异常才会执行
<aop:after method="after" pointcut-ref="pointCut1"/> //最终通知,无论是否出现异常,都会执行
</aop:aspect>
<aop:aspect ref="userServiceAdvice2"> //每一个通知的bean对应一个切面
。。。
</aop:aspect>
</aop:config>
注意上面的标签中
全自动aop和半自动aop定义切面的区别:
半自动中与切点结合的通知为整个通知类,即通知类中所有的方法会同时作用在切点上
全自动aop的通知为通知类中的方法,即可以指定某个方法作用在某个切点上,更加灵活
7.通知上的参数介绍:
1. 通知上可以添加JoinPoint参数,通过它可以获取目标相关的信息
public void before(JoinPoint jp) { //JoinPoint对象可用于获取被增强对象的信息
System.out.println("拦截的目标类:" +jp.getSignature().getDeclaringTypeName()); //通过参数jp可以获取目标类的名称 signature:署名;签名;信号
System.out.println("拦截的方法名称:" + jp.getSignature().getName()); //通过参数jp可以获取目标方法的名称
2. 后置通知获取目标方法返回值
public void afterReturning(JoinPoint jp,Object val) { //也可以传入joinpoint,同前置通知中的joinpoint
Object[] args = jp.getArgs(); //获取目标方法的参数列表
System.out.println("目标方法返回值:" + val);
System.out.println("后置通知");
}
注意:需要配置returning="val" 和参数对应
<aop:after-returning method="afterReturning" pointcut-ref="pointCut1" returning="val"/>
3. 环绕通知必须传入连接点参数:ProceedingJoinPoint //环绕通知可以实现其他所有的通知
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前");
Object result = pjp.proceed(); //此处调用执行被增强的方法
System.out.println("环绕后");
return result;
}
3. 异常通知获取异常信息
public void afterThrowing(JoinPoint jp,Throwable ex) { //也可以传入joinpoint,同前置通知中的joinpoint
System.out.println("发现了异常。。。。"+ex);
}
注意:需要配置throwing="ex" 和参数对应
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCut1" throwing="ex"/>
8.注解方式实现代理:
第一步:定义目标类,通知类
第二步:开启注解扫描,把通知和目标纳入Spring的管理当中,并配置aspectj自动代理aspectj-autoproxy:
a. 开启注解扫描
<context:component-scan base-package="cn.itheima"/>
b. 将通知和目标类加入spring管理 //配置@Component
c.配置aspectj自动代理
<aop:aspectj-autoproxy proxy-target-class="true"/> //proxy-target-class默认为false,如果设置为true,表示始终采用cglib代理
第三步:定义切面
a. 在通知的类上添加注解@aspect,声明这个类为切面
b. 根据不同的通知,在不同的方法上添加注解并定义切点表达式
声明切面 //在类上
@Aspect
public class CustomerServiceHelper {
前置通知
@Before("execution(* *.save(..))") //括号中传入切点表达式
public void before() {
System.out.println("前置通知...");
}
后置通知
@AfterReturning(value = "execution(* *.update(..))", returning = "value") //声明接收参数需要配置
public void afterReturning(JoinPoint jp, Object value) { //value 为目标方法返回的参数
System.out.println("后置通知,目标方法的返回是" + value);
}
环绕通知
@Around("execution(* *.s*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable { //必须传入ProceedingJoinPoint参数
System.out.println("环绕前...");
Object value = pjp.proceed();
System.out.println("环绕后");
return value;
}
抛异常通知
@AfterThrowing(value="execution(* *.s*(..))", throwing="tx") //需要配置异常参数
public void afterThrowing(Throwable tx) { //tx接收异常参数
System.out.println("异常抛出通知:" + tx);
}
最终通知
@After("execution(* *.s*(..))")
public void after() {
System.out.println("最终通知");
}
公用切点的定义和使用
@Pointcut("execution(* *.s*(..))") //在通知的类中可以这种方式定义切点,这样的切点可以被所用通知公用
private void mypointcut() {
}
使用方式:
@Before("mypointcut()") //直接通过 方法名() 引用切点,也可以 mypointcut()||mypointcut2() 表示引用两个切点
public void before() {
System.out.println("前置通知...");
}
spring的aop编程:
1.OOP和AOP
OOP:面向对象编程,主要关注的是一个类有哪些属性,要实现哪些功能。将这些属性和功能进行封装。
AOP(Aspect Oriented Programming):面向切面编程:关注的是不同类的重复的代码,将重复代码提取,在需要的时候插入进去,实现功能增强
OOP( 面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
AOP 则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。
spring整合junit测试:
Spring整合junit4可以方便我们的测试。
1. 需要导入一个spring-test-4.2.4.RELEASE.jar包
2. 在测试类上配置:
@RunWith(SpringJUnit4ClassRunner.class) //spring整合junit
@ContextConfiguration(locations = "classpath:applicationContext.xml") //指定配置文件的位置
public class AnnotationTest {
@Autowired
private IUserService userService; //将工厂中的对象注入到变量中
@Test
public void test2() {
userService.add();
}
}
2.AOP专业名词:
目标对象target:指的是需要被增强的对象,由于spring aop是通过代理模式实现,从而这个对象永远是被代理对象。
连接点(join point):所谓连接点是指那些被拦截到的点,在spring中这些点指的是方法,因为spring只支持方法类型的连接点
切入点(pointcut): 表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方,简单说切入点是指我们要对哪些连接点进行拦截的定义
通知(advice): 所谓通知是指拦截到连接点之后所要做的事情就是通知,通知分为前置通知,后置通知,异常通知,最终通知,环绕通知,Advice 定义了在 pointcut 里面定义的程序点具体要做的操作
引介introduction: 引介是一种特殊的通知,在不修改类代码的前提下,introduction可以在运行期为类动态地添加一些方法或属性
切面aspect: 是切入点和通知的结合
织入weaving: 是将切面应用到目标对象从而创建出AOP代理对象的过程,织入可以在编译期,类装载期,运行期进行。Spring采用动态织入,而aspectj采用静态织入
代理Proxy: 一个类被AOP织入增强后,就产生一个结果代理类
3.AOP底层实现介绍
分为静态AOP和动态AOP
静态AOP:是指AspectJ实现的AOP,他是将切面代码直接编译到Java类文件中。(编译时)
动态AOP:是指将切面代码进行动态织入实现的AOP(运行时)
spring 采用动态AOP,实现的技术为:
JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术)
4动态代理的实现
a.JDK动态代理:只针对于接口操作,采用jdk proxy对象实现,获取到的代理对象与目标对象实现了相同的接口
b.CGLIB:可以为没有实现接口的类去做代理 //代理对象本质上是被代理对象的子类
1. spring框架的spring-core.jar包中已经集成了cglib与asm,不需要导入jar包
2.代码:
private Object target; //定义成员变量,存放目标对象
public CGLIBProxy(Object target){ //定义构造方法,传入目标对象
this.target=target;
}
public Object createProxy(){ //定义获取代理对象的方法
Enhancer enhancer=new Enhancer(); //创建增强对象
enhancer.setSuperclass(target.getClass()); //设置目标对象的字节码对象
enhancer.setCallback(new MethodInterceptor() { //设置回调方法
@Override
public Object intercept(Object arg0, Method method, Object[] arg2,
MethodProxy arg3) throws Throwable {
System.out.println("cglibproxy"); //通知,增强的代码
return method.invoke(target, arg2); //调用目标对象
}
});
return enhancer.create(); //返回代理对象
}
问题:spring采用的是哪一种动态机制:
如果目标对象有接口,优先使用jdk动态代理
如果目标对象无接口,使用cglib动态代理。
cglib代理:目标对象和代理对象是父子关系(代理对象为被代理对象的子类)
jdk代理: 目标对象和代理对象是兄弟关系
注意:
配置<aop:config proxy-target-class="true"> 后始终使用cglib代理
4.AOP开发
必须导入AOP联盟包:com.springsource.org.aopalliance-1.0.0.jar
在传统的spring aop开发中它支持增强(advice)有五种:
1. 前置通知 : 目标方法执行前增强 org.springframework.aop.MethodBeforeAdvice
2. 后置通知: 目标方法执行后增强 org.springframework.aop.AfterReturningAdvice
3. 环绕通知: 目标方法执行前后进行增 org.aopalliance.intercept.MethodInterceptor
4. 异常抛出通知: 目标方法抛出异常后的增 org.springframework.aop.ThrowsAdvice
5. 引介通知: 在目标类中添加一些新的方法或属性(不讲解)
AOP开发步骤:
1. 定义目标
2. 定义通知
3. 定义切点
4. 定义切面(通知+切点)
5. 生成代理
5.基于AspectJ切点表达式的传统aop开发--半自动aop
需要导入jar包com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
定义目标
public class OrderServiceImpl implements IOrderService {
定义通知:
public class OrderHelper implements MethodBeforeAdvice, AfterReturningAdvice, MethodInterceptor { //不同的接口对应不同的通知类型
xml配置文件: //注意要引入aop名称空间
<bean id="orderService" class="cn.itheima.aop.OrderServiceImpl"></bean> //定义目标
<bean id="orderServiceAdvice" class="cn.itheima.aop.OrderHelper"></bean> //定义通知
使用<aop:xxx>标签来完成切面与切点声明:
<aop:config>
<aop:pointcut id="orderServicePointCut" expression="execution(* cn.itheima.aop.IOrderService.*(..))"/> //定义切点
<aop:advisor advice-ref="orderServiceAdvice" pointcut-ref="orderServicePointCut" /> //定义切面,通知的类型,由通知类实现的接口决定
</aop:config>
切点表达式:返回值 包名 类名 方法名(..)
1. execution(public * *()) 所有的public的方法
2. execution(* cn.itheima.aop..*(..)) 所有的aop包及其子包下的所有类的方法
3. execution(* cn.itheima.aop.IOrderService.*(..)) IOrderService接口中定义的所有方法
4. execution(* cn.itheima.aop.IOrderService+.*(..)) 匹配实现特定接口所有类的方法
5. execution(* save*(..)) 区配所有的以save开头的方法
缺点:
通知类需要实现相应的接口,在其要实现的方法中定义不同的通知方法:
org.springframework.aop.MethodBeforeAdvice; //前置通知
org.springframework.aop.AfterReturningAdvice; //后置通知
org.aopalliance.intercept.MethodInterceptor; //环绕通知
所有的通知方法必须同时去增强一个目标方法
6全自动AOP--xml方式
AspectJ框架它定义的通知类型有6种:
1. 前置通知Before 相当于BeforeAdvice
2. 后置通知AfterReturning 相当于AfterReturningAdvice
3. 环绕通知 Around 相当于MethodInterceptor
4. 抛出通知AfterThrowing 相当于ThrowAdvice
5. 引介通知DeclareParents 相当于IntroductionInterceptor
6. 最终通知After 不管是否异常,该通知都会执行
相比spring 的传统AOP Advice多了一个最终通知
定义目标,定义通知与半自动方式相同
<aop:config> //定义切面
<aop:aspect ref="userServiceAdvice"> //通知
<aop:pointcut expression="execution(* *..UserService Impl.add(..))" id="pointCut1"/> //切点,这个切点可以定义在aspect标签外,切点定义在标签外后,可以被所有的通知共
<aop:before method="before" pointcut-ref="pointCut1"/> //指定通知类型,通知方法,引用切点
<aop:after-returning method="afterReturning" pointcut-ref="pointCut1"/> //后置通知
<aop:around method="around" pointcut-ref="pointCut1"/> //环绕通知
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCut1"/> //异常通知,抛出异常才会执行
<aop:after method="after" pointcut-ref="pointCut1"/> //最终通知,无论是否出现异常,都会执行
</aop:aspect>
<aop:aspect ref="userServiceAdvice2"> //每一个通知的bean对应一个切面
。。。
</aop:aspect>
</aop:config>
注意上面的标签中
全自动aop和半自动aop定义切面的区别:
半自动中与切点结合的通知为整个通知类,即通知类中所有的方法会同时作用在切点上
全自动aop的通知为通知类中的方法,即可以指定某个方法作用在某个切点上,更加灵活
7.通知上的参数介绍:
1. 通知上可以添加JoinPoint参数,通过它可以获取目标相关的信息
public void before(JoinPoint jp) { //JoinPoint对象可用于获取被增强对象的信息
System.out.println("拦截的目标类:" +jp.getSignature().getDeclaringTypeName()); //通过参数jp可以获取目标类的名称 signature:署名;签名;信号
System.out.println("拦截的方法名称:" + jp.getSignature().getName()); //通过参数jp可以获取目标方法的名称
2. 后置通知获取目标方法返回值
public void afterReturning(JoinPoint jp,Object val) { //也可以传入joinpoint,同前置通知中的joinpoint
Object[] args = jp.getArgs(); //获取目标方法的参数列表
System.out.println("目标方法返回值:" + val);
System.out.println("后置通知");
}
注意:需要配置returning="val" 和参数对应
<aop:after-returning method="afterReturning" pointcut-ref="pointCut1" returning="val"/>
3. 环绕通知必须传入连接点参数:ProceedingJoinPoint //环绕通知可以实现其他所有的通知
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前");
Object result = pjp.proceed(); //此处调用执行被增强的方法
System.out.println("环绕后");
return result;
}
3. 异常通知获取异常信息
public void afterThrowing(JoinPoint jp,Throwable ex) { //也可以传入joinpoint,同前置通知中的joinpoint
System.out.println("发现了异常。。。。"+ex);
}
注意:需要配置throwing="ex" 和参数对应
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCut1" throwing="ex"/>
8.注解方式实现代理:
第一步:定义目标类,通知类
第二步:开启注解扫描,把通知和目标纳入Spring的管理当中,并配置aspectj自动代理aspectj-autoproxy:
a. 开启注解扫描
<context:component-scan base-package="cn.itheima"/>
b. 将通知和目标类加入spring管理 //配置@Component
c.配置aspectj自动代理
<aop:aspectj-autoproxy proxy-target-class="true"/> //proxy-target-class默认为false,如果设置为true,表示始终采用cglib代理
第三步:定义切面
a. 在通知的类上添加注解@aspect,声明这个类为切面
b. 根据不同的通知,在不同的方法上添加注解并定义切点表达式
声明切面 //在类上
@Aspect
public class CustomerServiceHelper {
前置通知
@Before("execution(* *.save(..))") //括号中传入切点表达式
public void before() {
System.out.println("前置通知...");
}
后置通知
@AfterReturning(value = "execution(* *.update(..))", returning = "value") //声明接收参数需要配置
public void afterReturning(JoinPoint jp, Object value) { //value 为目标方法返回的参数
System.out.println("后置通知,目标方法的返回是" + value);
}
环绕通知
@Around("execution(* *.s*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable { //必须传入ProceedingJoinPoint参数
System.out.println("环绕前...");
Object value = pjp.proceed();
System.out.println("环绕后");
return value;
}
抛异常通知
@AfterThrowing(value="execution(* *.s*(..))", throwing="tx") //需要配置异常参数
public void afterThrowing(Throwable tx) { //tx接收异常参数
System.out.println("异常抛出通知:" + tx);
}
最终通知
@After("execution(* *.s*(..))")
public void after() {
System.out.println("最终通知");
}
公用切点的定义和使用
@Pointcut("execution(* *.s*(..))") //在通知的类中可以这种方式定义切点,这样的切点可以被所用通知公用
private void mypointcut() {
}
使用方式:
@Before("mypointcut()") //直接通过 方法名() 引用切点,也可以 mypointcut()||mypointcut2() 表示引用两个切点
public void before() {
System.out.println("前置通知...");
}
以上是关于spring的使用- aop的主要内容,如果未能解决你的问题,请参考以下文章