代理模式 - spring aop 抛砖

Posted 没有梦想-何必远方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了代理模式 - spring aop 抛砖相关的知识,希望对你有一定的参考价值。

一、什么是代理模式

    这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方
    法。

    举个例子来说明代理的作用:假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目
    的.明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想
    在现实中的一个例子。

    代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象。

    代理模式两种方式:
    1 静态代理:在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。

    2 动态代理:代理类并不是在Java代码中实现,而是在运行时期生成,相比静态代理,动态代理可以很方便的对委托类的方
    法进行统一处理,如添加方法调用次数、添加日志功能等等,动态代理分为jdk动态代理和cglib动态代理。

二、什么是spring aop

    spring 使用 aop 面向切面编程将程序中的交叉业务逻辑(比如安全,日志,事务),封装成一个切面,然后注入到目标
    业务逻辑中去。实现系统高内聚、低耦合,以弥补OOP编程思想的不足。

三、spring aop 如何实现的代理模式

1 创建时机:

    在ioc容器初始化bean的过程中进行拦截,创建代理对象并“偷梁换柱”,替换原来的bean。

2 创建过程:

//创建代理对象 : DefaultAopProxyFactory的createAopProxy方法
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()) 
                //如果被代理的对象是接口,则使用jdk代理生成代理对象
                return new JdkDynamicAopProxy(config);
            
            //否则使用cglib代理生成代理对象
            return new ObjenesisCglibAopProxy(config);
        
        else 
            return new JdkDynamicAopProxy(config);
        
    
//jdk代理机制创建代理对象
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);
    
//cglib代理机制创建代理对象
public Object getProxy(ClassLoader classLoader) 
        if (logger.isDebugEnabled()) 
            logger.debug("Creating CGLIB proxy: target source is " + this.advised.getTargetSource());
        

        try 
            Class<?> rootClass = this.advised.getTargetClass();
            Assert.state(rootClass != null, "Target class must be available for creating a CGLIB
             proxy");

            Class<?> proxySuperClass = rootClass;
            if (ClassUtils.isCglibProxyClass(rootClass)) 
                proxySuperClass = rootClass.getSuperclass();
                Class<?>[] additionalInterfaces = rootClass.getInterfaces();
                for (Class<?> additionalInterface : additionalInterfaces) 
                    this.advised.addInterface(additionalInterface);
                
            

            // Validate the class, writing log messages as necessary.
            validateClassIfNecessary(proxySuperClass, classLoader);

            // Configure CGLIB Enhancer...
            Enhancer enhancer = createEnhancer();
            if (classLoader != null) 
                enhancer.setClassLoader(classLoader);
                if (classLoader instanceof SmartClassLoader &&
                        ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) 
                    enhancer.setUseCache(false);
                
            
            enhancer.setSuperclass(proxySuperClass);
            enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
            enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
            enhancer.setStrategy(new UndeclaredThrowableStrategy(UndeclaredThrowableException.class));

            Callback[] callbacks = getCallbacks(rootClass);
            Class<?>[] types = new Class<?>[callbacks.length];
            for (int x = 0; x < types.length; x++) 
                types[x] = callbacks[x].getClass();
            
            // fixedInterceptorMap only populated at this point, after getCallbacks call above
            enhancer.setCallbackFilter(new ProxyCallbackFilter(
this.fixedInterceptorOffset));
            enhancer.setCallbackTypes(types);

            // Generate the proxy class and create a proxy instance.
            return createProxyClassAndInstance(enhancer, callbacks);
        
        catch (CodeGenerationException ex) 
            throw new AopConfigException("Could not generate CGLIB subclass of class [" +
                    this.advised.getTargetClass() + "]: " +
                    "Common causes of this problem include using a final class or a non-visible 
                    class",
                    ex);
        
        catch (IllegalArgumentException ex) 
            throw new AopConfigException("Could not generate CGLIB subclass of class [" +
                    this.advised.getTargetClass() + "]: " +
                    "Common causes of this problem include using a final class or a non-visible
                     class",
                    ex);
        
        catch (Exception ex) 
            // TargetSource.getTarget() failed
            throw new AopConfigException("Unexpected AOP exception", ex);
        
    

以上,可以看出,jdk动态代理和cglib的区别:

  • JDK动态代理只能对实现了接口的类生成代理,而不能针对类
  • CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成final

四、spring aop 的两种使用方法

1 使用配置文件:

<!-- spring配置文件中添加配置切面: 通知 + 切入点 -->
<!--step1 : 编写通知类: 根据准备执行的方式实现不同接口,如下为throw通知-->
<!-- 通知有几种方式,before/after/afterReturning/around/throw等,可自主选择 -->
public class ExceptionHandlerAdvice implements ThrowsAdvice
    public void afterThrowing(Method m, Object[] args, Object target, Exception ex) 
        log.error("Exception in method: " + m.getName() + " Exception is: ", ex);
    

<!--step2 : 组装通知类 -->
 <bean id="logger" class="com.etoak.util.LoggerAdvice"/>
<!--step3 : 配置切入点 -->
 <aop:config>
        <aop:pointcut expression="execution(* com.etoak.action.Stu*.add*(..))" id="pc"/>
        <!-- 将id="lc"这个通知类提供的功能引用给   id="pc"这个切入点指向的那组方法. -->
        <aop:advisor advice-ref="logger" pointcut-ref="pc"/>
    </aop:config>

2 使用注解:

<!--step1 : 首选添加注解配置,使aop注解生效 -->
<aop:aspectj-autoproxy/>
<!--step2 : 配置切点 -->
private static final String POINTCUT ="execution(int 
com.web.aop.impl.ArithmeticCalculatorImpl.*(int , int ))";
<!--step3 : 配置通知及方法,如下为返回通知,返回通知与after区别在可以取到返回值‘returnObj’ -->
@AfterReturning(value = POINTCUT, returning = "returnObj")
    public void logArgAndReturn(final JoinPoint point, final Object returnObj) 

