[toc] AOP是SPRing提供的关键特性之一。AOP即面向切面编程,是OOP编程的有效补充。使用AOP技术,可以将一些系统性相关的编程工作,独立提取出来,独立实现,然后通过切面切入进系统。从而避免了在业务逻辑的代码中混入很多的系统相关的逻辑——比如权限管理,事物管理,日志记录等等。这些系统性的编程工作都可以独立编码实现,然后通过AOP技术切入进系统即可。从而达到了 将不同的关注点分离出来 的效果。本文深入剖析Spring的AOP的原理。
1) aspect :切面,切入系统的一个切面。比如事务管理是一个切面,权限管理也是一个切面;
2) Join point :连接点,也就是可以进行横向切入的位置;
3) Advice :通知,切面在某个连接点执行的操作(分为: Before advice , After returning advice , After throwing advice , After (finally) advice , Around advice );
4) Pointcut :切点,符合切点表达式的连接点,也就是真正被切入的地方;
被代理对象:
public class UserServiceImpl implements UserService { public void addUser(User user) { System.out.println("add user into database."); } public User getUser(int id) { User user = new User(); user.setId(id); System.out.println("getUser from database."); return user; }}代理中间类:
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;public class ProxyUtil implements InvocationHandler { private Object target; // 被代理的对象 public Object invoke(Object proxy, 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; } ProxyUtil(Object target){ this.target = target; } public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; }}测试:
import java.lang.reflect.Proxy;import net.aazj.pojo.User;public class ProxyTest { public static void main(String[] args){ Object proxyedObject = new UserServiceImpl(); // 被代理的对象 ProxyUtil proxyUtils = new ProxyUtil(proxyedObject); // 生成代理对象,对被代理对象的这些接口进行代理:UserServiceImpl.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 database.do sth after....do sth before....add user into database.do sth after....我们看到在 UserService接口中的方法 addUser 和 getUser方法的前面插入了我们自己的代码。这就是JDK动态代理实现AOP的原理。我们看到该方式有一个要求, 被代理的对象必须实现接口,而且只有接口中的方法才能被代理 。输出结果:
do sth before....getUser from database.do sth after....do sth before....add user into database.do sth after....我们看到达到了同样的效果。它的原理是生成一个父类 enhancer.setSuperclass( this.target.getClass()) 的子类 enhancer.create() ,然后对父类的方法进行拦截enhancer.setCallback( this) . 对父类的方法进行覆盖,所以父类方法不能是final的。从上面的源码我们可以看到:
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);}再看一个例子:
<bean id="aspectBean" class="net.aazj.aop.DataSourceInterceptor"/><aop:config> <aop:aspect id="dataSourceAspect" ref="aspectBean"> <aop:pointcut id="dataSourcePoint" expression="execution(public * net.aazj.service..*.getUser(..))" /> <aop:pointcut expression="" id=""/> <aop:before method="before" pointcut-ref="dataSourcePoint"/> <aop:after method=""/> <aop:around method=""/> </aop:aspect> <aop:aspect></aop:aspect></aop:config><aop:aspect> 配置一个切面;<aop:pointcut>配置一个切点,基于切点表达式;<aop:before>,<aop:after>,<aop:around>是定义不同类型的advise. aspectBean 是切面的处理bean:public class DataSourceInterceptor { public void before(JoinPoint jp) { DataSourceTypeManager.set(DataSources.SLAVE); }}然后扫描Service包:```python<context:component-scan base-package="net.aazj.service,net.aazj.aop" /><div class="se-preview-section-delimiter"></div>
最后在service上进行注解:
@Service("userService")@Transactionalpublic class UserServiceImpl implements UserService{ @Autowired private UserMapper userMapper; @Transactional (readOnly=true) public User getUser(int userId) { System.out.println("in UserServiceImpl getUser"); System.out.println(DataSourceTypeManager.get()); return userMapper.getUser(userId); } public void addUser(String username){ userMapper.addUser(username);// int i = 1/0; // 测试事物的回滚 } public void deleteUser(int id){ userMapper.deleteByPrimaryKey(id);// int i = 1/0; // 测试事物的回滚 } @Transactional (rollbackFor = BaseBusinessException.class) public void addAndDeleteUser(String username, int id) throws BaseBusinessException{ userMapper.addUser(username); this.m1(); userMapper.deleteByPrimaryKey(id); } private void m1() throws BaseBusinessException { throw new BaseBusinessException("xxx"); } public int insertUser(User user) { return this.userMapper.insert(user); }}<div class="se-preview-section-delimiter"></div>搞定。这种事务配置方式,不需要我们书写pointcut表达式,而是我们在需要事务的类上进行注解。但是如果我们自己来写切面的代码时,还是要写pointcut表达式。下面看一个例子(自己写切面逻辑):首先去扫描 @Aspect 注解定义的 切面:<context:component-scan base-package="net.aazj.aop" /><div class="se-preview-section-delimiter"></div>切面代码:
import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;@Aspect // for aop@Component // for auto scan@Order(0) // execute before @Transactionalpublic class DataSourceInterceptor { @Pointcut("execution(public * net.aazj.service..*.get*(..))") public void dataSourceSlave(){}; @Before("dataSourceSlave()") public void before(JoinPoint jp) { DataSourceTypeManager.set(DataSources.SLAVE); }}<div class="se-preview-section-delimiter"></div>我们使用到了 @Aspect 来定义一个切面;@Component是配合<context:component-scan/>,不然扫描不到;@Order定义了该切面切入的顺序 ,因为在同一个切点,可能同时存在多个切面,那么在这多个切面之间就存在一个执行顺序的问题。该例子是一个切换数据源的切面,那么他应该在 事务处理 切面之前执行,所以我们使用 @Order(0) 来确保先切换数据源,然后加入事务处理。@Order的参数越小,优先级越高,默认的优先级最低:/** * Annotation that defines ordering. The value is optional, and represents order value * as defined in the {@link Ordered} interface. Lower values have higher priority. * The default value is {@code Ordered.LOWEST_PRECEDENCE}, indicating * lowest priority (losing to any other specified order value). */@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})public @interface Order { /** * The order value. Default is {@link Ordered#LOWEST_PRECEDENCE}. * @see Ordered#getOrder() */ int value() default Ordered.LOWEST_PRECEDENCE;}<div class="se-preview-section-delimiter"></div>关于数据源的切换可以参加专门的博文:http://www.cnblogs.com/digdeep/p/4512368.html
方法:
@Before("execution(public * net.aazj.service..*.getUser(..)) && args(userId,..)") public void before3(int userId) { System.out.println("userId-----" + userId); }它会拦截 net.aazj.service 包下或者子包下的getUser方法,并且该方法的第一个参数必须是int型的, 那么使用切点表达式args(userId,..) 就可以使我们在切面中的处理方法before3中可以访问这个参数。before2方法也让我们知道也可以通过 JoinPoint 参数来获得被拦截方法的参数数组。 JoinPoint 是每一个切面处理方法都具有的参数, @Around 类型的具有的参数类型为ProceedingJoinPoint。通过 JoinPoint或者 ProceedingJoinPoint 参数可以访问到被拦截对象的一些信息(参见上面的 before2 方法)。新闻热点
疑难解答