springAOP之framework包的解读(二)
来源:互联网 发布:汉语拼音拼读软件 编辑:程序博客网 时间:2024/05/15 22:46
ProxyFactory
package org.springframework.aop.framework;import org.aopalliance.intercept.Interceptor;import org.springframework.aop.TargetSource;import org.springframework.util.ClassUtils;/** * 规范的AOP代理工厂,而不是通过一个bean工厂 * 该类提供了一个简单的方法用代码获取我配置AOP代理 */@SuppressWarnings("serial")public class ProxyFactory extends ProxyCreatorSupport { /** 创建一个ProxyFactory */ public ProxyFactory() { } /** * 创建一个新的ProxyFactory。根据给定的目标实现将代理所有的接口 * @param target 代理的目标对象 */ public ProxyFactory(Object target) { setTarget(target); setInterfaces(ClassUtils.getAllInterfaces(target)); } /** * 创建一个新的ProxyFactory。无目标对象,只有接口。必须添加拦截器 * @param proxyInterfaces 代理必须实现的接口 */ public ProxyFactory(Class<?>... proxyInterfaces) { setInterfaces(proxyInterfaces); } /** * 根据给定的接口和拦截器创建一个新的ProxyFactory。 * 方便的方法去创建一个单独拦截器的代理,假定拦截器处理本身所有的调用,而不是委托目标对象,例如远程代理。 * @param proxyInterface 代理必须实现的接口 * @param interceptor 代理必须调用的拦截器 */ public ProxyFactory(Class<?> proxyInterface, Interceptor interceptor) { addInterface(proxyInterface); addAdvice(interceptor); } /** * 根据具体的TargetSource创建建一个ProxyFactory,使代理实现具体的接口。 * @param proxyInterface 代理必须实现的接口 * @param targetSource 代理必须调用的目标源(TargetSource) */ public ProxyFactory(Class<?> proxyInterface, TargetSource targetSource) { addInterface(proxyInterface); setTargetSource(targetSource); } /** * 根据工厂的设置创建一个新的代理 * 可以重复调用。如果我们添加或删除接口,效果会不同。可以添加和删除拦截器。 * 使用默认的类加载器:通常,线程上下文类加载器(代理创建需要时) * @return 代理的对象 */ public Object getProxy() { return createAopProxy().getProxy(); } /** * 根据当前工厂的设置创建新的代理。可以重复调用。如果我们添加或删除接口,效果会不同。可以添加和删除拦截器。 * 使用给定的类载器(代理创建需要时) * @param classLoader 类加载器,用于创建默认低级别代理功能的代理 * @return 代理的对象 */ public Object getProxy(ClassLoader classLoader) { return createAopProxy().getProxy(classLoader); } /** * 根据给定的接口和拦截器创建新的代理。方便的方法去创建单独拦截器的代理,假定拦截器处理自身所有的调用,而不是委托目标对象,例如远程代理。 * @param proxyInterface 代理必须实现的接口 * @param interceptor 代理必须调用的拦截器 * @return 代理的对象 * @see #ProxyFactory(Class, org.aopalliance.intercept.Interceptor) */ @SuppressWarnings("unchecked") public static <T> T getProxy(Class<T> proxyInterface, Interceptor interceptor) { return (T) new ProxyFactory(proxyInterface, interceptor).getProxy(); } /** * 创建代理,利用指定的目标源,并实现具体的接口 * @param proxyInterface 代理必须实现的接口 * @param targetSource 代理必须调用的目标源 * @return 代理的对象 */ @SuppressWarnings("unchecked") public static <T> T getProxy(Class<T> proxyInterface, TargetSource targetSource) { return (T) new ProxyFactory(proxyInterface, targetSource).getProxy(); } /** * 创建代理,利用TargetSource的继承类 * @param targetSource 代理必须调用的目标源 * @return 代理的对象 */ public static Object getProxy(TargetSource targetSource) { if (targetSource.getTargetClass() == null) { throw new IllegalArgumentException("Cannot create class proxy for TargetSource with null target class"); } ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTargetSource(targetSource); proxyFactory.setProxyTargetClass(true); return proxyFactory.getProxy(); }}
类ProxyFactoryBean
package org.springframework.aop.framework;import java.io.IOException;import java.io.ObjectInputStream;import java.io.Serializable;import java.util.ArrayList;import java.util.Arrays;import java.util.HashMap;import java.util.List;import java.util.Map;import org.aopalliance.aop.Advice;import org.aopalliance.intercept.Interceptor;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.aop.Advisor;import org.springframework.aop.TargetSource;import org.springframework.aop.framework.adapter.AdvisorAdapterRegistry;import org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry;import org.springframework.aop.framework.adapter.UnknownAdviceTypeException;import org.springframework.aop.target.SingletonTargetSource;import org.springframework.beans.BeansException;import org.springframework.beans.factory.BeanClassLoaderAware;import org.springframework.beans.factory.BeanFactory;import org.springframework.beans.factory.BeanFactoryAware;import org.springframework.beans.factory.BeanFactoryUtils;import org.springframework.beans.factory.FactoryBean;import org.springframework.beans.factory.FactoryBeanNotInitializedException;import org.springframework.beans.factory.ListableBeanFactory;import org.springframework.core.OrderComparator;import org.springframework.util.ClassUtils;import org.springframework.util.ObjectUtils;/** * FactoryBean的实现,基于Spring的BeanFactory构造一个AOP代理。 * MethodInterceptors和Advisors当前bean工厂的一系列bean的名称确定,通过指定interceptorNames属性。 * 列表的最后一个入口(entry)可以为目标bean的名称或TargetSource;然而,通常有更好的代替方式:使用属性"targetName"/"target"/"targetSource" * 全局的拦截器和顾问能够以工厂的级别添加。指定的拦截器和顾问在拦截列表中被扩展, * 利用列表中入口:"xxx*",根据前缀的bean名称进行匹配(例如:"global*"匹配"globalBean1" 和 "globalBean2","*"匹配所有定义的拦截器)。 * 匹配的拦截器根据它们返回的顺序值被应用,如果它们实现了Ordered接口。 * 当代理接口有提供,创建一个JDK代理,若没有,则创建一个实际目标类的CGLIB代理. * 后者会工作,当且仅当目标类不拥有final方法,而动态的子类会在运行时被创建 * 从当前工厂获取的通知(Advised)可以被强转为代理,或者获取ProxyFactoryBean的引用并以编程的方法操控它(代理)。 * 当存在独立的原型引用,该bean不会工作。然而,它将会以原型工作,随后从工厂获得。 * 改变拦截器,会使单例(包括已存在的引用)立即生效。然而,改变接口或目标对象,则需要从工厂获取新的实例。 * 这意味着从工厂获取的单实例没有相同的对象ID,不过,它们有相同的拦截器和目标对象,改变任何引用,将会改变所有对象。 */@SuppressWarnings("serial")public class ProxyFactoryBean extends ProxyCreatorSupport implements FactoryBean<Object>, BeanClassLoaderAware, BeanFactoryAware { /** * 拦截器列表的前缀值,表示全局匹配 */ public static final String GLOBAL_SUFFIX = "*"; protected final Log logger = LogFactory.getLog(getClass()); private String[] interceptorNames; private String targetName; private boolean autodetectInterfaces = true; private boolean singleton = true; private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance(); private boolean freezeProxy = false; private transient ClassLoader proxyClassLoader = ClassUtils.getDefaultClassLoader(); private transient boolean classLoaderConfigured = false; private transient BeanFactory beanFactory; /** Whether the advisor chain has already been initialized */ private boolean advisorChainInitialized = false; /** 如果是单例,缓存该单例的代理实例 */ private Object singletonInstance; /** * 设置代理的接口名称。若没指定接口,实际类的CGLIB会被创建。 * 这个方法实际与setInterfaces()一样,但是反射了TransactionProxyFactoryBean的setProxyInterfaces方法 */ public void setProxyInterfaces(Class<?>[] proxyInterfaces) throws ClassNotFoundException { setInterfaces(proxyInterfaces); } /** * 设置通知/顾问的bean名称。该方法通常用来设置bean工厂的工厂bean。引用的bean必须为Interceptor\Advisor或者Advice类型。 * 列表的最后一个入口(entry)可以为工厂中的任意一个bean名称 * 如果既不是Advice,也不是Advisor,一个新的单例目标源被添加并包装它(这里的它指entry?)。 * 目标bean无法使用,当属性"target"or "targetSource" or "targetName"有设置,在这种情况下,属性interceptorNames数组必须只包含Advice/Advisor的bean名称。 * 指定一个目标bean当作interceptorNames列表的最终名称,这种行为是过时的,将在新的spring中被移除。使用属性"targetName"替代 */ public void setInterceptorNames(String... interceptorNames) { this.interceptorNames = interceptorNames; } /** * 设置目标bean的名称。在"interceptorNames"数组的最后指定目标对象的名称是可选的。 * 你也可以直接指定一个目标对象或才一个目标源对象,分别通过"target"/"targetSource"属性指定 */ public void setTargetName(String targetName) { this.targetName = targetName; } /** * 自动检查代理接口,若没指定。默认为true,关闭这个标志用来创建一个CGLIB代理,对于所有的目标对类,如果没有指定接口。 */ public void setAutodetectInterfaces(boolean autodetectInterfaces) { this.autodetectInterfaces = autodetectInterfaces; } /** * 设置单例属性。管理工厂总是返回相同的代理实例(说明是同一个目标对象),或者它总是返回一个新的原型实例, * 表示目标和拦截器也可能是新的实例,如果它们是从原型的bean定义中获取的。在对象图中,该方法允许很友好的唯一、独立的控制 */ public void setSingleton(boolean singleton) { this.singleton = singleton; } /** * 指定AdvisorAdapterRegistry,默认为全局的AdvisorAdapterRegistry */ public void setAdvisorAdapterRegistry(AdvisorAdapterRegistry advisorAdapterRegistry) { this.advisorAdapterRegistry = advisorAdapterRegistry; } @Override public void setFrozen(boolean frozen) { this.freezeProxy = frozen; } /** * 设置类加载器用来生成代理类,默认为bean类加载器,例如:控制BeanFactory加载所有bean类的类加载器 * 该方法可被重写,在指定的代理中。 */ public void setProxyClassLoader(ClassLoader classLoader) { this.proxyClassLoader = classLoader; this.classLoaderConfigured = (classLoader != null); } @Override public void setBeanClassLoader(ClassLoader classLoader) { if (!this.classLoaderConfigured) { this.proxyClassLoader = classLoader; } } @Override public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; checkInterceptorNames(); } /** * 返回一个代理。客户端从工厂bean获取beans时调用。通过该工厂创建一个AOP代理的实例返回。 * 该实例将被缓存为单例,并且代理每一次的调用都创建 * @return 一个刷新的AOP代理,影响该工厂的当前状态 */ @Override public Object getObject() throws BeansException { initializeAdvisorChain(); if (isSingleton()) { return getSingletonInstance(); } else { if (this.targetName == null) { logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " + "Enable prototype proxies by setting the 'targetName' property."); } return newPrototypeInstance(); } } /** * 返回代理的类型。若单例已创建,则检查, * 否则回退到代理的接口(如果只是单独的类型),或目标bean类型,或目标源的目标类。 */ @Override public Class<?> getObjectType() { synchronized (this) { if (this.singletonInstance != null) { return this.singletonInstance.getClass(); } } Class<?>[] ifcs = getProxiedInterfaces(); if (ifcs.length == 1) { return ifcs[0]; } else if (ifcs.length > 1) { return createCompositeInterface(ifcs); } else if (this.targetName != null && this.beanFactory != null) { return this.beanFactory.getType(this.targetName); } else { return getTargetClass(); } } @Override public boolean isSingleton() { return this.singleton; } /** * 根据给定的接口创建组合接口类,在单独的类里实现给定的接口。 * 默认的实现为给这的接口构造一个JDK代理类。 * @param interfaces 合并的接口 * @return 合并后的接口类 */ protected Class<?> createCompositeInterface(Class<?>[] interfaces) { return ClassUtils.createCompositeInterface(interfaces, this.proxyClassLoader); } /** * 返回该类的代理对象的单例,如果还没被创建,则延迟创建。 * @return 共享的单例代理 */ private synchronized Object getSingletonInstance() { if (this.singletonInstance == null) { this.targetSource = freshTargetSource(); if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) { // Rely on AOP infrastructure to tell us what interfaces to proxy. Class<?> targetClass = getTargetClass(); if (targetClass == null) { throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy"); } setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader)); } // Initialize the shared singleton instance. super.setFrozen(this.freezeProxy); this.singletonInstance = getProxy(createAopProxy()); } return this.singletonInstance; } /** * 创建一个该类已创建的代理对象的原型实例,依靠独立的AdvisedSupport配置。 * @return 一个完全独立的代理,独立操作通知(adivce) */ private synchronized Object newPrototypeInstance() { // 对于一个原型来说,我们需要提供代理,该代理为一个配置独立的实例 // 在这种情况下,该对象配置的实例不会有代理,但有一个单独的拷贝 if (logger.isTraceEnabled()) { logger.trace("Creating copy of prototype ProxyFactoryBean config: " + this); } ProxyCreatorSupport copy = new ProxyCreatorSupport(getAopProxyFactory()); // The copy needs a fresh advisor chain, and a fresh TargetSource. TargetSource targetSource = freshTargetSource(); copy.copyConfigurationFrom(this, targetSource, freshAdvisorChain()); if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) { // Rely on AOP infrastructure to tell us what interfaces to proxy. copy.setInterfaces( ClassUtils.getAllInterfacesForClass(targetSource.getTargetClass(), this.proxyClassLoader)); } copy.setFrozen(this.freezeProxy); if (logger.isTraceEnabled()) { logger.trace("Using ProxyCreatorSupport copy: " + copy); } return getProxy(copy.createAopProxy()); } /** * 返回暴露的代理对象 * 默认实现以工厂的bean类加载器调用getProxy()。指定一个自定义的类加载器可以被重写 * @param aopProxy 事先准备好的AopProxy实例,用于获取代理 * @return 暴露的代理对象 */ protected Object getProxy(AopProxy aopProxy) { return aopProxy.getProxy(this.proxyClassLoader); } /** * 检查interceptorNames列表,是否包括最后元素的目标名称,若找到,删除并设置为targetName */ private void checkInterceptorNames() { if (!ObjectUtils.isEmpty(this.interceptorNames)) { String finalName = this.interceptorNames[this.interceptorNames.length - 1]; if (this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) { // The last name in the chain may be an Advisor/Advice or a target/TargetSource. // Unfortunately we don't know; we must look at type of the bean. if (!finalName.endsWith(GLOBAL_SUFFIX) && !isNamedBeanAnAdvisorOrAdvice(finalName)) { // The target isn't an interceptor. this.targetName = finalName; if (logger.isDebugEnabled()) { logger.debug("Bean with name '" + finalName + "' concluding interceptor chain " + "is not an advisor class: treating it as a target or TargetSource"); } String[] newNames = new String[this.interceptorNames.length - 1]; System.arraycopy(this.interceptorNames, 0, newNames, 0, newNames.length); this.interceptorNames = newNames; } } } } /** * 检查bean工厂元数据解决该bean的名称(interceptorNames列表)是一个Advisor或Advice,或者可能为目标对象。 * @param beanName 检查的bean名称 * @return 为Advisor or Advice返回true */ private boolean isNamedBeanAnAdvisorOrAdvice(String beanName) { Class<?> namedBeanClass = this.beanFactory.getType(beanName); if (namedBeanClass != null) { return (Advisor.class.isAssignableFrom(namedBeanClass) || Advice.class.isAssignableFrom(namedBeanClass)); } // Treat it as an target bean if we can't tell. if (logger.isDebugEnabled()) { logger.debug("Could not determine type of bean with name '" + beanName + "' - assuming it is neither an Advisor nor an Advice"); } return false; } /** * 创建顾问(拦截器)链。由BeanFactory产生的顾问在每次一个新的原型实例被添加时会刷新。 * 通过工厂API以编程的方法添加的拦截器不会因这些改变(哪些改变?)而生效 */ private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException { if (this.advisorChainInitialized) { return; } if (!ObjectUtils.isEmpty(this.interceptorNames)) { if (this.beanFactory == null) { throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " + "- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames)); } // Globals can't be last unless we specified a targetSource using the property... if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) && this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) { throw new AopConfigException("Target required after globals"); } // Materialize interceptor chain from bean names. for (String name : this.interceptorNames) { if (logger.isTraceEnabled()) { logger.trace("Configuring advisor or advice '" + name + "'"); } if (name.endsWith(GLOBAL_SUFFIX)) { if (!(this.beanFactory instanceof ListableBeanFactory)) { throw new AopConfigException( "Can only use global advisors or interceptors with a ListableBeanFactory"); } addGlobalAdvisor((ListableBeanFactory) this.beanFactory, name.substring(0, name.length() - GLOBAL_SUFFIX.length())); } else { // If we get here, we need to add a named interceptor. // We must check if it's a singleton or prototype. Object advice; if (this.singleton || this.beanFactory.isSingleton(name)) { // Add the real Advisor/Advice to the chain. advice = this.beanFactory.getBean(name); } else { // It's a prototype Advice or Advisor: replace with a prototype. // Avoid unnecessary creation of prototype bean just for advisor chain initialization. advice = new PrototypePlaceholderAdvisor(name); } addAdvisorOnChainCreation(advice, name); } } } this.advisorChainInitialized = true; } /** * 返回独立的顾问链。每次一个新的原型实例返回,我们调用该方法, * 返回不同的原型的Advisors和Advices实例 */ private List<Advisor> freshAdvisorChain() { Advisor[] advisors = getAdvisors(); List<Advisor> freshAdvisors = new ArrayList<Advisor>(advisors.length); for (Advisor advisor : advisors) { if (advisor instanceof PrototypePlaceholderAdvisor) { PrototypePlaceholderAdvisor pa = (PrototypePlaceholderAdvisor) advisor; if (logger.isDebugEnabled()) { logger.debug("Refreshing bean named '" + pa.getBeanName() + "'"); } // Replace the placeholder with a fresh prototype instance resulting // from a getBean() lookup if (this.beanFactory == null) { throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " + "- cannot resolve prototype advisor '" + pa.getBeanName() + "'"); } Object bean = this.beanFactory.getBean(pa.getBeanName()); Advisor refreshedAdvisor = namedBeanToAdvisor(bean); freshAdvisors.add(refreshedAdvisor); } else { // Add the shared instance. freshAdvisors.add(advisor); } } return freshAdvisors; } /** * 添加所有的全局拦截器和切入点 */ private void addGlobalAdvisor(ListableBeanFactory beanFactory, String prefix) { String[] globalAdvisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Advisor.class); String[] globalInterceptorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Interceptor.class); List<Object> beans = new ArrayList<Object>(globalAdvisorNames.length + globalInterceptorNames.length); Map<Object, String> names = new HashMap<Object, String>(beans.size()); for (String name : globalAdvisorNames) { Object bean = beanFactory.getBean(name); beans.add(bean); names.put(bean, name); } for (String name : globalInterceptorNames) { Object bean = beanFactory.getBean(name); beans.add(bean); names.put(bean, name); } OrderComparator.sort(beans); for (Object bean : beans) { String name = names.get(bean); if (name.startsWith(prefix)) { addAdvisorOnChainCreation(bean, name); } } } /** * 当通知链创建时调用。添加advice、advisor、对象其他目标对象到拦截器列表。 * 因为有三个可能性,我们不能定义更强的类型 * @param next advice, advisor or target object * @param name 在我们自己的bean工厂获取的目标对象的bean名称 */ private void addAdvisorOnChainCreation(Object next, String name) { // We need to convert to an Advisor if necessary so that our source reference // matches what we find from superclass interceptors. Advisor advisor = namedBeanToAdvisor(next); if (logger.isTraceEnabled()) { logger.trace("Adding advisor with name '" + name + "'"); } addAdvisor(advisor); } /** * 创建代理时,返回的目标源。在interceptorNames列表的最后,目标没指定,TargetSource将成为这个类的TargetSource成员。 * 否则,我们获取目标,而且如果需要,将它包装为TargetSource */ private TargetSource freshTargetSource() { if (this.targetName == null) { if (logger.isTraceEnabled()) { logger.trace("Not refreshing target: Bean name not specified in 'interceptorNames'."); } return this.targetSource; } else { if (this.beanFactory == null) { throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " + "- cannot resolve target with name '" + this.targetName + "'"); } if (logger.isDebugEnabled()) { logger.debug("Refreshing target with name '" + this.targetName + "'"); } Object target = this.beanFactory.getBean(this.targetName); return (target instanceof TargetSource ? (TargetSource) target : new SingletonTargetSource(target)); } } /** * 通过在interceptorNames数组内调用getBean()生产的对象转化为Advisor或TargetSource */ private Advisor namedBeanToAdvisor(Object next) { try { return this.advisorAdapterRegistry.wrap(next); } catch (UnknownAdviceTypeException ex) { // We expected this to be an Advisor or Advice, // but it wasn't. This is a configuration error. throw new AopConfigException("Unknown advisor type " + next.getClass() + "; Can only include Advisor or Advice type beans in interceptorNames chain except for last entry," + "which may also be target or TargetSource", ex); } } /** * 清除并重新生成单例,当通知改变时 */ @Override protected void adviceChanged() { super.adviceChanged(); if (this.singleton) { logger.debug("Advice has changed; recaching singleton instance"); synchronized (this) { this.singletonInstance = null; } } } //--------------------------------------------------------------------- // Serialization support //--------------------------------------------------------------------- private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // Rely on default serialization; just initialize state after deserialization. ois.defaultReadObject(); // Initialize transient fields. this.proxyClassLoader = ClassUtils.getDefaultClassLoader(); } /** * 在拦截器链中使用,当我们在创建一个代理时需要用原型替代一个bean. */ private static class PrototypePlaceholderAdvisor implements Advisor, Serializable { private final String beanName; private final String message; public PrototypePlaceholderAdvisor(String beanName) { this.beanName = beanName; this.message = "Placeholder for prototype Advisor/Advice with bean name '" + beanName + "'"; } public String getBeanName() { return beanName; } @Override public Advice getAdvice() { throw new UnsupportedOperationException("Cannot invoke methods: " + this.message); } @Override public boolean isPerInstance() { throw new UnsupportedOperationException("Cannot invoke methods: " + this.message); } @Override public String toString() { return this.message; } }}
0 0
- springAOP之framework包的解读(二)
- springAOP之framework包的解读(一)
- springAOP之framework包的解读(三)
- springAOP之framework包的解读(四)
- SpringAOP之我的理解(二)------具体实现
- 解读QML之二
- 解读QML之二
- 使用springAOP时没有引入aopalliance包时报的错
- 官方解读Activity之二
- 个人解读Activity之二
- 解读Content Provider之二
- 解读商业智能之二 - 商业智能的组成部分
- Android开发的基础常识二之程序解读
- ngxin之main函数的解读(二)
- u3d honey hex framework 代码解读记录(二)
- SpringAOP之HelloWorld
- SpringAOP之学习总结
- SpringAOP之Advisor方式
- python函数变量的作用域
- C/C++ volatile
- codefoceA--New Year Transportation
- NS_OPTIONS
- 100款编程在线工具|果断收藏!
- springAOP之framework包的解读(二)
- jetty与tomcat区别
- 二进制
- Spring MVC配置
- arrts
- Python命名空间和作用域窥探
- 判断结尾是不是某个字符
- hdu2874 Connections between cities--LCA
- jQuery fullpage全屏的步骤及注意事项