关于 JoinPoint对象

  • Signature getSignature(); 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
  • Object[] getArgs(); 获取传入目标方法的参数对象
  • Object getTarget(); 获取被代理的对象
  • Object getThis(); 获取代理对象

五、spring aop 使用时需注意的问题

使用代理时要明确需要使用的对象是代理对象还是目标对象

例如在以下方法中出现的问题:

 @AfterReturning(value = POINTCUT, returning = "returnObj")
    public void logArgAndReturn(final JoinPoint point, final Object returnObj) 
        taskExecutor.execute(new Runnable() 
            @Override
            public void run() 
                try 
                    Object[] args = point.getArgs();
                    ......
                    Method method=((MethodSignature) point.getSignature()).getMethod();
                    Annotation an = method.getAnnotation(UserChangeLog.class);
                    ......
                 catch (Exception e) 
                    log.error("保存更新日志异常", e);
                
            

        );
    

我们拦截到了一个方法,方法上有@UserChangeLog注解,在下面的方法中却取不到,an 为null。这个问题的出现的原因我们简单来看,可能是因为我们通过代理对象来取注解,而代理对象生成是不会生成原始对象上带的注解,所以我们只能从目标对象上来取这个注解。
通过分析以上源码,可以看出,我们拦截的方法是实现接口的,所以采用了jdk动态代理,根据接口实现代理,而接口上是没有注解的,所以代理生成的方法也不会有注解。如果我们拦截的方法没有实现接口,那么使用cglib代理不会有问题。

如果我们必须用jdk代理,要解决此问题,有以下两种方法:

法1: 通过目标对象获取注解

 Method method=((MethodSignature) point.getSignature()).getMethod();
                    Signature signature = point.getSignature();
Method realMethod = point.getTarget().getClass().getDeclaredMethod(signature.getName(), 
method.getParameterTypes());
Annotation an = realMethod.getAnnotation(UserChangeLog.class);

法2 : 接口及接口方法实现都加上注解。

以上是关于代理模式 - spring aop 抛砖的主要内容,如果未能解决你的问题,请参考以下文章

AOP 理解

Spring AOP实现

Spring AOP的基石--Java动态代理

java设计模式--代理模式

java设计模式--代理模式

静态代理模式