spring -aop学习一
来源:互联网 发布:开源码无人驾驶汽车 编辑:程序博客网 时间:2024/06/14 19:56
AOP(Aspect-OrientedProgramming , 面向方面编程) 可以说是oop 的补充和完善
什么是aop
AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。 oop引入封装 继承 和多态性等概念来建立一种对象层次结构 用以模拟公共行为的一个集合 当我们需要为分散的对象引入公共的行为的时候 oop就显得无能为力了 也就是说 oop允许定义从上到下的关系 但是并不适合定义从左到右的关系 例如 日志功能 日志代码往往水平的散步在所有对象层次中 而与它散步到对象的核心功能毫无关系 对于其他类型的代码 入安全性 异常处理和透明的持续性也是如此 这种散步在各处的无关代码被称为横切代码 在oop 设计中 它导致了大量代码重复 而不利于各个模块的代码重复使用
介绍aop
AOP技术利用一种称为“横切”的技术,解剖封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,这样就能减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。
实现原理
代理模式分为 静态代理 和动态代理 以代理为基础 实现aop框架 再来研究aop的实现原理
一 静态代理 静态代理关键是在代理对象和目标对象实现共同的接口 并且代理对象持有目标对象的引用
- AOP 的实现原理
AOP分为静态AOP和动态AOP。静态AOP是指AspectJ实现的AOP,他是将切面代码直接编译到Java类文件中。动态AOP是指将切面代码进行动态织入实现的AOP。Spring的AOP为动态AOP,实现的技术为: JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术) 。尽管实现技术不一样,但 都是基于代理模式 , 都是生成一个代理对象 。
先从jdk 动态代理入手 看代理实现原理
1) JDK动态代理 原理demo
主要使用到 InvocationHandler 接口和 Proxy.newProxyInstance() 方法。 JDK动态代理要求被代理实现一个接口,只有接口中的方法才能够被代理 。其方法是将被代理对象注入到一个中间对象,而中间对象实现InvocationHandler接口,在实现该接口时,可以在 被代理对象调用它的方法时,在调用的前后插入一些代码。而 Proxy.newProxyInstance() 能够利用中间对象来生产代理对象。插入的代码就是切面代码。所以使用JDK动态代理可以实现AOP
第一部分
/** * JDK动态代理 * * 主要使用到 InvocationHandler 接口和 Proxy.newProxyInstance() 方法。 * JDK动态代理要求被代理实现一个接口,只有接口中的方法才能够被代理 * 。其方法是将被代理对象注入到一个中间对象,而中间对象实现InvocationHandler接口,在实现该接口时,可以在 * 被代理对象调用它的方法时,在调用的前后插入一些代码。而 Proxy.newProxyInstance() * 能够利用中间对象来生产代理对象。插入的代码就是切面代码。所以使用JDK动态代理可以实现AOP * * @author apple * */public class ProxyUtil implements InvocationHandler { private Object target; // 被代理的对象 @Override public Object invoke(Object arg0, Method method, Object[] args) throws Throwable { System.out.println("do sth before...."); Object result = method.invoke(target, args); System.out.println("do sth after...."); return result; } public ProxyUtil(Object target) { this.target = target; } public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; }}
service接口
public interface UserService { public void addUser(User user); public User getUser(String name);}
service接口的实现
public class UserServiceImpl implements UserService{ public void addUser(User user) { System.out.println("add user into databases"); } public User getUser(String name) { User user = new User(); user.setName(name); System.out.println("getUser from databases"); return user; }}
测试
public class ProxyTest { public static void main(String[] args) { Object poxyedObject = new UserServiceImpl();// 被代理的对象 ProxyUtil proxyUtils = new ProxyUtil(poxyedObject); // 生成代理对象 对象代理对象的这些接口进行代理 UserServiceImopl.class.getInterFaces() UserService proxyObject = (UserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), UserServiceImpl.class.getInterfaces(), proxyUtils); proxyObject.getUser("1"); proxyObject.addUser(new User()); }}
运行的结果:
do sth before….
getUser from databases
do sth after….
do sth before….
add user into databases
do sth after….
在从cglib 动态代代理 看代理实现原理 更改一下proxyUtil的代码
/** * CGLIB (code generate libary) 实现aop技术 * * 字节码生成技术实现AOP,其实就是继承被代理对象,然后Override需要被代理的方法,在覆盖该方法时,自然是可以插入我们自己的代码的。因为需要Override被代理对象的方法,所以自然CGLIB技术实现AOP时,就 * 必须要求需要被代理的方法不能是final方法,因为final方法不能被子类覆盖 * * @author apple * */public class CGProxy implements MethodInterceptor { private Object target; // 被代理对象 public CGProxy(Object target) { this.target = target; } @Override public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("do sth before"); Object result = proxy.invokeSuper(target, args); System.out.println("do sth after...."); return result; } /** * 创建代理对象 * * @return */ public Object getProxyObject() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass());// 设置弗雷 // 设置回调 在调用父类方法时 回调 this.intercept() enhancer.setCallback(this); // 创建代理对象 return enhancer.create(); }}
测试
public class CGProxyTest { public static void main(String[] args) { Object proxyedObject = new UserServiceImpl();//创建被代理对象 CGProxy cgproxy = new CGProxy(proxyedObject); // 给代理类传递被代理对象 UserService proxyObject = (UserService) cgproxy.getProxyObject(); //获得代理对象 proxyObject.getUser("ss"); proxyObject.addUser(new User()); }}
运行的结果:
do sth before….
getUser from databases
do sth after….
do sth before….
add user into databases
do sth after….
好 那我们接下来查看spring aop的实现源码
@SuppressWarnings("serial")public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface()) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }
其实不难从一下代码看出 spring的实现原理
if (targetClass.isInterface()) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config);
如果被代理对象实现了接口,那么就使用JDK的动态代理技术,反之则使用CGLIB来实现AOP,所以 Spring默认是使用JDK的动态代理技术实现AOP的 。
JdkDynamicAopProxy的实现其实很简单:
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { @Overridepublic Object getProxy(ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); } Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);}
- Spring AOP的配置
Spring中AOP的配置一般有两种方法,一种是使用 标签在xml中进行配置,一种是使用注解以及@Aspect风格的配置。
1) 基于的AOP配置
xml的配置
<bean id="DukePerformer" class="com.aop.config.service.DukePerformer"> <property name="name" value="duke"/> </bean> <bean id="audience" class="com.aop.config.service.Audience"/> <aop:config> <aop:aspect ref="audience"> <aop:before method="takeSeat" pointcut="execution(* *.perform(..))"/> <aop:before method="turnOffPhone" pointcut="execution(* *.perform(..))"/> <aop:after-returning method="applaud" pointcut="execution(* *.perform(..))"/> <aop:after-throwing method="unHappy" pointcut="execution(* *.perform(..))"/> </aop:aspect> </aop:config>
service的实现
public class DukePerformer implements Performer{ private String name; public void setName(String name) { this.name=name; } public String getName() { return this.name; } @Override public void perform() { // TODO Auto-generated method stub System.out.println(this.name+" sing a song."); }}
public class Audience {
public void takeSeat(){ System.out.println("The audiences take seat.");}public void turnOffPhone(){ System.out.println("The audiences turn off the phone.");}public void applaud(){ System.out.println("CLAP CLAP CLAP...");}public void unHappy(){ System.out.println("The audiences are unhappy.");}
}
接口
public interface Performer { public void perform();}
测试
public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); Performer per=(Performer)ctx.getBean("DukePerformer"); per.perform(); }}
运行结果:
2) 基于注解和@Aspect风格的AOP配置
先来了解一下AOP的相关概念,《Spring参考手册》中定义了以下几个AOP的重要概念,结合以上代码分析如下:
切面(Aspect):官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”,在本例中,“切面”就是类TestAspect所关注的具体行为,例如,AServiceImpl.barA()的调用就是切面TestAspect所关注的行为之一。“切面”在ApplicationContext中来配置。
连接点(Joinpoint) :程序执行过程中的某一行为,例如,UserService.get的调用或者UserService.delete抛出异常等行为。
通知(Advice) :“切面”对于某个“连接点”所产生的动作,例如,TestAspect中对com.spring.service包下所有类的方法进行日志记录的动作就是一个Advice。其中,一个“切面”可以包含多个“Advice”,例如ServiceAspect。
切入点(Pointcut) :匹配连接点的断言,在AOP中通知和一个切入点表达式关联。例如,TestAspect中的所有通知所关注的连接点,都由切入点表达式execution(* com.spring.service..(..))来决定。
目标对象(Target Object) :被一个或者多个切面所通知的对象。例如,AServcieImpl和BServiceImpl,当然在实际运行时,Spring AOP采用代理实现,实际AOP操作的是TargetObject的代理对象。
AOP代理(AOP Proxy) :在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理。默认情况下,TargetObject实现了接口时,则采用JDK动态代理,例如,AServiceImpl;反之,采用CGLIB代理,例如,BServiceImpl。强制使用CGLIB代理需要将 的 proxy-target-class属性设为true。
通知(Advice)类型:
前置通知(Before advice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在里面使用元素进行声明。例如,TestAspect中的doBefore方法。
后置通知(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在里面使用元素进行声明。例如,ServiceAspect中的returnAfter方法,所以Teser中调用UserService.delete抛出异常时,returnAfter方法仍然执行。
返回后通知(After return advice):在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在里面使用元素进行声明。
环绕通知(Around advice):包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在里面使用元素进行声明。例如,ServiceAspect中的around方法。
抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。ApplicationContext中在里面使用元素进行声明。例如,ServiceAspect中的returnThrow方法。
注:可以将多个通知应用到一个目标对象上,即可以将多个切面织入到同一目标对象。
使用Spring AOP可以基于两种方式,一种是比较方便和强大的注解方式,另一种则是中规中矩的xml配置方式。
先说注解,使用注解配置Spring AOP总体分为两步,第一步是在xml文件中声明激活自动扫描组件功能,同时激活自动代理功能(同时在xml中添加一个UserService的普通服务层组件,来测试AOP的注解功能)
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- 激活组件扫描功能,在包cn.ysh.studio.spring.aop及其子包下面自动扫描通过注解配置的组件 --> <context:component-scan base-package="com.aop"/> <!-- 激活自动代理功能 --> <aop:aspectj-autoproxy proxy-target-class="true"/> <!-- 用户服务对象 --> <bean id="userService" class="com.aop.service.impl.service" /></beans>
第二步是为Aspect切面类添加注解:
package com.aop.aspect;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;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;/** * 系统服务组件Aspect切面Bean * * * @author apple * */////声明这是一个组件//@Component////声明这是一个切面Bean//@Aspectpublic class ServiceAspect { public final static Log log = LogFactory.getLog(ServiceAspect.class); // 配置切入点,该方法无方法体 ,主要为了方便同类中的其他方法使用此处配置的切入点 @Pointcut("execution(* com.aop.service..*(..))") public void aspect() { } /** * * 配置前置通知 泗洪在方法aspect()上注册的切入点 同时接受joinPoint 切入点对象 可以没有该参数 */ @Before("aspect()") public void before(JoinPoint joinpoint) { if (log.isInfoEnabled()) { log.info("befor " + joinpoint); } } // 配置后置通知,使用在方法aspect()上注册的切入点 @After("aspect()") public void after(JoinPoint joinPoint) { if (log.isInfoEnabled()) { log.info("after " + joinPoint); } }// // 配置环绕通知,使用在方法aspect()上注册的切入点// @Around("aspect()")// public void around(JoinPoint joinPoint) {// long start = System.currentTimeMillis();// try {// ((ProceedingJoinPoint) joinPoint).proceed();// long end = System.currentTimeMillis();// if (log.isInfoEnabled()) {// log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");// }// } catch (Throwable e) {// long end = System.currentTimeMillis();// if (log.isInfoEnabled()) {// log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : "// + e.getMessage());// }// }// } // 配置后置返回通知,使用在方法aspect()上注册的切入点 @AfterReturning("aspect()") public void afterReturn(JoinPoint joinPoint) { if (log.isInfoEnabled()) { log.info("afterReturn " + joinPoint); } } // 配置抛出异常后通知,使用在方法aspect()上注册的切入点 @AfterThrowing(pointcut = "aspect()", throwing = "ex") public void afterThrow(JoinPoint joinPoint, Exception ex) { if (log.isInfoEnabled()) { log.info("afterThrow " + joinPoint + "\t" + ex.getMessage()); } }}
常情况下,表达式中使用”execution“就可以满足大部分的要求。表达式格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
modifiers-pattern:方法的操作权限
ret-type-pattern:返回值
declaring-type-pattern:方法所在的包
name-pattern:方法名
parm-pattern:参数名
throws-pattern:异常
其中,除ret-type-pattern和name-pattern之外,其他都是可选的。上例中,execution(* com.spring.service..(..))表示com.spring.service包下,返回值为任意类型;方法名任意;参数不作限制的所有方法。
- spring -aop学习一
- spring学习之AOP(一)
- Spring AOP学习(一)
- Spring AOP学习之:一
- spring AOP学习一 前置通知
- Spring框架AOP学习总结(一)
- 【Spring AOP】学习记录(一)
- Spring AOP (一)
- Spring AOP简介一
- Spring AOP(一)
- Spring AOP(一)
- Spring AOP (一)
- Spring(一):AOP
- spring aop(一)
- Spring之AOP(一)
- Spring Aop 详细(一)
- Spring AOP (一)
- Spring源码学习--AOP那点事(一)
- java取出字符串前面数值的方法
- Java 可变参数
- As语句
- Python的基本语法(一)
- Python坑之——默认参数必须指向不变对象
- spring -aop学习一
- 关于maven的常见错误
- SSM整合SpringSecurity实现权限管理实例 javaconfig配置方式
- start_kernel之前的调用流程(head.s)
- Java入职第一次记录,环境配置
- MySQL/AS语句
- 跨站点脚本解决方案
- HDU2555 人人都能参加第30届校田径运动会了
- C++静态绑定和动态绑定