spring -aop学习一

AOP(Aspect-OrientedProgramming , 面向方面编程) 可以说是oop 的补充和完善

什么是aop
AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。 oop引入封装 继承 和多态性等概念来建立一种对象层次结构 用以模拟公共行为的一个集合 当我们需要为分散的对象引入公共的行为的时候 oop就显得无能为力了 也就是说 oop允许定义从上到下的关系 但是并不适合定义从左到右的关系 例如 日志功能 日志代码往往水平的散步在所有对象层次中 而与它散步到对象的核心功能毫无关系 对于其他类型的代码 入安全性 异常处理和透明的持续性也是如此 这种散步在各处的无关代码被称为横切代码 在oop 设计中 它导致了大量代码重复 而不利于各个模块的代码重复使用

介绍aop
AOP技术利用一种称为“横切”的技术,解剖封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,这样就能减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。

实现原理
代理模式分为 静态代理 和动态代理 以代理为基础 实现aop框架 再来研究aop的实现原理
一 静态代理 静态代理关键是在代理对象和目标对象实现共同的接口 并且代理对象持有目标对象的引用

  1. 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 {   
@Override
public 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);
}
  1. 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
//@Aspect
public 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包下,返回值为任意类型;方法名任意;参数不作限制的所有方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值