代理模式

来源:互联网 发布:淘宝不发货卖家不说话 编辑:程序博客网 时间:2024/05/29 05:10

一、什么是代理模式

    这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方    法。    举个例子来说明代理的作用:假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目    的.明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想    在现实中的一个例子。    代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象。    代理模式两种方式:    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 : 接口及接口方法实现都加上注解。

原创粉丝点击