在了解Spring AOP的实现之前,先了解一些Spring AOP的相关概念
AOP的相关概念
在使用Spring进行AOP相关的编程时,我们经常使用Advice (通知), PointCut (切点), Advisor (通知器)来实现我们需要的功能。
Advice
Advice是AOP联盟定义的一个接口,定义了我们可以在切点做些什么,即我们希望织入的增强逻辑,为切面增强提供织入的入口。在Spring中,Advice作为一个统一的接口,Spring在Advice的基础上定义了具体的通知类型,比如,
BeforeAdvice: 前置增强接口,在目标方法调用之前回调。
AfterAdvice : 后置增强接口,在目标方法调用结束并成功返回时回调。
ThrowAdvice : 在抛出异常时回调。
Interceptor: 表示一个通用的拦截器,可以在方法的调用前后进行增强。
DynamicIntroductionAdvice: 与上面的Advice和Interceptor不同,DynamicIntroductionAdvice不对方法进行增强,而是动态的引入新的接口实现。我们可以为目标类添加一个接口的实现(原来目标类未实现某个接口),那么通过DynamicIntroductionAdvice增强可以为目标类创建实现某接口的代理。
Pointcut
Pointcut 决定Advice可以作用于哪些连接点,即通过Pointcut我们可以定义需要增强的方法的集合。这些方法的集合可以通过Pointcut中定义的规则来选取,当方法符合Pointcut定义的规则时,返回true。这些规则可是正则表达式,也可以是字符串的匹配等。
Spring定义了Pointcut的接口,Pointcut接口中定义了用于获取类过滤器和方法匹配器的抽象方法。
public interface Pointcut {
/**
* Return the ClassFilter for this pointcut.
* @return the ClassFilter (never {@code null})
*/
ClassFilter getClassFilter();
/**
* Return the MethodMatcher for this pointcut.
* @return the MethodMatcher (never {@code null})
*/
MethodMatcher getMethodMatcher();
/**
* Canonical Pointcut instance that always matches.
*/
Pointcut TRUE = TruePointcut.INSTANCE;
}
但是有了类过滤器和方法匹配器,我们还需要知道如何使用类过滤器和方法匹配器,因此在实现Pointcut的同时也需要实现MethodMatcher。MethodMatcher定义了matches方法,即用于规则匹配的方法。
public interface MethodMatcher {
/**
* Perform static checking whether the given method matches.
* If this returns {@code false} or if the {@link #isRuntime()}
* method returns {@code false}, no runtime check (i.e. no
* {@link #matches(java.lang.reflect.Method, Class, Object[])} call)
* will be made.
* @param method the candidate method
* @param targetClass the target class
* @return whether or not this method matches statically
*/
boolean matches(Method method, Class targetClass);
/**
* Is this MethodMatcher dynamic, that is, must a final call be made on the
* {@link #matches(java.lang.reflect.Method, Class, Object[])} method at
* runtime even if the 2-arg matches method returns {@code true}?
*
Can be invoked when an AOP proxy is created, and need not be invoked
* again before each method invocation,
* @return whether or not a runtime match via the 3-arg
* {@link #matches(java.lang.reflect.Method, Class, Object[])} method
* is required if static matching passed
*/
boolean isRuntime();
/**
* Check whether there a runtime (dynamic) match for this method,
* which must have matched statically.
*
This method is invoked only if the 2-arg matches method returns
* {@code true} for the given method and target class, and if the
* {@link #isRuntime()} method returns {@code true}. Invoked
* immediately before potential running of the advice, after any
* advice earlier in the advice chain has run.
* @param method the candidate method
* @param targetClass the target class
* @param args arguments to the method
* @return whether there's a runtime match
* @see MethodMatcher#matches(Method, Class)
*/
boolean matches(Method method, Class targetClass, Object... args);
/**
* Canonical instance that matches all methods.
*/
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
下图给出了Spring中一些Pointcut的继承关系,可以看到具体的实现都集成了Pointcut接口和MethodMatcher接口。
Advisor
前面介绍了Advice的增强逻辑,Pointcut定义了方法的集合,但是哪些方法使用什么样的增强逻辑依旧没有关联起来,Advisor就是将Advice和Pointcut结合起来,通过Advisor,可以定义在某个Pointcut连接点上使用哪个Advice。 Spring 提供了一个DefaultPointcutAdvisor, 在DefaultPointcutAdvisor中有两个属性,分别为advice和pointcut用来配置Advice 和Pointcut。 其实现如下所示。
public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {
private Pointcut pointcut = Pointcut.TRUE;
/**
* Create an empty DefaultPointcutAdvisor.
* Advice must be set before use using setter methods.
* Pointcut will normally be set also, but defaults to {@code Pointcut.TRUE}.
*/
public DefaultPointcutAdvisor() {
}
/**
* Create a DefaultPointcutAdvisor that matches all methods.
*
{@code Pointcut.TRUE} will be used as Pointcut.
* @param advice the Advice to use
*/
public DefaultPointcutAdvisor(Advice advice) {
this(Pointcut.TRUE, advice);
}
/**
* Create a DefaultPointcutAdvisor, specifying Pointcut and Advice.
* @param pointcut the Pointcut targeting the Advice
* @param advice the Advice to run when Pointcut matches
*/
public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
this.pointcut = pointcut;
setAdvice(advice);
}
/**
* Specify the pointcut targeting the advice.
*
Default is {@code Pointcut.TRUE}.
* @see #setAdvice
*/
public void setPointcut(@Nullable Pointcut pointcut) {
this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public String toString() {
return getClass().getName() + ": pointcut [" + getPointcut() + "]; advice [" + getAdvice() + "]";
}
}
Spring AOP的实现
前面已经介绍了Spring AOP的相关概念,但是Spring AOP是如何对方法的调用进行拦截的呢?下面就Spring AOP的实现进行分析。 同样以Spring中的单元测试开始Spring AOP的实现分析。 通过以下代码开始Spring AOP的实现分析。
@Test
public void testProxyFactory() {
TestBean target = new TestBean();
ProxyFactory pf = new ProxyFactory(target);
NopInterceptor nop = new NopInterceptor();
CountingBeforeAdvice cba = new CountingBeforeAdvice();
Advisor advisor = new DefaultPointcutAdvisor(cba);
pf.addAdvice(nop);
pf.addAdvisor(advisor);
ITestBean proxied = (ITestBean) pf.getProxy();
proxied.setAge(5);
assertThat(cba.getCalls()).isEqualTo(1);
assertThat(nop.getCount()).isEqualTo(1);
assertThat(pf.removeAdvisor(advisor)).isTrue();
assertThat(proxied.getAge()).isEqualTo(5);
assertThat(cba.getCalls()).isEqualTo(1);
assertThat(nop.getCount()).isEqualTo(2);
}
上述的代码中创建了一个TestBean,NopInterceptor, CountingBeforeAdvice对象,并使用TestBean初始化了ProxyFactory,CountingBeforeAdvice对象初始化DefaultPointcutAdvisor,同时将NopInterceptorDefaultPointcutAdvisor添加到ProxyFactory中。可以看到上述的代码中没有指明Pointcut, 也就意味着使用了默认的Pointcut.TRUE, 即对所有的方法都进行增强。
首先来看一下ProxyFactory的继承关系。
从上往下看,首先是TargetClassAware, 定义了一个getTargetClass()方法用来获取目标对象的Class,Advised继承了该接口,Advised接口定义了获取和设置AOP 代理工厂(Aop proxy factory)配置的方法,具体代码如下:
public interface Advised extends TargetClassAware {
boolean isFrozen();
boolean isProxyTargetClass();
Class[] getProxiedInterfaces();
boolean isInterfaceProxied(Class intf);
void setTargetSource(TargetSource targetSource);
TargetSource getTargetSource();
void setExposeProxy(boolean exposeProxy);
boolean isExposeProxy();
void setPreFiltered(boolean preFiltered);
boolean isPreFiltered();
Advisor[] getAdvisors();
default int getAdvisorCount() {
return getAdvisors().length;
}
void addAdvisor(Advisor advisor) throws AopConfigException;
void addAdvisor(int pos, Advisor advisor) throws AopConfigException;
boolean removeAdvisor(Advisor advisor);
void removeAdvisor(int index) throws AopConfigException;
int indexOf(Advisor advisor);
boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;
void addAdvice(Advice advice) throws AopConfigException;
void addAdvice(int pos, Advice advice) throws AopConfigException;
boolean removeAdvice(Advice advice);
int indexOf(Advice advice);
String toProxyConfigString();
}
而ProxyConfig则保存了AOP 代理工厂的部分属性,可以看成是一个数据基类,如下:
public class ProxyConfig implements Serializable {
...
private boolean proxyTargetClass = false;
private boolean optimize = false;
boolean opaque = false;
boolean exposeProxy = false;
private boolean frozen = false;
...
}
AdvisedSupport 继承了ProxyConfig同时实现了Advised接口,封装了AOP对Advice和Advisor的相关操作。
/** The AdvisorChainFactory to use. */
AdvisorChainFactory advisorChainFactory = new DefaultAdvisorChainFactory();
/** Cache with Method as key and advisor chain List as value. */
private transient Map> methodCache;
/**
* Interfaces to be implemented by the proxy. Held in List to keep the order
* of registration, to create JDK proxy with specified order of interfaces.
*/
private List> interfaces = new ArrayList<>();
/**
* List of Advisors. If an Advice is added, it will be wrapped
* in an Advisor before being added to this List.
*/
private List advisors = new ArrayList<>();
ProxyCreatorSupport 则提供了设置ProxyFactory和创建代理对象的方法,创建的具体的代理对象则交给具体的ProxyFactory完成。 最下面的则是三个具体的ProxyFactory的实现,分别为:
ProxyFactory,可以在IOC容器中使用声明式配置AOP。
ProxyFactoryBean,需要编程式的使用AOP
AspectProxyFactory, 对于使用AspectJ的AOP应用,集成了Spring和AspectJ。
了解了ProxyFactory的继承关系后,继续往下看,我们已经知道了具体的代理的对象的创建交给具体的ProxyFactory。 我们主要关注下面用于获取代理对象的这行代码:
ITestBean proxied = (ITestBean) pf.getProxy();
ProxyFactory 的getProxy的实现如下:
public Object getProxy() {
return createAopProxy().getProxy();
}
getProxy()调用ProxyCreatorSupport的createAopProxy()用于创建AopProxy。
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
return getAopProxyFactory().createAopProxy(this);
}
createAopProxy()先通过getAopProxyFactory()获取AopProxyFactory。getAopProxyFactory()直接返回一个DefaultAopProxyFactory的对象,然后调用DefaultAopProxyFactory的createAopProxy()方法创建具体的AopProxy,并传入this指针,即ProxyFactory对象本身,因为ProxyFactory继承了AdvisedSupport。
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!NativeDetector.inNativeImage() &&
(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() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class[] ifcs = config.getProxiedInterfaces();
return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}
config.isOptimize():表示是否使用了优化策略,配置的属性optimize值决定;
config.isProxyTargetClass():表示是否是代理目标类,配置的属性proxy-target-class值决定;
hasNoUserSuppliedProxyInterfaces():就是在判断代理的对象是否有实现接口
当代理的是接口时,则使用JdkDynamicAopProxy,否则使用ObjenesisCglibAopProxy()。 JdkDynamicAopProxy保存了config和需要代理的接口。
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
Assert.notNull(config, "AdvisedSupport must not be null");
if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
throw new AopConfigException("No advisors and no TargetSource specified");
}
this.advised = config;
this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces);
}
当advised没有实现SpringProxy,Advised, DecoratingProxy 接口AopProxyUtils.completeProxiedInterfaces()会分别添加这三个接口。
到这里AopProxyFactory就实例化完成了。继续看getProxy()做了什么。
public Object getProxy() {
return getProxy(ClassUtils.getDefaultClassLoader());
}
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}
通过将classLoader, proxiedInterfaces和this传入到newProxyInstance中去创建了目标对象的代理对象。JdkDynamicAopProxy实现了InvocationHandler接口,因此可以将this指针传进去创建代理对象。
代理对象创建完成之后,当我们调用代理对象的方法时,就会回调JdkDynamicAopProxy的invoke()方法。到这里我们只看见了代理对象的创建,依旧没有看到怎么对方法进行增强的逻辑,因为对代码进行增强的实现就在invoke()方法里面。
接下来看一下invoke()方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
else if (method.getDeclaringClass() == DecoratingProxy.class) {
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
Object retVal;
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
Class targetClass = (target != null ? target.getClass() : null);
// Get the interception chain for this method.
List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// We need to create a method invocation...
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
// Massage return value if necessary.
Class returnType = method.getReturnType();
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
}
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
invoke()的入参为代理对象,调用的方法,以及调用方法的参数。invoke()方首先检查method是不是equal,hashCode方法,以及declaringClass是不是DecoratingProxy,是不是需要将proxy设置到AopContext里面。做完这一系列的检查之后,通过getInterceptorsAndDynamicInterceptionAdvice()获取Interceptor和Advice保存到chain中,如果chain为空,表示没有interceptor和Advice,则直接通过反射的方法调用目标方法,invokeJoinpointUsingReflection()方法封装反射调用的逻辑。如果非空,则构造一个ReflectiveMethodInvocation对象,ReflectiveMethodInvocation对象的proceed方法封装了Advice方法的增强逻辑。
先来看一下getInterceptorsAndDynamicInterceptionAdvice()的实现:
public List getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class targetClass) {
MethodCacheKey cacheKey = new MethodCacheKey(method);
List cached = this.methodCache.get(cacheKey);
if (cached == null) {
cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
this, method, targetClass);
this.methodCache.put(cacheKey, cached);
}
return cached;
}
先将method封装成MethodCacheKey,然后尝试从缓存中获取这个key对应的缓存,如果没有,则通过advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice()去获取,这里的advisorChainFactory的默认实现是DefaultAdvisorChainFactory,看一下getInterceptorsAndDynamicInterceptionAdvice()的实现。
public List getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, @Nullable Class targetClass) {
// This is somewhat tricky... We have to process introductions first,
// but we need to preserve order in the ultimate list.
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
Advisor[] advisors = config.getAdvisors();
List interceptorList = new ArrayList<>(advisors.length);
Class actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
Boolean hasIntroductions = null;
for (Advisor advisor : advisors) {
if (advisor instanceof PointcutAdvisor) {
// Add it conditionally.
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
boolean match;
if (mm instanceof IntroductionAwareMethodMatcher) {
if (hasIntroductions == null) {
hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
}
match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);
}
else {
match = mm.matches(method, actualClass);
}
if (match) {
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
if (mm.isRuntime()) {
// Creating a new object instance in the getInterceptors() method
// isn't a problem as we normally cache created chains.
for (MethodInterceptor interceptor : interceptors) {
interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
}
}
else {
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
}
else if (advisor instanceof IntroductionAdvisor) {
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
else {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
return interceptorList;
}
上述的代码首先通过GlobalAdvisorAdapterRegistry.getInstance()获取了DefaultAdvisorAdapterRegistry的实例,DefaultAdvisorAdapterRegistry注册了三种Adviced的适配器,用于将Advice适配成Interceptor。
public DefaultAdvisorAdapterRegistry() {
registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
registerAdvisorAdapter(new AfterReturningAdviceAdapter());
registerAdvisorAdapter(new ThrowsAdviceAdapter());
}
然后对我们添加的Advisor逐个遍历,首先判断是不是PointcutAdvisor,然后判断是不是IntroductionAdvisor,如果都不是则认为是Interceptor。如果当前的advisor是PointcutAdvisor,则先判断是不是提前过滤过了,或者class是否符合ClassFilter中定义的规则,如果进一步判断MethodMatcher的类型以及method是否匹配。无论是PointcutAdvisor,IntroductionAdvisor还是Interceptor, 最后都通过 registry.getInterceptors()方法对advisor进行适配,将advisor对象通过响应的适配器适配成MethodInterceptor的一个实例。具体的实现如下所示:
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
List interceptors = new ArrayList<>(3);
Advice advice = advisor.getAdvice();
if (advice instanceof MethodInterceptor) {
interceptors.add((MethodInterceptor) advice);
}
for (AdvisorAdapter adapter : this.adapters) {
if (adapter.supportsAdvice(advice)) {
interceptors.add(adapter.getInterceptor(advisor));
}
}
if (interceptors.isEmpty()) {
throw new UnknownAdviceTypeException(advisor.getAdvice());
}
return interceptors.toArray(new MethodInterceptor[0]);
}
看一下其中一个adapter的实现。
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice);
}
}
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {
private final MethodBeforeAdvice advice;
public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
}
@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
return mi.proceed();
}
}
可以看到对于BeforeAdvice最终被是配成了MethodBeforeAdviceInterceptor,实现了MethodInterceptor接口,其中invoke方法就是后面拦截器链的入口。
继续看proceed的实现。
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
从索引为-1开始递增,如果所有的Interceptor或者Advice都调用完毕,则通过反射调用目标函数。如果当前的interceptorOrInterceptionAdvice是InterceptorAndDynamicMethodMatcher的实例,则先通matches方法进行匹配,如果匹配成功,则调用interceptor的invoke方法,否则跳过,如果不是InterceptorAndDynamicMethodMatcher的实例则时表示是一个interceptor,也直接调用invoke方法。
结合上述的MethodInterceptor的invoke方法,可以看到所有的Advice和interceptor串成了一条拦截器链,从头开始,通过matches方法进行匹配,匹配成功则进行增强,否则继续往下查找,直到尾部,调用目标方法,整个过程就是对目标方法的增强过程,也是AOP的实现原理。
总结
本文以ProxyFactory为例分析了Spring AOP的实现,其实现原理大致可以分为三个部分:
Advice, Pointcut,Advisor的实现
目标对象代理对象的生成。
对Advice进行适配,并组装成一条拦截器链,通过拦截器链对目标方法进行增强。
你可能感兴趣的:(Spring AOP 的实现)
B/S架构和C/S架构概述与优缺点
此方konata
架构 php 开发语言
B/S架构(Browser/Server架构)定义B/S架构是一种通过浏览器访问服务器的架构模式。客户端(通常是浏览器)与服务器之间通过HTTP/HTTPS协议进行通信。优点易于部署和维护:客户端只需要安装浏览器,无需单独安装客户端软件。服务器端的更新和维护只需在一个地方进行,客户端自动生效。跨平台性:只要支持HTTP协议的浏览器,就可以访问应用程序,不受操作系统限制。提高了应用程序的可用性和灵活
介绍一下Qt中的动态属性
已是上好佳
qt 数据库 开发语言 c++
在Qt中,动态属性是一种强大且灵活的特性,它允许你在运行时为对象添加、修改和查询属性,而不需要在类的定义中预先声明这些属性。下面为你详细介绍Qt动态属性的相关内容:1.动态属性的基本概念在传统的C++类中,属性通常是在类的定义里通过成员变量来表示的,并且在编译时就已经确定。而Qt的动态属性打破了这种限制,它可以在程序运行期间为任何继承自QObject的对象添加额外的属性,这些属性以键值对的形式存储
HashMap源码解读
十五001
基础 哈希算法 散列表 算法
1.HashMap概述HashMap是基于哈希表的Map接口实现,允许空键和空值。它继承自AbstractMap,实现了Map、Cloneable和Serializable接口。2.底层数据结构在JDK1.8中,HashMap的底层数据结构由数组+链表+红黑树构成:数组:存储哈希表的节点(Node)。链表:解决哈希冲突,当多个键的哈希值相同或相近时,它们会被存储在同一个数组槽位的链表中。红黑树:当
Vim常用命令备忘
assaper
vim 编辑器 linux
文章目录一、Vim支持模式二、Vim常用命令1.光标移动2.文本操作3.查找置换4.保存退出5.多文件编辑6.多窗口编辑7.多标签编辑8.目录操作9.运行命令10.可视化操作11.其他命令一、Vim支持模式普通模式:打开文件时的默认模式,在其他模式下按Esc键都可返回到该模式。插入模式:在普通模式下按i/a/o键进入该模式,进行文本编辑操作。命令行模式:在普通模式下输入:后会进入该模式,在该模式下
168. Excel表列名称(JS实现)
PAT-python-zjw
剑指offer
1题目给定一个正整数,返回它在Excel表中相对应的列名称。例如,1->A2->B3->C…26->Z27->AA28->AB…示例1:输入:1输出:“A”示例2:输入:28输出:“AB”示例3:输入:701输出:“ZY”链接:https://leetcode-cn.com/problems/excel-sheet-column-title2思路这道题看起来挺简单,但实际做的时候要好好考虑一下索引
地址解析协议(ARP):深入理解网络的“地址翻译官”
leo·li
路由交换技术笔记 ARP 网络通信 IP与MAC 局域网 ARP欺骗 网络排错 协议原理
地址解析协议(ARP,AddressResolutionProtocol)是网络通信中的“幕后翻译”,负责在局域网中将IP地址转换为MAC地址。作为TCP/IP协议栈的基础组件,ARP在数据帧传输中起着关键作用。本文将从零开始,详细剖析ARP的原理、过程及应用,通过丰富的示例带你彻底掌握这一“地址翻译官”的工作奥秘。一、ARP的基本概念:IP与MAC的“桥梁”在局域网中,设备通信靠的是二层地址(M
IPsec+预共享密钥的IKE野蛮模式
leo·li
IPSec VPN H3C 路由交换 网络 路由器 网络协议
目标配置IPsec+预共享密钥的IKE野蛮模式步骤一、配置各接口IP地址步骤二、配置默认路由[RTB]iproute-static0.0.0.002.2.2.2步骤三:配置公网连接在SWA上配置DHCPServer。设置RTA从SWA动态获得IP地址和默认路由。[SWA]dhcpenable[SWA]dhcpserverip-pool1[SWA-dhcp-pool-1]network1.1.1.0
bootstrap row 之间的竖直方向的距离要怎么调整?
yzy85
问:我有两个rowa和rowb,我发现这两个row之间的竖直方向的距离靠的有点近,我想调整a和b之间的距离,row之前的水平距离可以通过col-md-offset*调整,那竖直方向上的距离要怎么调整,谢谢!答:1、目前我是通过直接加一个空的h1标签,来拉开距离,感觉有点不太rails.大家有更合适的方法吗?谢谢!2、可以自己在css中给top-offset的class写个margin,然后把这个c
vue让table表格滚动的功能代码
yzy85
vue.js javascript 前端
方法1:vue中在固定高度的容器中,表格内容需要滚动显示,代码如下:/***自动滚动*@param{divData}dom*@param{time}多久滚动一次默认50ms*/exportfunctioninfinitScroll(divData,time=50){divData.onmouseover=function(){clearInterval(t);//鼠标移入,停止滚动};divDat
Android wpa_supplicant源码分析--conf配置文件
水木无痕
http://blog.csdn.net/cuijiyue/article/details/514288351配置文件conf文件作为wpa_supplicant的配置文件,一般叫做wpa_supplicant.conf。其中存储着wpa_supplicant的运行参数和以保存的网络列表。conf文件的路径,通过启动wpa_supplicant时的–c参数传入,初始化过程中赋值到wpa_s->co
svn 通过127.0.01能访问 但通过公网IP不能访问,这是什么原因?
行思理
运维 Linux svn linux 防火墙
连接失败的提示如下1、SVN的启动方法方法一:svnserve-d-r/mnt/svn方法二:svnserve-d--listen-port3690-r/mnt/svn方法三:svnserve-d-r/mnt/svn--listen-host0.0.0.02、首先检查svn服务器是否启动方法一:netstat-tunlp|grepsvn演示如下如上状态,说明已启动方法二:svnserve--ver
SVN学习笔记
颜洛滨
开发工具 SVN 开发工具 版本管理
SVN学习笔记SVN背景知识SVN,全称Subversion,是一个开放源码的集中式版本控制系统,这里需要注意的一个点就是集中式,所谓的集中式,就是说,SVN管理的所有仓库都位于一个集中的服务器上,如下图所示SVN官网:SVN官网SVN安装:SVN支持多个平台,包括Windows,Mac,Linux等,官网上提供了详细的安装指南,这里我使用的是Centos6,对应的安装步骤如下首先使用svn--v
MySQL存储结构
胖虎是只mao
MySQL 数据库 mysql 哈希表 数组 二叉树
背景:为什么数据库存储使用b+树而不是二叉树,因为二叉树树高过高,每次查询都需要访问过多节点,即访问数据块过多,而从磁盘随机读取数据块过于耗时。1.表存储结构单位:表>段>区>页>行在数据库中,不论读一行,还是读多行,都是将这些行所在的页进行加载。也就是说存储空间的基本单位是页。一个页就是一棵树B+树的节点,数据库I/O操作的最小单位是页,与数据库相关的内容都会存储在页的结构里。2.B+树索引结构
简单分享下Python数据可视化
小软件大世界
信息可视化 python 开发语言
在数据科学的广阔天地里,数据可视化是不可或缺的一环,它让复杂的数据变得易于理解。对于Python初学者而言,掌握Matplotlib和Seaborn这两个强大的库,无疑能让你的分析报告更加生动有趣。本文专为渴望提升数据可视化技能的你设计,通过15个实用技巧,带你从基础走向高级,探索数据背后的精彩故事。1.基础条形图-简单入手Matplotlib示例:import matplotlib.pyplot
168. Excel表列名称 题解
睡的饱
c++ leetcode 开发语言
168.Excel表列名称题解给你一个整数columnNumber,返回它在Excel表中相对应的列名称。例如:A->1B->2C->3...Z->26AA->27AB->28...示例1:输入:columnNumber=1输出:"A"示例2:输入:columnNumber=28输出:"AB"示例3:输入:columnNumber=701输出:"ZY"示例4:输入:columnNumber=214
网络安全主动防御技术与应用
坚持可信
信息安全 web安全 php 安全
入侵阻断技术(IntrusionPreventionTechnologies)是指通过检测并阻止网络和系统中的恶意活动,防止攻击者利用系统漏洞或其他手段进行破坏或未经授权访问的技术和方法。这些技术通常结合入侵检测(IntrusionDetection)功能,通过实时监控和响应机制,有效防御各种类型的网络攻击。以下是入侵阻断技术的详细介绍及其应用。一、入侵阻断技术入侵检测和防御系统(IDS/IPS)
168. Excel表列名称——【Leetcode每日一题】
零點零壹
LeetCode excel leetcode 算法
168.Excel表列名称给你一个整数columnNumber,返回它在Excel表中相对应的列名称。例如:A->1B->2C->3…Z->26AA->27AB->28…示例1:输入:columnNumber=1输出:“A”示例2:输入:columnNumber=28输出:“AB”示例3:输入:columnNumber=701输出:“ZY”示例4:输入:columnNumber=214748364
matlab中logm函数的应用,matlab 各种 对数函数 用法以及实例是什么
刘惠昌
在MATLAB中输入对数函数主要分为以下两种类型:一、直接型以e、2或者是10为底的对数的话,直接输入:y=log(x),y=log2(x),y=log10(x)。例如,a1=log(2.7183);a2=log2(2);a3=log10(10),其结果如下图:二、转换性如果需要求的对数函在MATLAB运算当中,我们常常需要求对数,在编写M文件的过程中,我们也需要表示对数,下面我就通过一些示例介绍
轻量级python编辑器 内存_vscode-轻量级实用编辑器
weixin_39557402
轻量级python编辑器 内存
前言:经网友推荐,下载vscode,发现速度确实快,度娘看了下,是微软抽调的一小波人做的。这样就不担心windows平台插件支持了。js,python都支持高亮,本身自带插件也都实用。自带控制台,终端,emmet格式插件,图标插件,小地图插件。占用内存少,推荐!先整理部分觉得不错的插件,体验一段时间再修改一、常用插件1.vscode-icon让vscode的文件夹目录添加上对应的图标注:安装好如果
linux svn创建资源库,CentOS 7搭建svn服务
你的麦克疯
linux svn创建资源库
一、背景自己平时有记笔记的习惯,回到宿舍笔记就同步不了。打算入手下很火的笔记软件,用着觉得不顺手,目录一多查找不方便,没有英文首字母定位快,想想决定用svn同步,整理出来分享给大家。二、搭建svn服务1、安装subversionyum-yinstallsubversion2、创建版本库目录,为创建版本库提供存放位置mkdir-p/home/svn/svnrepos3、创建svn版本库,mynote
Java面试专业技能怎么写_Java面试——专业技能
靳天羽
Java面试专业技能怎么写
目录一、简单讲下Java的跨平台原理二、装箱与拆箱三、实现一个拷贝文件的工具类使用字节流还是字符流四、介绍下线程池五、JSP和Servlet有哪些相同点和不同点六、简单介绍一下关系数据库三范式七、Mysql数据库的默认的最大连接数八、说一下Mysql和Oracle的分页九、简单讲一下数据库的触发器的使用场景十、简单讲一下数据库的存储过程的使用场景十一、简单介绍一下Activiti十二、编写一个Se
基于统信UOS的Kivy移动应用打包
Botiway
移动APP python Kivy
将Kivy应用打包为移动应用(Android或iOS)是发布应用的关键步骤。Kivy提供了多种工具来简化打包过程,其中最常用的是Buildozer(用于Android)和Kivy-iOS(用于iOS)。以下是详细的打包指南。1.打包为Android应用使用Buildozer可以将Kivy应用打包为AndroidAPK文件。1.1安装Buildozer首先,确保已安装Buildozer:pip3in
linux | Vim 命令快捷操作
斐夷所非
Linux linux Vim
注:本文为过去的“vim使用笔记”。跳转命令跳转命令#:向前查找光标当前所在单词,并跳转到该单词的上一个出现位置。*:向后查找光标当前所在单词,并跳转到该单词的下一个出现位置。行内跳转0:跳转到当前行的行首。[Home]$:跳转到当前行的行尾。[End]^:跳转到当前行的第一个非空字符处。g_:跳转到行尾最后一个非空白字符。|n:跳转到当前行的第n列(例如:|10跳转到第10列)。文件内跳转gg:
高斯溅射融合之路(一)- webgl渲染3d gaussian splatting
山海鲸可视化
数字孪生 GIS系统 webgl 数字孪生 GIS 高斯泼溅 AI重构
大家好,我是山海鲸的技术负责人。之前已经写了一个GIS融合系列。其实CesiumJS的整合有相当的难度,同时也有很多方面的工作,很难在几篇文章内写完,整个山海鲸团队也是投入了接近两年的时间,才把周边整套工具链进行了完善,后续有新的内容也会持续分享,隔壁系列传送门:GIS融合之路一坑未平,一坑又起。去年年末,我们的AI合作伙伴突然给山海鲸技术团队丢来了一个新技术-3DGaussianSplattin
如何打造多种风格的三维地图?2月27日直播讲解演示
山海鲸可视化
数字孪生 GIS系统 数字孪生 数据可视化 可视化大屏 GIS 三维地图
欢迎扫码图片中的二维码订阅直播,也可以在Bilibili关注“山海鲸可视化”,我们将于同一时间进行直播。
171. Excel 表列序号
冱洇
力扣刷题记录 excel leetcode 算法
Excel表列序号题目描述尝试做法推荐做法题目描述给你一个字符串columnTitle,表示Excel表格中的列名称。返回该列名称对应的列序号。例如:A->1B->2C->3…Z->26AA->27AB->28…示例1:输入:columnTitle=“A”输出:1示例2:输入:columnTitle=“AB”输出:28示例3:输入:columnTitle=“ZY”输出:701提示:1=0;i--)
393. UTF-8 编码验证
冱洇
力扣刷题记录 leetcode 算法
UTF-8编码验证题目描述尝试做法推荐做法题目描述给定一个表示数据的整数数组data,返回它是否为有效的UTF-8编码。UTF-8中的一个字符可能的长度为1到4字节,遵循以下的规则:对于1字节的字符,字节的第一位设为0,后面7位为这个符号的unicode码。对于n字节的字符(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符
Robot Framework 搭建环境
挽风821
Robot Framework python 自动化 模块测试 单元测试
windows电脑版目录一、Poetry1.1下载poetry虚拟环境1.2pycharm配置poetry编辑注意:如果提示不能设置poetry,becausegbk二、下载RobotFramework下载内置库小demo:打开百度首页小tips:pycharm安装plugins查看运行case的报告一、Poetry1.1下载poetry虚拟环境pipinstallpoetry查看poetry安装
HTML+CSS面试试题(部分)
四分十七
前端学习html html css 大数据
目录1:主流浏览器的内核分别是什么?2:每个HTML文件开头都有,它的作用是什么3:div+css的布局较table布局有什么优点?4:img的alt属性与title属性有何异同5:strong标签与em标签的异同6:渐进增强和优雅降级之间的不同7:为什么利用多个域名来存储网站资源8:网页标准和标准制定机构的意义9:简述一下src与href的区别10:网页制作会用的图片格式有哪些11:微格式,前端
二层网络类型及协议
wuqing_5450
华为数通路由交换HCIP p2p 网络 网络协议
数据链路层网络类型(根据协议及规则划分):P2P:在一个网段内只能存在两个节点。点到点MA多路访问:在同一个网段内,节点的数量不限制正常需要存在二层地址,否则无法单播。多点接入。有以下两种BMA--广播型多路访问NBMA--非广播型多路访问网络类型基于数据链路层选用的技术进行区分:部分虚拟技术以太网BMA帧中继MGRE(虚拟)NBMAPPP/HDLCPPPOE、GRE(虚拟)点到点
sql统计相同项个数并按名次显示
朱辉辉33
java oracle
现在有如下这样一个表:
A表
ID Name time
------------------------------
0001 aaa 2006-11-18
0002 ccc 2006-11-18
0003 eee 2006-11-18
0004 aaa 2006-11-18
0005 eee 2006-11-18
0004 aaa 2006-11-18
0002 ccc 20
Android+Jquery Mobile学习系列-目录
白糖_
JQuery Mobile
最近在研究学习基于Android的移动应用开发,准备给家里人做一个应用程序用用。向公司手机移动团队咨询了下,觉得使用Android的WebView上手最快,因为WebView等于是一个内置浏览器,可以基于html页面开发,不用去学习Android自带的七七八八的控件。然后加上Jquery mobile的样式渲染和事件等,就能非常方便的做动态应用了。
从现在起,往后一段时间,我打算
如何给线程池命名
daysinsun
线程池
在系统运行后,在线程快照里总是看到线程池的名字为pool-xx,这样导致很不好定位,怎么给线程池一个有意义的名字呢。参照ThreadPoolExecutor类的ThreadFactory,自己实现ThreadFactory接口,重写newThread方法即可。参考代码如下:
public class Named
IE 中"HTML Parsing Error:Unable to modify the parent container element before the
周凡杨
html 解析 error readyState
错误: IE 中"HTML Parsing Error:Unable to modify the parent container element before the child element is closed"
现象: 同事之间几个IE 测试情况下,有的报这个错,有的不报。经查询资料后,可归纳以下原因。
java上传
g21121
java
我们在做web项目中通常会遇到上传文件的情况,用struts等框架的会直接用的自带的标签和组件,今天说的是利用servlet来完成上传。
我们这里利用到commons-fileupload组件,相关jar包可以取apache官网下载:http://commons.apache.org/
下面是servlet的代码:
//定义一个磁盘文件工厂
DiskFileItemFactory fact
SpringMVC配置学习
510888780
spring mvc
spring MVC配置详解
现在主流的Web MVC框架除了Struts这个主力 外,其次就是Spring MVC了,因此这也是作为一名程序员需要掌握的主流框架,框架选择多了,应对多变的需求和业务时,可实行的方案自然就多了。不过要想灵活运用Spring MVC来应对大多数的Web开发,就必须要掌握它的配置及原理。
一、Spring MVC环境搭建:(Spring 2.5.6 + Hi
spring mvc-jfreeChart 柱图(1)
布衣凌宇
jfreechart
第一步:下载jfreeChart包,注意是jfreeChart文件lib目录下的,jcommon-1.0.23.jar和jfreechart-1.0.19.jar两个包即可;
第二步:配置web.xml;
web.xml代码如下
<servlet>
<servlet-name>jfreechart</servlet-nam
我的spring学习笔记13-容器扩展点之PropertyPlaceholderConfigurer
aijuans
Spring3
PropertyPlaceholderConfigurer是个bean工厂后置处理器的实现,也就是BeanFactoryPostProcessor接口的一个实现。关于BeanFactoryPostProcessor和BeanPostProcessor类似。我会在其他地方介绍。PropertyPlaceholderConfigurer可以将上下文(配置文件)中的属性值放在另一个单独的标准java P
java 线程池使用 Runnable&Callable&Future
antlove
java thread Runnable callable future
1. 创建线程池
ExecutorService executorService = Executors.newCachedThreadPool();
2. 执行一次线程,调用Runnable接口实现
Future<?> future = executorService.submit(new DefaultRunnable());
System.out.prin
XML语法元素结构的总结
百合不是茶
xml 树结构
1.XML介绍1969年 gml (主要目的是要在不同的机器进行通信的数据规范)1985年 sgml standard generralized markup language1993年 html(www网)1998年 xml extensible markup language
改变eclipse编码格式
bijian1013
eclipse 编码格式
1.改变整个工作空间的编码格式
改变整个工作空间的编码格式,这样以后新建的文件也是新设置的编码格式。
Eclipse->window->preferences->General->workspace-
javascript中return的设计缺陷
bijian1013
JavaScript AngularJS
代码1:
<script>
var gisService = (function(window)
{
return
{
name:function ()
{
alert(1);
}
};
})(this);
gisService.name();
&l
【持久化框架MyBatis3八】Spring集成MyBatis3
bit1129
Mybatis3
pom.xml配置
Maven的pom中主要包括:
MyBatis
MyBatis-Spring
Spring
MySQL-Connector-Java
Druid
applicationContext.xml配置
<?xml version="1.0" encoding="UTF-8"?>
&
java web项目启动时自动加载自定义properties文件
bitray
java Web 监听器 相对路径
创建一个类
public class ContextInitListener implements ServletContextListener
使得该类成为一个监听器。用于监听整个容器生命周期的,主要是初始化和销毁的。
类创建后要在web.xml配置文件中增加一个简单的监听器配置,即刚才我们定义的类。
<listener>
<des
用nginx区分文件大小做出不同响应
ronin47
昨晚和前21v的同事聊天,说到我离职后一些技术上的更新。其中有个给某大客户(游戏下载类)的特殊需求设计,因为文件大小差距很大——估计是大版本和补丁的区别——又走的是同一个域名,而squid在响应比较大的文件时,尤其是初次下载的时候,性能比较差,所以拆成两组服务器,squid服务于较小的文件,通过pull方式从peer层获取,nginx服务于较大的文件,通过push方式由peer层分发同步。外部发布
java-67-扑克牌的顺子.从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的.2-10为数字本身,A为1,J为11,Q为12,K为13,而大
bylijinnan
java
package com.ljn.base;
import java.util.Arrays;
import java.util.Random;
public class ContinuousPoker {
/**
* Q67 扑克牌的顺子 从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。
* 2-10为数字本身,A为1,J为1
翟鸿燊老师语录
ccii
翟鸿燊
一、国学应用智慧TAT之亮剑精神A
1. 角色就是人格
就像你一回家的时候,你一进屋里面,你已经是儿子,是姑娘啦,给老爸老妈倒怀水吧,你还觉得你是老总呢?还拿派呢?就像今天一样,你们往这儿一坐,你们之间是什么,同学,是朋友。
还有下属最忌讳的就是领导向他询问情况的时候,什么我不知道,我不清楚,该你知道的你凭什么不知道
[光速与宇宙]进行光速飞行的一些问题
comsci
问题
在人类整体进入宇宙时代,即将开展深空宇宙探索之前,我有几个猜想想告诉大家
仅仅是猜想。。。未经官方证实
1:要在宇宙中进行光速飞行,必须首先获得宇宙中的航行通行证,而这个航行通行证并不是我们平常认为的那种带钢印的证书,是什么呢? 下面我来告诉
oracle undo解析
cwqcwqmax9
oracle
oracle undo解析2012-09-24 09:02:01 我来说两句 作者:虫师收藏 我要投稿
Undo是干嘛用的? &nb
java中各种集合的详细介绍
dashuaifu
java 集合
一,java中各种集合的关系图 Collection 接口的接口 对象的集合 ├ List 子接口 &n
卸载windows服务的方法
dcj3sjt126com
windows service
卸载Windows服务的方法
在Windows中,有一类程序称为服务,在操作系统内核加载完成后就开始加载。这里程序往往运行在操作系统的底层,因此资源占用比较大、执行效率比较高,比较有代表性的就是杀毒软件。但是一旦因为特殊原因不能正确卸载这些程序了,其加载在Windows内的服务就不容易删除了。即便是删除注册表中的相 应项目,虽然不启动了,但是系统中仍然存在此项服务,只是没有加载而已。如果安装其他
Warning: The Copy Bundle Resources build phase contains this target's Info.plist
dcj3sjt126com
ios xcode
http://developer.apple.com/iphone/library/qa/qa2009/qa1649.html
Excerpt:
You are getting this warning because you probably added your Info.plist file to your Copy Bundle
2014之C++学习笔记(一)
Etwo
C++ Etwo Etwo iterator 迭代器
已经有很长一段时间没有写博客了,可能大家已经淡忘了Etwo这个人的存在,这一年多以来,本人从事了AS的相关开发工作,但最近一段时间,AS在天朝的没落,相信有很多码农也都清楚,现在的页游基本上达到饱和,手机上的游戏基本被unity3D与cocos占据,AS基本没有容身之处。so。。。最近我并不打算直接转型
js跨越获取数据问题记录
haifengwuch
jsonp json Ajax
js的跨越问题,普通的ajax无法获取服务器返回的值。
第一种解决方案,通过getson,后台配合方式,实现。
Java后台代码:
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String ca
蓝色jQuery导航条
ini
JavaScript html jquery Web html5
效果体验:http://keleyi.com/keleyi/phtml/jqtexiao/39.htmHTML文件代码:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>jQuery鼠标悬停上下滑动导航条 - 柯乐义<
linux部署jdk,tomcat,mysql
kerryg
jdk tomcat linux mysql
1、安装java环境jdk:
一般系统都会默认自带的JDK,但是不太好用,都会卸载了,然后重新安装。
1.1)、卸载:
(rpm -qa :查询已经安装哪些软件包;
rmp -q 软件包:查询指定包是否已
DOMContentLoaded VS onload VS onreadystatechange
mutongwu
jquery js
1. DOMContentLoaded 在页面html、script、style加载完毕即可触发,无需等待所有资源(image/iframe)加载完毕。(IE9+)
2. onload是最早支持的事件,要求所有资源加载完毕触发。
3. onreadystatechange 开始在IE引入,后来其它浏览器也有一定的实现。涉及以下 document , applet, embed, fra
sql批量插入数据
qifeifei
批量插入
hi,
自己在做工程的时候,遇到批量插入数据的数据修复场景。我的思路是在插入前准备一个临时表,临时表的整理就看当时的选择条件了,临时表就是要插入的数据集,最后再批量插入到数据库中。
WITH tempT AS (
SELECT
item_id AS combo_id,
item_id,
now() AS create_date
FROM
a
log4j打印日志文件 如何实现相对路径到 项目工程下
thinkfreer
Web log4j 应用服务器 日志
最近为了实现统计一个网站的访问量,记录用户的登录信息,以方便站长实时了解自己网站的访问情况,选择了Apache 的log4j,但是在选择相对路径那块 卡主了,X度了好多方法(其实大多都是一样的内用,还一个字都不差的),都没有能解决问题,无奈搞了2天终于解决了,与大家分享一下
需求:
用户登录该网站时,把用户的登录名,ip,时间。统计到一个txt文档里,以方便其他系统调用此txt。项目名
linux下mysql-5.6.23.tar.gz安装与配置
笑我痴狂
mysql linux unix
1.卸载系统默认的mysql
[root@localhost ~]# rpm -qa | grep mysql
mysql-libs-5.1.66-2.el6_3.x86_64
mysql-devel-5.1.66-2.el6_3.x86_64
mysql-5.1.66-2.el6_3.x86_64
[root@localhost ~]# rpm -e mysql-libs-5.1