Spring AOP学习笔记04:AOP核心实现之创建代理
上文中,我们分析了对所有增强器的获取以及获取匹配的增强器,在本文中我们就来分析一下Spring AOP中另一部分核心逻辑--代理的创建。这部分逻辑的入口是在wrapIfNecessary()方法中紧接着增强器的获取之后的createProxy():
protected Object createProxy(
Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) { ProxyFactory proxyFactory = new ProxyFactory();
// 获取当前类中相关属性
proxyFactory.copyFrom(this);
// 决定对于给定的bean是否应该使用targetClass而不是它的接口进行代理
if (!shouldProxyTargetClass(beanClass, beanName)) {
// Must allow for introductions; can't just set interfaces to
// the target's interfaces only.
Class<?>[] targetInterfaces = ClassUtils.getAllInterfacesForClass(beanClass, this.proxyClassLoader);
for (Class<?> targetInterface : targetInterfaces) {
// 添加代理接口
proxyFactory.addInterface(targetInterface);
}
} Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
for (Advisor advisor : advisors) {
// 加入增强器
proxyFactory.addAdvisor(advisor);
}
// 设置要代理的类
proxyFactory.setTargetSource(targetSource);
// 定制代理
customizeProxyFactory(proxyFactory);
// 用来控制代理工厂被配置之后,是否还允许修改通知
// 默认值为false(即在代理被配置之后,不允许修改代理的配置)
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
} return proxyFactory.getProxy(this.proxyClassLoader);
}
对于代理类的创建及处理,Spring委托给了ProxyFactory进行处理,而在上面的函数中主要是对ProxyFactory的初始化操作,为真正的代理创建做准备,初始化包括如下内容:
- 获取当前中的属性;
- 添加代理接口;
- 封装Advisor并加入到ProxyFactory中;
- 设置要代理的类;
- 对代理工厂进行定制化处理,供子类实现;
- 进行获取代理操作;
其中封装Advisor并加入到ProxyFactory中以及创建代理是两个相对繁琐的过程,可以通过ProxyFactory提供的addAdvisor方法直接将增强器置入代理创建工厂中,但是将拦截器封装为增强器还是需要一定的逻辑的。
protected Advisor[] buildAdvisors(String beanName, Object[] specificInterceptors) {
// 解析注册的所有interceptorName
Advisor[] commonInterceptors = resolveInterceptorNames(); List<Object> allInterceptors = new ArrayList<Object>();
if (specificInterceptors != null) {
// 加入拦截器
allInterceptors.addAll(Arrays.asList(specificInterceptors));
if (commonInterceptors != null) {
if (this.applyCommonInterceptorsFirst) {
allInterceptors.addAll(0, Arrays.asList(commonInterceptors));
}
else {
allInterceptors.addAll(Arrays.asList(commonInterceptors));
}
}
}
if (logger.isDebugEnabled()) {
int nrOfCommonInterceptors = (commonInterceptors != null ? commonInterceptors.length : 0);
int nrOfSpecificInterceptors = (specificInterceptors != null ? specificInterceptors.length : 0);
logger.debug("Creating implicit proxy for bean '" + beanName + "' with " + nrOfCommonInterceptors +
" common interceptors and " + nrOfSpecificInterceptors + " specific interceptors");
} Advisor[] advisors = new Advisor[allInterceptors.size()];
for (int i = 0; i < allInterceptors.size(); i++) {
// 将拦截器进行包装转化为Advisor
advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));
}
return advisors;
} public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
// 如果要封装的对象本身就是Advisor类型的那么无需再做过多处理
if (adviceObject instanceof Advisor) {
return (Advisor) adviceObject;
}
// 如果不是Advisor与Advice两种类型,则抛出异常
if (!(adviceObject instanceof Advice)) {
throw new UnknownAdviceTypeException(adviceObject);
}
Advice advice = (Advice) adviceObject;
if (advice instanceof MethodInterceptor) {
// 如果是MethodInterceptor类型则使用DefaultPointcutAdvisor封装
return new DefaultPointcutAdvisor(advice);
}
// 如果存在Advisor的适配器那么也同样需要进行封装
for (AdvisorAdapter adapter : this.adapters) {
// Check that it is supported.
if (adapter.supportsAdvice(advice)) {
return new DefaultPointcutAdvisor(advice);
}
}
throw new UnknownAdviceTypeException(advice);
}
因为Spring中涉及过多的拦截器、增强器、增强方法等方式来对逻辑进行增强,所以非常有必要将增强器封装成Advisor来进行代理的创建,完成了增强的封装过程,那么接下来就是最重要的一步--代理的创建与获取。
public Object getProxy(ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}
1. 创建代理
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
// 创建代理
return getAopProxyFactory().createAopProxy(this);
} public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface()) {
return new JdkDynamicAopProxy(config);
}
return CglibProxyFactory.createCglibProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
到这里已经完成了代理的创建了,不管我们之前是否有阅读过Spring的源码,但是应该或多或少都听说过对于Spring的代理中JDKProxy的实现CglibProxy的实现。Spring是如果选取的呢?现在我们就从源码的角度分析,看看到底Spring是如何选择代理方式的。
从上面代码中的判断条件可以看到3个方面影响着Spring的判断:
- optimize:用来控制通过CGLIB创建的代理是否使用激进的优化策略。除非完全了解AOP代理如何处理优化,否则不推荐用户使用这个设置。目前这个属性仅用于CGLIB代理,对于JDK动态代理(默认代理)无效。
- proxyTargetClass:这个属性为true时,目标类本身被代理而不是目标类的接口,并且使用CGLIB方式创建代理,xml文件配置方式为:<aop:aspectj-autoproxy proxy-target-class="true"/>。
- hasNoUserSuppliedProxyInterfaces:是否存在代理接口。
下面是对JDK与Cglib方式的总结:
- 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP;
- 如果目标对象实现了接口,可以强制使用CGLIB实现AOP;
- 如果目标对象没有实现接口,则必须采用CGLIB方式实现AOP,Spring会自动切换;
如何强制使用CGLIB实现AOP?
- 添加CGLIB库,Spring_HOME/cglib/*.jar
- 在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
JDK动态代理和CGLIB字节码生成的区别?
- JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
- CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成final。
2. 获取代理
确定了使用哪种代理方式之后便可以进行代理的创建了,Spring中主要使用了两种方式来实现代理的创建:JDK动态代理、cglib,我们一一来解析。
2.1 JDK动态代理方式
这里直接定位到JdkDynamicAopProxy中的getProxy():
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
JDK动态代理的使用关键是创建自定义的InvocationHandler,而InvocationHandler中包含了需要覆盖的函数getProxy,这里其实JdkDynamicAopProxy就是继承了InvocationHandler的,所以上面的方法正是完成了这个操作,并且我们还可以推断出,在JdkDynamicAopProxy中一定会有一个invoke函数,并且JdkDynamicAopProxy会把AOP的核心逻辑写在其中,找一下,一定会有这样一个函数的:
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 {
// 处理equals方法
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
return equals(args[0]);
}
// 处理hash方法
if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
return hashCode();
}
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) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
} target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
} // 获取当前方法的拦截器链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); if (chain.isEmpty()) {
// 如果没有任何拦截器链则直接调用切点方法
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
// 将拦截器封装在ReflectiveMethodInvocation,以便于使用其proceed进行链式调用拦截器
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// 执行拦截器链
retVal = invocation.proceed();
} // 返回结果
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
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()函数最主要的工作就是创建了一个拦截器链,并使用ReflectiveMethodInvocation类进行了链的封装,而在ReflectiveMethodInvocation类的proceed方法中实现了拦截器的逐一调用,那么我们就继续来探究,在proceed方法中是怎么实现诸如前置增强在目标方法前调用以及后置增强在目标方法后调用的逻辑的。
public Object proceed() throws Throwable {
// 执行完所有增强后执行切点方法
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
// 获取下一个要执行的拦截器
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// 动态匹配
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// 若未匹配则不执行拦截器,调用拦截器链中下一个
return proceed();
}
}
else {
// 普通拦截器,直接调用。将this作为参数传入以保证当前实例中调用链的执行
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
ReflectiveMethodInvocation的主要职责是维护一个链式调用的计数器,记录着当前调用链的位置,以便链可以有序地进行下去。其实在这个方法中并没有我们设想的维护各种增强的顺序,但是细心的读者可能会发现,这部分工作其实是委托给了各个增强器来实现,前面有说到。
2.2 Cglib方式
完成CGLIB代理的类是委托给CglibAopProxy类去实现的,我们来一探究竟。根据前面的分析,我们容易判断出来,CglibAopProxy的入口应该也是在getProxy():
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating CGLIB proxy: target source is " + this.advised.getTargetSource());
} try {
Class<?> rootClass = this.advised.getTargetClass();
Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy"); Class<?> proxySuperClass = rootClass;
if (ClassUtils.isCglibProxyClass(rootClass)) {
proxySuperClass = rootClass.getSuperclass();
Class<?>[] additionalInterfaces = rootClass.getInterfaces();
for (Class<?> additionalInterface : additionalInterfaces) {
this.advised.addInterface(additionalInterface);
}
} // 验证Class
validateClassIfNecessary(proxySuperClass); // 创建及配置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 MemorySafeUndeclaredThrowableStrategy(UndeclaredThrowableException.class));
enhancer.setInterceptDuringConstruction(false);
// 设置拦截器
Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
enhancer.setCallbackFilter(new ProxyCallbackFilter(
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);
enhancer.setCallbacks(callbacks); // 生成代理类及创建代理对象
Object proxy;
if (this.constructorArgs != null) {
proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);
}
else {
proxy = enhancer.create();
} return proxy;
}
catch (CodeGenerationException ex) {
catch若干异常。。。
}
}
上面的函数中就是一个完整创建Enhancer的过程,详细可以参考Enhancer的文档,这里最重要的是通过getCallbacks()方法设置拦截器链。
private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
// 对于expose-proxy属性的处理
boolean exposeProxy = this.advised.isExposeProxy();
boolean isFrozen = this.advised.isFrozen();
boolean isStatic = this.advised.getTargetSource().isStatic(); // 将拦截器封装在DynamicAdvisedInterceptor中
Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised); // Choose a "straight to target" interceptor. (used for calls that are
// unadvised but can return this). May be required to expose the proxy.
Callback targetInterceptor;
if (exposeProxy) {
targetInterceptor = isStatic ?
new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :
new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource());
}
else {
targetInterceptor = isStatic ?
new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
new DynamicUnadvisedInterceptor(this.advised.getTargetSource());
} // 将拦截器加入到callback中
Callback targetDispatcher = isStatic ?
new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp(); Callback[] mainCallbacks = new Callback[] {
aopInterceptor, // for normal advice
targetInterceptor, // invoke target without considering advice, if optimized
new SerializableNoOp(), // no override for methods mapped to this
targetDispatcher, this.advisedDispatcher,
new EqualsInterceptor(this.advised),
new HashCodeInterceptor(this.advised)
}; Callback[] callbacks; // If the target is a static one and the advice chain is frozen,
// then we can make some optimisations by sending the AOP calls
// direct to the target using the fixed chain for that method.
if (isStatic && isFrozen) {
Method[] methods = rootClass.getMethods();
Callback[] fixedCallbacks = new Callback[methods.length];
this.fixedInterceptorMap = new HashMap<String, Integer>(methods.length); // TODO: small memory optimisation here (can skip creation for methods with no advice)
for (int x = 0; x < methods.length; x++) {
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(methods[x], rootClass);
fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
this.fixedInterceptorMap.put(methods[x].toString(), x);
} // Now copy both the callbacks from mainCallbacks
// and fixedCallbacks into the callbacks array.
callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
this.fixedInterceptorOffset = mainCallbacks.length;
}
else {
callbacks = mainCallbacks;
}
return callbacks;
}
在getCallback()中Spring考虑了很多情况,有很多的细节,但是我们阅读源码是没有必要也没有那么多精力把每一个细节都弄明白的,重点是抓住主干即可。这里只需要理解最常用的,比如将advised属性封装在DynamicAdvisedInterceptor并加入在callbacks中,这么做的目的是什么呢?在CGLIB中对于方法的拦截是通过将自定义的拦截器(实现了MethodInterceptor接口的类)加入Callback中并在调用代理时直接激活拦截器中的intercept()方法来实现的,而在getCallback()方法中正好有这一部分功能的实现,DynamicAdvisedInterceptor继承自MethodInterceptor,加入Callback中后,在再次调用代理时会直接调用其intercept()方法,由此推断,对于CGLIB方式实现的代理,其核心逻辑应该是在DynamicAdvisedInterceptor中的intercept()方法中的:
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Class<?> targetClass = null;
Object target = null;
try {
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 = getTarget();
if (target != null) {
targetClass = target.getClass();
}
// 获取拦截器链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// 如果拦截器链为空则直接激活原方法
retVal = methodProxy.invoke(target, args);
}
else {
// 链式调用
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
if (target != null) {
releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
这里的实现与JDK动态代理方式实现代理中的invoke方法大同小异,都是首先构造拦截器链,然后封装此链进行串联调用,不同的是在JDK动态代理的方式中是直接构造ReflectiveMethodInvocation,而在cglib中则是使用CglibMethodInvocation,其是继承自ReflectiveMethodInvocation,但是proceed()方法并没有重写。
3. 总结
本文着重分析了Spring AOP实现原理中代理对象的创建过程,在bean的初始化过程中会执行Spring的后置处理器,这里会去判断这个bean是否需要增强,如果需要则会根据Aspect中定义的增强信息,对指定bean进行增强,也就是创建一个代理对象。对代理对象的创建有两种方式,一种是通过JDK动态代理的方式,另一种是通过cglib的方式。
Spring AOP学习笔记04:AOP核心实现之创建代理的更多相关文章
- 【转】Spring.NET学习笔记——目录
目录 前言 Spring.NET学习笔记——前言 第一阶段:控制反转与依赖注入IoC&DI Spring.NET学习笔记1——控制反转(基础篇) Level 200 Spring.NET学习笔 ...
- Spring.NET学习笔记——目录(原)
目录 前言 Spring.NET学习笔记——前言 第一阶段:控制反转与依赖注入IoC&DI Spring.NET学习笔记1——控制反转(基础篇) Level 200 Spring.NET学习笔 ...
- Spring学习笔记之aop动态代理(3)
Spring学习笔记之aop动态代理(3) 1.0 静态代理模式的缺点: 1.在该系统中有多少的dao就的写多少的proxy,麻烦 2.如果目标接口有方法的改动,则proxy也需要改动. Person ...
- Spring入门IOC和AOP学习笔记
Spring入门IOC和AOP学习笔记 概述 Spring框架的核心有两个: Spring容器作为超级大工厂,负责管理.创建所有的Java对象,这些Java对象被称为Bean. Spring容器管理容 ...
- Spring学习笔记2—AOP
1.AOP概念 AOP(Aspect Oriented Programming):面向切面编程,AOP能够将那些与业务无关,却为业务模块所共同调用的应用(例如事务处理.日志管理.权限控制等)封装起来, ...
- Spring框架学习笔记(1)
Spring 框架学习笔记(1) 一.简介 Rod Johnson(spring之父) Spring是分层的Java SE/EE应用 full-stack(服务端的全栈)轻量级(跟EJB比)开源框架, ...
- Spring MVC 学习笔记12 —— SpringMVC+Hibernate开发(1)依赖包搭建
Spring MVC 学习笔记12 -- SpringMVC+Hibernate开发(1)依赖包搭建 用Hibernate帮助建立SpringMVC与数据库之间的联系,通过配置DAO层,Service ...
- Spring Boot 学习笔记(六) 整合 RESTful 参数传递
Spring Boot 学习笔记 源码地址 Spring Boot 学习笔记(一) hello world Spring Boot 学习笔记(二) 整合 log4j2 Spring Boot 学习笔记 ...
- [java学习笔记]java语言核心----面向对象之this关键字
一.this关键字 体现:当成员变量和函数的局部变量重名时,可以使用this关键字来区别:在构造函数中调用其它构造函数 原理: 代表的是当前对象. this就是所在函数 ...
随机推荐
- python基本操作-文件、目录及路径
目录 1 前言 2 文件夹操作 2.1 查询操作 2.2 创建操作 2.3 删除操作 2.4 修改操作 3 文件操作 3.1 查询操作 3.2 创建操作 3.3 修改操作 3.4 删除 4 路径操作 ...
- Rocket - diplomacy - resolveStar
https://mp.weixin.qq.com/s/W1cS9sgwLFjOOm86d05NIA 介绍各类型节点如何确定星型绑定所包含的连接数. 1. 定义 resoveSta ...
- Java实现 LeetCode 709 转换成小写字母(ASCII码处理)
709. 转换成小写字母 实现函数 ToLowerCase(),该函数接收一个字符串参数 str,并将该字符串中的大写字母转换成小写字母,之后返回新的字符串. 示例 1: 输入: "Hell ...
- (Java实现) 友好城市
1263:[例9.7]友好城市 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 1867 通过数: 1032 [题目描述] Palmia国有一条横贯东西的大河,河有笔直的南北两岸, ...
- Java实现 LeetCode 542 01 矩阵(暴力大法,正反便利)
542. 01 矩阵 给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离. 两个相邻元素间的距离为 1 . 示例 1: 输入: 0 0 0 0 1 0 0 0 0 输出: 0 0 0 ...
- Java实现 LeetCode 416 分割等和子集
416. 分割等和子集 给定一个只包含正整数的非空数组.是否可以将这个数组分割成两个子集,使得两个子集的元素和相等. 注意: 每个数组中的元素不会超过 100 数组的大小不会超过 200 示例 1: ...
- 第六届蓝桥杯JavaA组省赛真题
解题代码部分来自网友,如果有不对的地方,欢迎各位大佬评论 题目1.熊怪吃核桃 题目描述 森林里有一只熊怪,很爱吃核桃.不过它有个习惯,每次都把找到的核桃分成相等的两份,吃掉一份,留一份.如果不能等分, ...
- java中Runtime类详细介绍
Runtime类描述了虚拟机一些信息.该类采用了单例设计模式,可以通过静态方法 getRuntime()获取Runtime类实例.下面演示了获取虚拟机的内存信息: package Main; publ ...
- mysql数据库-mysql数据定义语言DDL (Data Definition Language)归类(六)
0x01 创建数据库并指定字符集和排序规则 -- 三种实例写法 create database temptab2 character set utf8 collate utf8_general_ci; ...
- zabbix 中文乱码
环境 zabbix 3.4.7 centos 7.4 问题现象 zabbix 中文乱码 解决方法 1.先准备一个字体包 Windows路径 C:\Windows\Fonts\simkai ...