目录
前言
本文使用的调试代码
IOC 容器管理 AOP 实例
ProxyFactory 详解
基于注解的 Spring AOP 源码分析
闲聊 InstantiationAwareBeanPostProcessor
小结
之前写过 IOC 的源码分析,那篇文章真的有点长,看完需要点耐心。很多读者希望能写一写 Spring AOP 的源码分析文章,这样读者看完 IOC + AOP 也就对 Spring 会有比较深的理解了。今天终于成文了,可能很多读者早就不再等待了,不过主要为了后来者吧。
本文不会像 IOC 源码分析那篇文章一样,很具体地分析每一行 Spring AOP 的源码,目标读者是已经知道 Spring IOC 源码是怎么回事的读者,因为 Spring AOP 终归是依赖于 IOC 容器来管理的。
阅读建议:1、先搞懂 IOC 容器的源码,AOP 依赖于 IOC 容器来管理。2、仔细看完 Spring AOP 使用介绍 这篇文章,先搞懂各种使用方式,你才能"猜到"应该怎么实现。
Spring AOP 的源码并不简单,因为它多,所以阅读源码最好就是找到一个分支,追踪下去。本文定位为走马观花,看个大概,不具体到每一个细节。
前言
这一节,我们先来"猜猜" Spring 是怎么实现 AOP 的。
在 Spring 的容器中,我们面向的对象是一个个的 bean 实例,bean 是什么?我们可以简单理解为是 BeanDefinition 的实例,Spring 会根据 BeanDefinition 中的信息为我们生产合适的 bean 实例出来。
当我们需要使用 bean 的时候,通过 IOC 容器的 getBean(…) 方法从容器中获取 bean 实例,只不过大部分的场景下,我们都用了依赖注入,所以很少手动调用 getBean(...) 方法。
Spring AOP 的原理很简单,就是动态代理 ,它和 AspectJ 不一样,AspectJ 是直接修改掉你的字节码。
代理模式很简单,接口 + 真实实现类 + 代理类,其中 真实实现类 和 代理类 都要实现接口,实例化的时候要使用代理类。所以,Spring AOP 需要做的是生成这么一个代理类,然后替换掉 真实实现类来对外提供服务。
替换的过程怎么理解呢?在 Spring IOC 容器中非常容易实现,就是在 getBean(…) 的时候返回的实际上是代理类的实例,而这个代理类我们自己没写代码,它是 Spring 采用 JDK Proxy 或 CGLIB 动态生成的。
getBean(…) 方法用于查找或实例化容器中的 bean,这也是为什么 Spring AOP 只能作用于 Spring 容器中的 bean 的原因,对于不是使用 IOC 容器管理的对象,Spring AOP 是无能为力的。
本文使用的调试代码
阅读源码很好用的一个方法就是跑代码来调试,因为自己一行一行地看的话,比较枯燥,而且难免会漏掉一些东西。
下面,我们先准备一些简单的调试用的代码。
首先先定义两个 Service 接口:
// OrderService.java
public interface OrderService {
Order createOrder(String username, String product);
Order queryOrder(String username);
}
// UserService.java
public interface UserService {
User createUser(String firstName, String lastName, int age);
User queryUser();
}
然后,分别来一个接口实现类:
// OrderServiceImpl.java
public class OrderServiceImpl implements OrderService {
@Override
public Order createOrder(String username, String product) {
Order order = new Order();
order.setUsername(username);
order.setProduct(product);
return order;
}
@Override
public Order queryOrder(String username) {
Order order = new Order();
order.setUsername("test");
order.setProduct("test");
return order;
}
}
// UserServiceImpl.java
public class UserServiceImpl implements UserService {
@Override
public User createUser(String firstName, String lastName, int age) {
User user = new User();
user.setFirstName(firstName);
user.setLastName(lastName);
user.setAge(age);
return user;
}
@Override
public User queryUser() {
User user = new User();
user.setFirstName("test");
user.setLastName("test");
user.setAge(20);
return user;
}
}
写两个 Advice:
public class LogArgsAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("准备执行方法: " + method.getName() + ", 参数列表:" + Arrays.toString(args));
}
}
public class LogResultAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target)
throws Throwable {
System.out.println(method.getName() + "方法返回:" + returnValue);
}
}
配置一下:
我们这边使用了前面文章介绍的配置 Advisor 的方式,我们回顾一下。
每个 advisor 内部持有 advice 实例,advisor 负责匹配,内部的 advice 负责实现拦截处理。配置了各个 advisor 后,配置 DefaultAdvisorAutoProxyCreator 使得所有的 advisor 配置自动生效。
启动:
public class SpringAopSourceApplication {
public static void main(String[] args) {
// 启动 Spring 的 IOC 容器
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:DefaultAdvisorAutoProxy.xml");
UserService userService = context.getBean(UserService.class);
OrderService orderService = context.getBean(OrderService.class);
userService.createUser("Tom", "Cruise", 55);
userService.queryUser();
orderService.createOrder("Leo", "随便买点什么");
orderService.queryOrder("Leo");
}
}
输出:
准备执行方法: createUser, 参数列表:[Tom, Cruise, 55]
queryUser方法返回:User{firstName='test', lastName='test', age=20, address='null'}
准备执行方法: createOrder, 参数列表:[Leo, 随便买点什么]
queryOrder方法返回:Order{username='test', product='test'}
从输出结果,我们可以看到:
LogArgsAdvice 作用于 UserService#createUser(…) 和 OrderService#createOrder(…) 两个方法;
LogResultAdvice 作用于 UserService#queryUser() 和 OrderService#queryOrder(…) 两个方法;
下面的代码分析中,我们将基于这个简单的例子来介绍。
IOC 容器管理 AOP 实例
本节介绍 Spring AOP 是怎么作用于 IOC 容器中的 bean 的。
Spring AOP 的使用介绍 那篇文章已经介绍过 DefaultAdvisorAutoProxyCreator 类了,它能实现自动将所有的 advisor 生效。
我们来追踪下 DefaultAdvisorAutoProxyCreator 类,看看它是怎么一步步实现的动态代理。然后在这个基础上,我们再简单追踪下 @AspectJ 配置方式下的源码实现。
首先,先看下 DefaultAdvisorAutoProxyCreator 的继承结构:
我们可以发现,DefaultAdvisorAutoProxyCreator 最后居然是一个 BeanPostProcessor ,在 Spring IOC 源码分析的时候说过,BeanPostProcessor 的两个方法,分别在 init-method 的前后得到执行。
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
这里再贴一下 IOC 的源码,我们回顾一下:
// AbstractAutowireCapableBeanFactory
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 1. 创建实例
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
...
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 2. 装载属性
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
// 3. 初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
...
}
在上面第 3 步 initializeBean(...) 方法中会调用 BeanPostProcessor 中的方法,如下:
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
...
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
// 1. 执行每一个 BeanPostProcessor 的 postProcessBeforeInitialization 方法
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
// 调用 bean 配置中的 init-method="xxx"
invokeInitMethods(beanName, wrappedBean, mbd);
}
...
if (mbd == null || !mbd.isSynthetic()) {
// 我们关注的重点是这里!!!
// 2. 执行每一个 BeanPostProcessor 的 postProcessAfterInitialization 方法
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
也就是说,Spring AOP 会在 IOC 容器创建 bean 实例的最后对 bean 进行处理。其实就是在这一步进行代理增强。
我们回过头来,DefaultAdvisorAutoProxyCreator 的继承结构中,postProcessAfterInitialization() 方法在其父类 AbstractAutoProxyCreator 这一层被覆写了:
// AbstractAutoProxyCreator
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
继续往里看 wrapIfNecessary(...) 方法,这个方法将返回代理类(如果需要的话):
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// 返回匹配当前 bean 的所有的 advisor、advice、interceptor
// 对于本文的例子,"userServiceImpl" 和 "OrderServiceImpl" 这两个 bean 创建过程中,
// 到这边的时候都会返回两个 advisor
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 创建代理...创建代理...创建代理...
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
这里有两个点提一下:
getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null),这个方法将得到所有的可用于拦截当前 bean 的 advisor、advice、interceptor。
另一个就是 TargetSource 这个概念,它用于封装真实实现类的信息,上面用了 SingletonTargetSource 这个实现类,其实我们这里也不太需要关心这个,知道有这么回事就可以了。
我们继续往下看 createProxy(…) 方法:
// 注意看这个方法的几个参数,
// 第三个参数携带了所有的 advisors
// 第四个参数 targetSource 携带了真实实现的信息
protected Object createProxy(
Class beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
// 创建 ProxyFactory 实例
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
// 在 schema-based 的配置方式中,我们介绍过,如果希望使用 CGLIB 来代理接口,可以配置
// proxy-target-class="true",这样不管有没有接口,都使用 CGLIB 来生成代理:
// ......
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
// 点进去稍微看一下代码就知道了,主要就两句:
// 1. 有接口的,调用一次或多次:proxyFactory.addInterface(ifc);
// 2. 没有接口的,调用:proxyFactory.setProxyTargetClass(true);
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
// 这个方法会返回匹配了当前 bean 的 advisors 数组
// 对于本文的例子,"userServiceImpl" 和 "OrderServiceImpl" 到这边的时候都会返回两个 advisor
// 注意:如果 specificInterceptors 中有 advice 和 interceptor,它们也会被包装成 advisor,进去看下源码就清楚了
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
for (Advisor advisor : advisors) {
proxyFactory.addAdvisor(advisor);
}
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
return proxyFactory.getProxy(getProxyClassLoader());
}
我们看到,这个方法主要是在内部创建了一个 ProxyFactory 的实例,然后 set 了一大堆内容,剩下的工作就都是这个 ProxyFactory 实例的了,通过这个实例来创建代理: getProxy(classLoader)
。
ProxyFactory 详解
根据上面的源码,我们走到了 ProxyFactory 这个类了,我们到这个类来一看究竟。
顺着上面的路子,我们首先到 ProxyFactory#getProxy(classLoader) 方法:
public Object getProxy(ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}
该方法首先通过 createAopProxy() 创建一个 AopProxy 的实例:
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
return getAopProxyFactory().createAopProxy(this);
}
创建 AopProxy 之前,我们需要一个 AopProxyFactory 实例,然后看 ProxyCreatorSupport 的构造方法:
public ProxyCreatorSupport() {
this.aopProxyFactory = new DefaultAopProxyFactory();
}
这样就将我们导到 DefaultAopProxyFactory
这个类了,我们看它的 createAopProxy(…) 方法:
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// (我也没用过这个optimize,默认false) || (proxy-target-class=true) || (没有接口)
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.");
}
// 如果要代理的类本身就是接口,也会用 JDK 动态代理
// 我也没用过这个。。。
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])));
}
}
到这里,我们知道 createAopProxy 方法有可能返回 JdkDynamicAopProxy 实例,也有可能返回 ObjenesisCglibAopProxy 实例,这里总结一下:
如果被代理的目标类实现了一个或多个自定义的接口,那么就会使用 JDK 动态代理,如果没有实现任何接口,会使用 CGLIB 实现代理,如果设置了 proxy-target-class="true",那么都会使用 CGLIB。
JDK 动态代理基于接口,所以只有接口中的方法会被增强,而 CGLIB 基于类继承,需要注意就是如果方法使用了 final 修饰,或者是 private 方法,是不能被增强的。
有了 AopProxy 实例以后,我们就回到这个方法了:
public Object getProxy(ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}
我们分别来看下两个 AopProxy 实现类的 getProxy(classLoader) 实现。
JdkDynamicAopProxy 类的源码比较简单,总共两百多行,
@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, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
java.lang.reflect.Proxy.newProxyInstance(…) 方法需要三个参数,第一个是 ClassLoader,第二个参数代表需要实现哪些接口,第三个参数最重要,是 InvocationHandler 实例,我们看到这里传了 this,因为 JdkDynamicAopProxy 本身实现了 InvocationHandler 接口。
InvocationHandler 只有一个方法,当生成的代理类对外提供服务的时候,都会导到这个方法中:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
下面来看看 JdkDynamicAopProxy 对其的实现:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Class targetClass = null;
Object target = null;
try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
// 代理的 equals 方法
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
// 代理的 hashCode 方法
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;
// 如果设置了 exposeProxy,那么将 proxy 放到 ThreadLocal 中
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// May be null. Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
}
// Get the interception chain for this method.
// 创建一个 chain,包含所有要执行的 advice
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.
// chain 是空的,说明不需要被增强,这种情况很简单
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// We need to create a method invocation...
// 执行方法,得到返回值
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);
}
}
}
上面就三言两语说了一下,感兴趣的读者自己去深入探索下,不是很难。简单地说,就是在执行每个方法的时候,判断下该方法是否需要被一次或多次增强(执行一个或多个 advice)。
说完了 JDK 动态代理 JdkDynamicAopProxy#getProxy(classLoader),我们再来瞄一眼 CGLIB 的代理实现 ObjenesisCglibAopProxy#getProxy(classLoader)。
ObjenesisCglibAopProxy 继承了 CglibAopProxy,而 CglibAopProxy 继承了 AopProxy。
ObjenesisCglibAopProxy 使用了 Objenesis 这个库,和 cglib 一样,我们不需要在 maven 中进行依赖,因为 spring-core.jar 直接把它的源代码也搞过来了。
通过 CGLIB 生成代理的代码量有点大,我们就不进行深入分析了,我们看下大体的骨架。它的 getProxy(classLoader) 方法在父类 CglibAopProxy 类中:
// CglibAopProxy#getProxy(classLoader)
@Override
public Object getProxy(ClassLoader 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 ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));
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.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);
// Generate the proxy class and create a proxy instance.
return createProxyClassAndInstance(enhancer, callbacks);
}
catch (CodeGenerationException ex) {
...
}
catch (IllegalArgumentException ex) {
...
}
catch (Throwable ex) {
...
}
}
CGLIB 生成代理的核心类是 Enhancer 类,这里就不展开说了。
基于注解的 Spring AOP 源码分析
上面我们走马观花地介绍了使用 DefaultAdvisorAutoProxyCreator 来实现 Spring AOP 的源码,这里,我们也同样走马观花地来看下 @AspectJ 的实现原理。
我们之前说过,开启 @AspectJ 的两种方式,一个是
,一个是 @EnableAspectJAutoProxy
,它们的原理是一样的,都是通过注册一个 bean 来实现的。
解析
需要用到 AopNamespaceHandler:
然后到类 AspectJAutoProxyBeanDefinitionParser:
class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser {
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
extendBeanDefinition(element, parserContext);
return null;
}
...
}
进去 registerAspectJAnnotationAutoProxyCreatorIfNecessary(...) 方法:
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) {
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement));
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
registerComponentIfNecessary(beanDefinition, parserContext);
}
再进去 AopConfigUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary(...):
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry,
@Nullable Object source) {
return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}
最终我们看到,Spring 注册了一个 AnnotationAwareAspectJAutoProxyCreator 的 bean,beanName 为:"org.springframework.aop.config.internalAutoProxyCreator"。
我们看下 AnnotationAwareAspectJAutoProxyCreator 的继承结构:
和前面介绍的 DefaultAdvisorAutoProxyCreator 一样,它也是一个 BeanPostProcessor,剩下的我们就不说了,它和它的父类 AspectJAwareAdvisorAutoProxyCreator 都不复杂。
闲聊 InstantiationAwareBeanPostProcessor
为什么要说这个呢?因为我发现,很多人都以为 Spring AOP 是通过这个接口来作用于 bean 生成代理的。
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException;
boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;
PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException;
}
它和 BeanPostProcessor 的方法非常相似,而且它还继承了 BeanPostProcessor。
不仔细看还真的不好区分,下面是 BeanPostProcessor 中的两个方法:
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
发现没有,InstantiationAwareBeanPostProcessor 是 Instantiation
,BeanPostProcessor 是 Initialization
,它代表的是 bean 在实例化完成并且属性注入完成,在执行 init-method 的前后进行作用的。
而 InstantiationAwareBeanPostProcessor 的执行时机要前面一些,大家需要翻下 IOC 的源码:
// AbstractAutowireCapableBeanFactory 447行
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
...
try {
// 让 InstantiationAwareBeanPostProcessor 在这一步有机会返回代理
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
// BeanPostProcessor 是在这里面实例化后才能得到执行
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
...
return beanInstance;
}
点进去看 resolveBeforeInstantiation(beanName, mbdToUse) 方法,然后就会导到 InstantiationAwareBeanPostProcessor 的 postProcessBeforeInstantiation 方法,对于我们分析的 AOP 来说,该方法的实现在 AbstractAutoProxyCreator 类中:
@Override
public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException {
...
if (beanName != null) {
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
this.targetSourcedBeans.add(beanName);
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
}
return null;
}
我们可以看到,这里也有创建代理的逻辑,以至于很多人会搞错。确实,这里是有可能创建代理的,但前提是对于相应的 bean 我们有自定义的 TargetSource 实现,进到 getCustomTargetSource(...) 方法就清楚了,我们需要配置一个 customTargetSourceCreators,它是一个 TargetSourceCreator 数组。
这里就不再展开说 TargetSource 了,请参考 Spring Reference 中的 Using TargetSources。
小结
本文真的是走马观花,和我之前写的文章有很大的不同,希望读者不会嫌弃。
不过如果读者有看过之前的 Spring IOC 源码分析和 Spring AOP 使用介绍 这两篇文章的话,通过看本文应该能对 Spring AOP 的源码实现有比较好的理解了。
转载自: https://javadoop.com/post/spring-aop-source
你可能感兴趣的:(Spring,源码分析)
springboot中Webclient对象怎么禁止缓存、保证每次数据都属来源后端
阿里嘎多学长
spring boot 缓存 数据库
在SpringBoot中,使用WebClient对象进行HTTP请求时,可以通过配置请求头来禁止缓存,确保每次请求都能从后端获取最新的数据。以下是一些常用的方法来实现这一目的:设置Cache-Control请求头:在发起请求时,可以设置Cache-Control请求头为no-cache,这会告诉服务器和任何中间缓存,不应使用缓存的响应来满足请求。WebClientwebClient=WebClie
SpringBoot集成Sentry日志收集-2 (Sentry修改语言为中文)
欢乐少年1904
spring boot sentry 后端
Sentry修改成中文英文状态是不太友好,对于我这个英文不好的人来说,我用的一切开发软件,全是中文
面试基础---MySQL 分布式 ID 方案深度解析
WeiLai1112
mysql vue.js
MySQL分布式ID方案深度解析:UUID、自增ID与雪花算法引言在分布式系统中,生成全局唯一的ID是一个常见的需求。MySQL作为最流行的关系型数据库之一,如何在高并发、分布式环境下生成唯一ID是一个重要的技术挑战。本文将深入探讨MySQL分布式ID的生成方案,包括UUID、自增ID和雪花算法,结合实际项目案例和源码分析,帮助读者深入理解其实现原理。1.分布式ID的需求与挑战在分布式系统中,生成
基于springboot+ollama实现大模型接入项目的小白笔记
河禾合和
spring boot 笔记 后端 llama
org.springframework.ai无法被maven加载的问题问题原因:阿里镜像没有这个库共用库中只保存了0.8.1的测试版本解决方法:参考了这个视频https://www.youtube.com/watch?v=dffEF9ORVUg学习视频中方法使用springinitializr这个工具进行项目的生成具体参数如下图在右边依赖中搜索ollama加载ai依赖,以及springweb依赖,
Spring Boot 缓存最佳实践:从基础到生产的完整指南
天才选手Yoke
java springboot spring boot 缓存 后端 redis
SpringBoot缓存最佳实践:从基础到生产的完整指南引言在现代分布式系统中,缓存是提升系统性能的银弹。SpringBoot通过spring-boot-starter-cache模块提供了开箱即用的缓存抽象,但如何根据业务需求实现灵活、可靠的缓存方案?本文将带您从零开始,逐步构建符合生产要求的缓存系统。一、基础篇:5分钟快速接入1.1最小化配置pom.xml依赖org.springframewo
Spring Boot 3.0深度实战:从核心特性到生产级调优
Code_Cracke
Java spring boot 后端 java
一、SpringBoot3.0核心特性解读1.1JDK17LTS支持(实测性能提升)记录类(Record)与SpringDataJPA完美适配模式匹配简化类型判断密封类(SealedClass)增强DTO安全性//使用Record优化DTOpublicrecordUserDTO(@NotBlankStringusername,@EmailStringemail){}//密封接口定义响应类型publ
MyBatis-Plus开发流程:Spring Boot + MyBatis-Plus 实现对 book_tab 表的增删改查及Redis缓存
Darling &you&
mybatis 缓存 spring boot
前言MyBatis-Plus是一个MyBatis的增强工具,旨在简化开发、减少工作量。本文将介绍如何使用SpringBoot集成MyBatis-Plus来操作数据库,并结合Redis实现数据的缓存功能。1项目搭建1.1创建SpringBoot项目可以通过SpringInitializr快速创建一个新的SpringBoot项目,选择必要的依赖如SpringWeb,MyBatisPlus,MySQLD
Vue前后端参数传递(重要)
亭台烟雨中
vue前端 springboot json vue java
一般来说,大部分情况下springboot中controller只要参数名可以一一对应,就可以很方便的传递参数,但是如果前台给后台的参数非常复杂,涉及到多种类型,那么controller就无法智能的自动注入了letProdOBJ={"productCode":"155","productName":"产品-徐","productListCode":"list-xu","productListNam
SHEIN面试——Java高级开发
有鹿如溪
面试 Java 面试 职场和发展
2022年7月20希音二面高级开发工程师(供应链)面试流程1自我介绍2问问题开始多线程:1线程间的通信方式2什么是pua定义3什么是内卷的定义4spring和mybatis怎么操作数据库的5平时工作量怎么定义的6工作技巧7如何提升工作效率8对加班有啥看法9为啥离职耗时:30分钟
【项目实战】Spring AI集成DeepSeek实战指南(硅基流动平台版)
zxg45
AI大模型 spring 人工智能 java deepseek 硅基流动 AI大模型
SpringAI集成DeepSeek实战指南(硅基流动平台版)本文手把手教你通过SpringAI框架集成国产大模型DeepSeek,结合硅基流动平台实现智能对话功能。本方案支持普通对话和流式响应两种模式,完整代码已通过测试,可直接用于生产环境。一、环境准备开发工具JDK17+Maven3.9+SpringBoot3.2.x+(推荐3.3.0)硅基流动平台配置登录硅基流动官网,新用户赠送2000万t
【Spring】整合【SpringMVC】
好教员好
java spring
导入依赖 org.springframework spring-core ${spring.version} org.springframework spring-context ${spring.version} org.springframework spring-web ${spring.version} org.springframe
springboot整合rabbitMQ
twx95
java-rabbitmq spring boot rabbitmq
安装rabbitMQ虚拟机或者服务器上安装我这里使用的是vm虚拟机做演示第一步:安装docker参考linux安装docker-CSDN博客第二步:拉取rabbitMQ镜像3-management(镜像版本)dockerpullrabbitmq:3-management查看镜像是否拉取成功dockerimages第三步:运行rabbitMQdockerrun\-eRABBITMQ_DEFAULT_
rabbitmq 手动提交_第四章----SpringBoot+RabbitMQ发送确认和消费手动确认机制
weixin_39622178
rabbitmq 手动提交
1.配置RabbitMQ#发送确认spring.rabbitmq.publisher-confirms=true#发送回调spring.rabbitmq.publisher-returns=true#消费手动确认spring.rabbitmq.listener.simple.acknowledge-mode=manual2.生产者发送消息确认机制其实这个也不能叫确认机制,只是起到一个监听的作用,监
rabbitmq-amqp事务消息+消费失败重试机制+prefetch限流
執迷王二
rabbitmq 分布式
1.安装和配置org.springframework.bootspring-boot-starter-amqpcom.fasterxml.jackson.corejackson-databind1.2yml配置###生产端的配置spring:rabbitmq:host:localhostport:5672virtual-host:/#虚拟主机username:guestpassword:guest
Spring Boot 异步编程
珠峰日记
spring boot java 后端
文章目录一、异步方法的使用1.开启异步支持2.定义异步方法3.调用异步方法踩坑记录心得体会二、线程池配置1.自定义线程池2.使用自定义线程池踩坑记录心得体会三、异步任务的监控与管理1.日志记录2.异常处理3.线程池监控踩坑记录心得体会在现代应用程序开发中,异步编程是提升系统性能和响应能力的重要手段。SpringBoot提供了便捷的方式来实现异步编程,下面将详细介绍异步方法的使用、线程池配置以及异步
SpringBoot缓存实践
珠峰日记
spring boot 缓存 后端
文章目录一、引言二、SpringCache抽象(一)核心概念与原理(二)优势与局限性三、集成常用缓存(一)集成Redis缓存1.集成步骤2.踩坑记录与心得体会(二)集成Ehcache缓存1.集成步骤2.踩坑记录与心得体会四、缓存注解的使用(一)`@Cacheable`(二)`@CachePut`(三)`@CacheEvict`(四)`@Caching`(五)踩坑记录与心得体会五、总结一、引言在当今
Springboot应用json数据传给前端首字母大写变小写的问题
珠峰日记
css3 css spring jquery html
对接.net数据,属性首字母大写传给前端后自动转成了小写。解决思路网上有三种:1)如果没有使用fastjson(阿里巴巴出品),使用@JsonProperty("XXXX")的注解方式可以解决问题,XXXX就是转换成Json的属性名,但注意,只有放在getter上才是有效的,直接在属性上加没效果.(这说明spring默认的jackson类库处理是基于访问器。2)如果使用了fastjson,就用@J
Spring测试框架中的@ContextConfiguration继承机制
t0_54program
spring java 后端 个人开发
在Spring框架中,@ContextConfiguration注解是一个非常重要的工具,它用于指定测试类的上下文配置来源。然而,很多人可能并不清楚,这个注解还提供了inheritLocations和inheritInitializers属性,用于控制是否继承父类的配置位置和上下文初始化器。本文将通过具体的代码示例,深入探讨这两个属性的作用。默认行为:继承配置位置和初始化器在默认情况下,inher
Spring Boot——消息队列集成RabbitMQ详细步骤大全
程序员阿皓
SpringBoot java-rabbitmq spring boot rabbitmq
SpringBoot提供了与多种消息队列系统集成的支持,其中最常见的是集成RabbitMQ或Kafka。以RabbitMQ为例集成的详细步骤如下:1.添加RabbitMQ的依赖首先,在SpringBoot项目的pom.xml文件中添加RabbitMQ的依赖:org.springframework.bootspring-boot-starter-amqp2.配置RabbitMQ的连接信息在appli
SpringBoot(2,arm架构和x86架构区别
m0_64205716
程序员 面试 java 后端
//gradle自身会用到的相关设置buildscript{//仓库repositories{//本地mavenLocal()//中央仓库mavenCentral()//grandle插件maven{url‘https://plugins.gradle.org/m2/’}}//子模块会用到的变量ext{springBootVersion=‘2.4.4’}}//插件plugins{id‘java’i
LogBack 动态修改日志级别
敏君宝爸
日志 log spring boot java
由于线上运行的项目,出现问题想看下sql的参数,或者一些框架的debug日志。需要把日志界别从info-->debug。需要动态实时的修改日志界别。只要类:LoggersEndpointmaven依赖:org.springframework.bootspring-boot-starter-actuatorspringboot1.x===================yml配置==========
springboot 适配ARM 架构
敏君宝爸
spring boot 架构 java
下载对应的mavenhttps://hub.docker.com/_/maven/tags?page=&page_size=&ordering=&name=3.5.3-alpinedockerpullmaven:3.5.3-alpine@sha256:4c4e266aacf8ea6976b52df8467134b9f628cfed347c2f6aaf9e6aff832f7c452、下载对应的jdk
容器运行时 源码分析
梅梅与彤彤
kubernetes源码分析 kubernetes 容器
源码地址https://github.com/opencontainers/runctagv1.2.5整体流程一个容器启动主要分为三大部分,如文章题目所示create:主要是为了解析、组装容器启动的配置和与子进程的消息通道等;init:主要根据容器配置启动容器整个运行环境,包括熟知ns,cgroups,seccomp,apparmor,caps等;start:主要是为了通知init进程启动容器;r
Spring Boot整合Resilience4j教程
嘵奇
提升自己 spring boot java
精心整理了最新的面试资料和简历模板,有需要的可以自行获取点击前往百度网盘获取点击前往夸克网盘获取以下是将SpringBoot与Resilience4j整合的详细教程,包含基础配置和核心功能示例:SpringBoot整合Resilience4j教程Resilience4j提供容错机制(断路器、重试、限流等),帮助构建弹性微服务。一、环境准备创建项目使用SpringInitializr生成项目,选择:
基于SpringBoot的电影售票系统
Computer程序设计
Java计算机程序设计 spring boot 后端 java vue.js spring java-ee tomcat
引言 在当今信息化高速发展的时代,互联网已经成为人们日常生活不可或缺的一部分。电影作为一种重要的文化娱乐形式,其售票方式也逐渐从传统的线下购票转向线上购票。基于SpringBoot和Vue的电影售票系统,正是为了迎合这一趋势而设计的。该系统不仅为用户提供了便捷、高效的购票渠道,还为影院管理者提供了强大的后台管理功能。通过该系统,用户可以轻松浏览影片信息、选择座位、在线支付,完成购票全过程;而影院
实时数据推送:Spring Boot 中两种 SSE 实战方案
微特尔普拉斯
java SpringBoot html5 前端 服务器 spring http 交互
在Web开发中,实时数据交互变得越来越普遍。无论是股票价格的波动、比赛比分的更新,还是聊天消息的传递,都需要服务器能够及时地将数据推送给客户端。传统的HTTP请求-响应模式在处理这类需求时显得力不从心,而服务器推送事件(Server-SentEvents,SSE)为我们提供了一种轻量级且高效的解决方案。本文将介绍两种基于SpringBoot实现SSE的方案,并结合代码示例,帮助你快速掌握实时数据推
深入源码分析spring AOP
萌新coder
Java基础知识 spring java 后端
深入源码分析springAOP一、SpringAOP核心概念AOP(面向切面编程)是Spring框架的核心功能之一,它通过动态代理技术,在不修改源代码的情况下,为业务逻辑横向添加通用功能(如日志、事务、权限等)。其核心思想是将业务代码与非业务代码解耦,例如://业务代码publicvoidtransferMoney(){//转账逻辑...}//非业务代码(日志记录)publicvoidlog(){
Spring Boot 实战:生成条形码的高效方案
墨夶
Java学习资料1 spring boot java 后端
嘿,小伙伴们!今天我们要来动手实践一个非常实用的功能——使用SpringBoot生成条形码。如果你是一名对后端开发感兴趣的开发者,并且希望在项目中集成条形码生成功能,那么这篇文章绝对不容错过!条形码广泛应用于物流、零售、库存管理等多个领域,能够极大地提高数据处理效率和准确性。通过本文,我们将从零开始创建一个简单的SpringBoot应用程序,涵盖以下内容:项目初始化引入依赖生成条形码提供RESTA
SpringBoot系列:Spring Boot定时任务Spring Schedule
2401_85763595
spring spring boot java
SpringSchedule是Spring提供的定时任务框架,相较于Quartz,Schedule更加简单易用,在中小型应用中,对于大部分需求,Schedule都可以胜任。一、SpringSchedule使用演示在SpringBoot使用SpringSchedule非常简单,因为SpringBoot自身的starter中已经集成了Schedule,而不需要我们做更多的处理。使用@EnableSch
【中国首个AI原生IDE:字节跳动发布AI编程工具Trae,开启智能编程新时代】
Kwan的解忧杂货铺@新空间代码工作室
s2 AIGC AI-native ide AI编程
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。推荐:kwan的首页,持续学习,不断总结,共同进步,活到老学到老导航檀越剑指大厂系列:全面总结java核心技术,jvm,并发编程redis,kafka,Spring,微服务等常用开发工具系列:常用的开发工具,IDEA,Mac,Alfred,Git,
mondb入手
木zi_鸣
mongodb
windows 启动mongodb 编写bat文件,
mongod --dbpath D:\software\MongoDBDATA
mongod --help 查询各种配置
配置在mongob
打开批处理,即可启动,27017原生端口,shell操作监控端口 扩展28017,web端操作端口
启动配置文件配置,
数据更灵活
大型高并发高负载网站的系统架构
bijian1013
高并发 负载均衡
扩展Web应用程序
一.概念
简单的来说,如果一个系统可扩展,那么你可以通过扩展来提供系统的性能。这代表着系统能够容纳更高的负载、更大的数据集,并且系统是可维护的。扩展和语言、某项具体的技术都是无关的。扩展可以分为两种:
1.
DISPLAY变量和xhost(原创)
czmmiao
display
DISPLAY
在Linux/Unix类操作系统上, DISPLAY用来设置将图形显示到何处. 直接登陆图形界面或者登陆命令行界面后使用startx启动图形, DISPLAY环境变量将自动设置为:0:0, 此时可以打开终端, 输出图形程序的名称(比如xclock)来启动程序, 图形将显示在本地窗口上, 在终端上输入printenv查看当前环境变量, 输出结果中有如下内容:DISPLAY=:0.0
获取B/S客户端IP
周凡杨
java 编程 jsp Web 浏览器
最近想写个B/S架构的聊天系统,因为以前做过C/S架构的QQ聊天系统,所以对于Socket通信编程只是一个巩固。对于C/S架构的聊天系统,由于存在客户端Java应用,所以直接在代码中获取客户端的IP,应用的方法为:
String ip = InetAddress.getLocalHost().getHostAddress();
然而对于WEB
浅谈类和对象
朱辉辉33
编程
类是对一类事物的总称,对象是描述一个物体的特征,类是对象的抽象。简单来说,类是抽象的,不占用内存,对象是具体的,
占用存储空间。
类是由属性和方法构成的,基本格式是public class 类名{
//定义属性
private/public 数据类型 属性名;
//定义方法
publ
android activity与viewpager+fragment的生命周期问题
肆无忌惮_
viewpager
有一个Activity里面是ViewPager,ViewPager里面放了两个Fragment。
第一次进入这个Activity。开启了服务,并在onResume方法中绑定服务后,对Service进行了一定的初始化,其中调用了Fragment中的一个属性。
super.onResume();
bindService(intent, conn, BIND_AUTO_CREATE);
base64Encode对图片进行编码
843977358
base64 图片 encoder
/**
* 对图片进行base64encoder编码
*
* @author mrZhang
* @param path
* @return
*/
public static String encodeImage(String path) {
BASE64Encoder encoder = null;
byte[] b = null;
I
Request Header简介
aigo
servlet
当一个客户端(通常是浏览器)向Web服务器发送一个请求是,它要发送一个请求的命令行,一般是GET或POST命令,当发送POST命令时,它还必须向服务器发送一个叫“Content-Length”的请求头(Request Header) 用以指明请求数据的长度,除了Content-Length之外,它还可以向服务器发送其它一些Headers,如:
HttpClient4.3 创建SSL协议的HttpClient对象
alleni123
httpclient 爬虫 ssl
public class HttpClientUtils
{
public static CloseableHttpClient createSSLClientDefault(CookieStore cookies){
SSLContext sslContext=null;
try
{
sslContext=new SSLContextBuilder().l
java取反 -右移-左移-无符号右移的探讨
百合不是茶
位运算符 位移
取反:
在二进制中第一位,1表示符数,0表示正数
byte a = -1;
原码:10000001
反码:11111110
补码:11111111
//异或: 00000000
byte b = -2;
原码:10000010
反码:11111101
补码:11111110
//异或: 00000001
java多线程join的作用与用法
bijian1013
java 多线程
对于JAVA的join,JDK 是这样说的:join public final void join (long millis )throws InterruptedException Waits at most millis milliseconds for this thread to die. A timeout of 0 means t
Java发送http请求(get 与post方法请求)
bijian1013
java spring
PostRequest.java
package com.bijian.study;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURL
【Struts2二】struts.xml中package下的action配置项默认值
bit1129
struts.xml
在第一部份,定义了struts.xml文件,如下所示:
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts
【Kafka十三】Kafka Simple Consumer
bit1129
simple
代码中关于Host和Port是割裂开的,这会导致单机环境下的伪分布式Kafka集群环境下,这个例子没法运行。
实际情况是需要将host和port绑定到一起,
package kafka.examples.lowlevel;
import kafka.api.FetchRequest;
import kafka.api.FetchRequestBuilder;
impo
nodejs学习api
ronin47
nodejs api
NodeJS基础 什么是NodeJS
JS是脚本语言,脚本语言都需要一个解析器才能运行。对于写在HTML页面里的JS,浏览器充当了解析器的角色。而对于需要独立运行的JS,NodeJS就是一个解析器。
每一种解析器都是一个运行环境,不但允许JS定义各种数据结构,进行各种计算,还允许JS使用运行环境提供的内置对象和方法做一些事情。例如运行在浏览器中的JS的用途是操作DOM,浏览器就提供了docum
java-64.寻找第N个丑数
bylijinnan
java
public class UglyNumber {
/**
* 64.查找第N个丑数
具体思路可参考 [url] http://zhedahht.blog.163.com/blog/static/2541117420094245366965/[/url]
*
题目:我们把只包含因子
2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14
二维数组(矩阵)对角线输出
bylijinnan
二维数组
/**
二维数组 对角线输出 两个方向
例如对于数组:
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 },
{ 13, 14, 15, 16 },
slash方向输出:
1
5 2
9 6 3
13 10 7 4
14 11 8
15 12
16
backslash输出:
4
3
[JWFD开源工作流设计]工作流跳跃模式开发关键点(今日更新)
comsci
工作流
既然是做开源软件的,我们的宗旨就是给大家分享设计和代码,那么现在我就用很简单扼要的语言来透露这个跳跃模式的设计原理
大家如果用过JWFD的ARC-自动运行控制器,或者看过代码,应该知道在ARC算法模块中有一个函数叫做SAN(),这个函数就是ARC的核心控制器,要实现跳跃模式,在SAN函数中一定要对LN链表数据结构进行操作,首先写一段代码,把
redis常见使用
cuityang
redis 常见使用
redis 通常被认为是一个数据结构服务器,主要是因为其有着丰富的数据结构 strings、map、 list、sets、 sorted sets
引入jar包 jedis-2.1.0.jar (本文下方提供下载)
package redistest;
import redis.clients.jedis.Jedis;
public class Listtest
配置多个redis
dalan_123
redis
配置多个redis客户端
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi=&quo
attrib命令
dcj3sjt126com
attr
attrib指令用于修改文件的属性.文件的常见属性有:只读.存档.隐藏和系统.
只读属性是指文件只可以做读的操作.不能对文件进行写的操作.就是文件的写保护.
存档属性是用来标记文件改动的.即在上一次备份后文件有所改动.一些备份软件在备份的时候会只去备份带有存档属性的文件.
Yii使用公共函数
dcj3sjt126com
yii
在网站项目中,没必要把公用的函数写成一个工具类,有时候面向过程其实更方便。 在入口文件index.php里添加 require_once('protected/function.php'); 即可对其引用,成为公用的函数集合。 function.php如下:
<?php /** * This is the shortcut to D
linux 系统资源的查看(free、uname、uptime、netstat)
eksliang
netstat linux uname linux uptime linux free
linux 系统资源的查看
转载请出自出处:http://eksliang.iteye.com/blog/2167081
http://eksliang.iteye.com 一、free查看内存的使用情况
语法如下:
free [-b][-k][-m][-g] [-t]
参数含义
-b:直接输入free时,显示的单位是kb我们可以使用b(bytes),m
JAVA的位操作符
greemranqq
位运算 JAVA位移 << >>>
最近几种进制,加上各种位操作符,发现都比较模糊,不能完全掌握,这里就再熟悉熟悉。
1.按位操作符 :
按位操作符是用来操作基本数据类型中的单个bit,即二进制位,会对两个参数执行布尔代数运算,获得结果。
与(&)运算:
1&1 = 1, 1&0 = 0, 0&0 &
Web前段学习网站
ihuning
Web
Web前段学习网站
菜鸟学习:http://www.w3cschool.cc/
JQuery中文网:http://www.jquerycn.cn/
内存溢出:http://outofmemory.cn/#csdn.blog
http://www.icoolxue.com/
http://www.jikexue
强强联合:FluxBB 作者加盟 Flarum
justjavac
r
原文:FluxBB Joins Forces With Flarum作者:Toby Zerner译文:强强联合:FluxBB 作者加盟 Flarum译者:justjavac
FluxBB 是一个快速、轻量级论坛软件,它的开发者是一名德国的 PHP 天才 Franz Liedke。FluxBB 的下一个版本(2.0)将被完全重写,并已经开发了一段时间。FluxBB 看起来非常有前途的,
java统计在线人数(session存储信息的)
macroli
java Web
这篇日志是我写的第三次了 前两次都发布失败!郁闷极了!
由于在web开发中常常用到这一部分所以在此记录一下,呵呵,就到备忘录了!
我对于登录信息时使用session存储的,所以我这里是通过实现HttpSessionAttributeListener这个接口完成的。
1、实现接口类,在web.xml文件中配置监听类,从而可以使该类完成其工作。
public class Ses
bootstrp carousel初体验 快速构建图片播放
qiaolevip
每天进步一点点 学习永无止境 bootstrap 纵观千象
img{
border: 1px solid white;
box-shadow: 2px 2px 12px #333;
_width: expression(this.width > 600 ? "600px" : this.width + "px");
_height: expression(this.width &
SparkSQL读取HBase数据,通过自定义外部数据源
superlxw1234
spark sparksql sparksql读取hbase sparksql外部数据源
关键字:SparkSQL读取HBase、SparkSQL自定义外部数据源
前面文章介绍了SparSQL通过Hive操作HBase表。
SparkSQL从1.2开始支持自定义外部数据源(External DataSource),这样就可以通过API接口来实现自己的外部数据源。这里基于Spark1.4.0,简单介绍SparkSQL自定义外部数据源,访
Spring Boot 1.3.0.M1发布
wiselyman
spring boot
Spring Boot 1.3.0.M1于6.12日发布,现在可以从Spring milestone repository下载。这个版本是基于Spring Framework 4.2.0.RC1,并在Spring Boot 1.2之上提供了大量的新特性improvements and new features。主要包含以下:
1.提供一个新的sprin