AOP(Aspect Oriented Programming)

面向切面编程,是一种编程范式,提供从另一个角度来考虑程序结构从而完善面向对象编程(OOP)。

在进行OOP开发时,都是基于对组件(比如类)进行开发,然后对组件进行组合,OOP最大问题就是无法解耦组件进行开发。

AOP为开发者提供一种进行横切关注点(比如日志关注点横切了支付关注点)分离并织入的机制,把横切关注点分离,然后通过某种技术织入到系统中,从而无耦合的完成了我们的功能。

横切关注点可能包含很多,比如非业务的:日志、事务处理、缓存、性能统计、权限控制等;还可能是业务的:如某个业务组件横切于多个模块。

基本概念

  • 连接点(Jointpoint):表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等,Spring只支持方法执行连接点;“在哪里干”
  • 切入点(Pointcut):选择一组相关连接点的模式,即可以认为连接点的集合,Spring默认使用AspectJ语法;“在哪里干的集合”;[ <aop:aspectj-autoproxy/> 开启spring对@Aspectj的支持]
  • 通知(Advice):在连接点上执行的行为;“干什么”
  • 引入/内部类型声明(inter-type declaration):为已有的类添加额外新的字段或方法,Spring允许引入新的接口(必须对应一个实现)到所有被代理对象(目标对象);“干什么(引入什么)”
  • 切面(Aspect):横切关注点的模块化,比如日志组件。可以认为是通知、引入和切入点的组合,在Spring中可以使用Schema和@AspectJ方式进行组织实现;“在哪干和干什么集合”
  • 目标对象(Target Object):需要被织入横切关注点的对象,被代理对象;“对谁干”
  • 织入(Weaving):织入是一个过程,是将切面应用到目标对象从而创建出AOP代理对象的过程,织入可以在编译期、类装载期、运行期进行。
    • 编译时织入,生成完整功能的Java字节码,需特殊编译器(AspectJ)
    • 类加载时织入,AspectJ、AspectWerkz
    • 运行时织入,动态代理。@EnableAspectJAutoProxy。[Spring采用运行时]
  • AOP代理(AOP Proxy):AOP框架使用代理模式创建的对象,从而实现在连接点处插入通知,就是通过代理来对目标对象应用切面

What-@Aspect

Where-@Pointcut

When-@Advice(@Before、@After、@AfterReturning、@AfterThrowing、@Around)

在Spring中通过代理模式实现AOP,并通过拦截器模式以环绕连接点的拦截器链织入通知(即应用切面)

通知类型

  • 前置通知(Before Advice):在切入点选择的连接点处的方法之前执行的通知,该通知不影响正常程序执行流程(除非该通知抛出异常,该异常将中断当前方法链的执行而返回)。
  • 后置通知(After Advice):在切入点选择的连接点处的方法之后执行的通知,包括如下类型的后置通知:
    • 后置返回通知(After returning Advice):在切入点选择的连接点处的方法正常执行完毕时执行的通知,必须是连接点处的方法没抛出任何异常正常返回时才调用后置通知。
    • 后置异常通知(After throwing Advice): 在切入点选择的连接点处的方法抛出异常返回时执行的通知,必须是连接点处的方法抛出任何异常返回时才调用异常通知。
    • 后置最终通知(After finally Advice): 在切入点选择的连接点处的方法返回时执行的通知,不管抛没抛出异常都执行,类似于Java中的finally块。
  • 环绕通知(Around Advices):环绕着在切入点选择的连接点处的方法所执行的通知,环绕通知可以在方法调用之前和之后自定义任何行为,并且可以决定是否执行连接点处的方法、替换返回值、抛出异常等等。

通知顺序

Spring中可以通过在切面实现类上实现org.springframework.core.Ordered接口或使用Order注解来指定切面优先级。在多个切面中,Ordered.getValue()方法返回值(或者注解值)较小值的那个切面拥有较高优先级。

@Order(2)

动态代理

如果目标对象实现了接口,默认使用JDK,可以强制用CGLIB;否则,采用CGLIB。

JDK动态代理

只能为接口创建动态代理实例,而不能针对类。

使用java.lang.reflect.Proxy动态代理实现,通过调用目标类的getClass().getInterfaces()方法获取目标对象的接口信息,并生成一个实现了代理接口的动态代理class,然后通过反射技术获得该class的构造函数,并利用构造函数生成实例,在调用具体方法前调用InvocationHandler处理。

在Proxy这个类当中首先实例化一个对象ProxyClassFactory,然后在get方法中调用了apply方法,完成对代理类的创建:

  • generateProxyClass通过反射收集字段和属性然后生成字节
  • defineClass0 jvm内部完成对上述字节的load

CGLIB动态代理

主要是对指定的类生成一个子类,覆盖其中的方法。(不能通知final方法)

利用asm开源包,采用字节码技术,为一个类创建子类,在子类中采用方法拦截(MethodInterceptor @override intercept()),拦截所有父类方法的调用,顺势织入横切逻辑。

会产生两次构造器调用,第一次是目标类的构造器调用,第二次是CGLIB生成的代理类的构造器调用。

设计分析

1 为目标对象建立AopProxy代理对象

在依赖注入时,实例化bean后都会调用getObjectForBeanInstance方法,这里就是处理FactoryBean的入口。

通过配置和调用ProxyFactoryBean来完成代理对象的创建。

配置通知器advisor、proxyFactoryBean(目标对象target、interceptorNames拦截器数组、目标对象接口数组)

// ProxyFactoryBean
@Nullable
public Object getObject() throws BeansException {
// 对通知器链进行初始化
// 通知器链封装了一系列的拦截器(需要从配置中读取)
// 然后为代理对象的生成做准备
this.initializeAdvisorChain();
// 区分两种类型的Bean
if (this.isSingleton()) {
return this.getSingletonInstance();
} else {
...
return this.newPrototypeInstance();
}
} private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {
// 通知器链未初始化(初始化工作发生在应用第一次通过ProxyFactoryBean获取代理对象的时候)
if (!this.advisorChainInitialized) {
if (!ObjectUtils.isEmpty(this.interceptorNames)) {
...
String[] var1 = this.interceptorNames;
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
String name = var1[var3];
...
if (name.endsWith("*")) {
...
} else {
Object advice;
if (!this.singleton && !this.beanFactory.isSingleton(name)) {
// Prototype类型
advice = new ProxyFactoryBean.PrototypePlaceholderAdvisor(name);
} else {
// singleton类型:通过getBean获取通知器
advice = this.beanFactory.getBean(name);
}
// 将通知器加入拦截器链中
this.addAdvisorOnChainCreation(advice, name);
}
}
} this.advisorChainInitialized = true;
}
} private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
this.targetSource = this.freshTargetSource();
if (this.autodetectInterfaces && this.getProxiedInterfaces().length == 0 && !this.isProxyTargetClass()) {
// 判断需要代理的接口
Class<?> targetClass = this.getTargetClass();
...
// 设置代理对象调用接口
this.setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
} super.setFrozen(this.freezeProxy);
// createAopProxy:生成aop代理对象 ★调用父类的createAopProxy()
// getProxy:<<AopProxy>>接口的两种实现--JdkDynamicAopProxy、CglibAopProxy
this.singletonInstance = this.getProxy(this.createAopProxy());
} return this.singletonInstance;
} private synchronized Object newPrototypeInstance() {
...
ProxyCreatorSupport copy = new ProxyCreatorSupport(this.getAopProxyFactory());
TargetSource targetSource = this.freshTargetSource();
copy.copyConfigurationFrom(this, targetSource, this.freshAdvisorChain());
if (this.autodetectInterfaces && this.getProxiedInterfaces().length == 0 && !this.isProxyTargetClass()) {
// 和上面一模一样的套路啊。。。
Class<?> targetClass = targetSource.getTargetClass();
if (targetClass != null) {
copy.setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
}
}
...
// 然后通过<<AopProxy>>接口的两种实现--JdkDynamicAopProxy、CglibAopProxy获取代理对象
return this.getProxy(copy.createAopProxy());
}
// ProxyCreatorSupport
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
this.activate();
} // createAopProxy:<<AopProxyFactory>>接口的方法
return this.getAopProxyFactory().createAopProxy(this);
}
// 具体实现:DefaultAopProxyFactory
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 如果目标对象是接口类,使用jdk
if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
return new JdkDynamicAopProxy(config);
} else {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
...
} else {
// ObjenesisCglibAopProxy extends CglibAopProxy
return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
}
}
// new的过程都是先从AdvisedSupport对象中取得配置的目标对象进行检查
}
// JdkDynamicAopProxy
public Object getProxy(@Nullable ClassLoader classLoader) {
...
// 首先从advised对象中获取代理对象的代理接口配置
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
this.findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
// 然后调用Proxy的newProxyInstance方法得到Proxy代理对象
// 需要三个参数:类加载器、代理接口、回调方法所在对象
// 回调方法用的是InvokeHandler的invoke回调入口
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
// CglibAopProxy
public Object getProxy(@Nullable ClassLoader classLoader) {
...
try {
// 从advised对象中获取配置的target对象
Class<?> rootClass = this.advised.getTargetClass();
...
Class<?> proxySuperClass = rootClass;
int x;
if (ClassUtils.isCglibProxyClass(rootClass)) {
proxySuperClass = rootClass.getSuperclass();
Class<?>[] additionalInterfaces = rootClass.getInterfaces();
Class[] var5 = additionalInterfaces;
int var6 = additionalInterfaces.length; for(x = 0; x < var6; ++x) {
Class<?> additionalInterface = var5[x];
this.advised.addInterface(additionalInterface);
}
}
// 验证代理对象的接口设置
this.validateClassIfNecessary(proxySuperClass, classLoader);
// 创建并配置cglib的enhancer
Enhancer enhancer = this.createEnhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
if (classLoader instanceof SmartClassLoader && ((SmartClassLoader)classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
// 设置enhancer的代理接口、回调方法等
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new CglibAopProxy.ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));
Callback[] callbacks = this.getCallbacks(rootClass);
Class<?>[] types = new Class[callbacks.length]; for(x = 0; x < types.length; ++x) {
types[x] = callbacks[x].getClass();
} enhancer.setCallbackFilter(new CglibAopProxy.ProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);
// 通过enhancer生成代理对象 ★
// 注意这里有回调
return this.createProxyClassAndInstance(enhancer, callbacks);
}...
} // 这里是回调方法,通过设置DynamicAdvisedInterceptor拦截器完成AOP功能
private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
...
Callback aopInterceptor = new CglibAopProxy.DynamicAdvisedInterceptor(this.advised);
...
return callbacks;
}

2 启动代理对象的拦截器来完成各种横切面的织入

在第一步的过程中,拦截器已经配置到代理对象中,它是通过回调方法起作用。

JdkDynamicAopProxy的invoke拦截
    @Nullable
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; Boolean var8;
try {
if (this.equalsDefined || !AopUtils.isEqualsMethod(method)) {
...
// 得到目标对象
target = targetSource.getTarget();
Class<?> targetClass = target != null ? target.getClass() : null;
// 这个方法对象注册了拦截器
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// 没有设定拦截器链,直接调用target对象的方法
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
// 对target对象的方法调用是通过反射机制,然后使用invoke调用方法反射对象
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
} else {
// 拦截,通过ReflectiveMethodInvocation
MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// 沿着拦截器链继续前进
retVal = invocation.proceed();
}
...
}
var8 = this.equals(args[0]);
}...
return var8;
}
CglibAopProxy的DynamicAdvisedInterceptor的intercept拦截
        @Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
...
try {
...
// 得到目标对象
target = targetSource.getTarget();
Class<?> targetClass = target != null ? target.getClass() : null;
// 从advised取得配置好的AOP通知
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// 若没有,则直接调用target对象的方法
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
} else {
// 拦截,通过CglibMethodInvocation启动advice通知
// CglibMethodInvocation extends ReflectiveMethodInvocation
// 沿着拦截器链继续前进
retVal = (new CglibAopProxy.CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed();
}
retVal = CglibAopProxy.processReturnType(proxy, target, method, retVal);
var16 = retVal;
}...
return var16;
}

综上,都是从advised取得拦截器链的。

拦截都是通过ReflectiveMethodInvocation的proceed方法实现的。

如何将拦截器链配置进advised的?

拦截器是ReflectiveMethodInvocation类中一个名为interceptorsAndDynamicMethodMatchers的List中的元素。

this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

advised的getInterceptorsAndDynamicInterceptionAdvice是由advisorChainFactory实现的,它的具体类型是DefaultAdvisorChainFactory。

DefaultAdvisorChainFactory通过AdvisorAdapterRegistry来适配ProxyFactoryBean中得到的通知器,注册拦截器。

在ProxyFactoryBean的getObject方法中对advisor进行初始化时,从配置中获取了通知器。(通过实现BeanFactoryAware接口,设置回调方法委托给IoC容器)

proceed方法
    @Nullable
public Object proceed() throws Throwable {
// 如果已经到拦截器链的末尾,直接调用目标对象的方法
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return this.invokeJoinpoint();
} else {
// 否则,得到下一个拦截器
Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
// 通过拦截器进行matches判断是否适用于横切增强的场合
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
Class<?> targetClass = this.targetClass != null ? this.targetClass : this.method.getDeclaringClass();
// 如果是,从拦截器得到通知器,并启动invoke方法
// 否则,迭代调用proceed方法
return dm.methodMatcher.matches(this.method, targetClass, this.arguments) ? dm.interceptor.invoke(this) : this.proceed();
} else {
return ((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this);
}
}
}
advice通知是如何实现的?

DefaultAdvisorChainFactory通过AdvisorAdapterRegistry来适配ProxyFactoryBean中得到的通知器,注册拦截器。

// DefaultAdvisorChainFactory
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Advised config, Method method, @Nullable Class<?> targetClass) {
// GlobalAdvisorAdapterRegistry是一个单例,作用是适配器
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
Advisor[] advisors = config.getAdvisors();
... for(int var11 = 0; var11 < var10; ++var11) {
Advisor advisor = var9[var11];
if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor)advisor;
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
...
if (match) {
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
...
}
}
} 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;
}

MethodInterceptor[] interceptors = registry.getInterceptors(advisor);封装着advice织入实现的入口。

// DefaultAdvisorAdapterRegistry
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
List<MethodInterceptor> interceptors = new ArrayList(3);
Advice advice = advisor.getAdvice();
if (advice instanceof MethodInterceptor) {
interceptors.add((MethodInterceptor)advice);
}
// 对通知进行适配
Iterator var4 = this.adapters.iterator();
while(var4.hasNext()) {
AdvisorAdapter adapter = (AdvisorAdapter)var4.next();
// 如果适配器支持某种通知,则从对应的适配器中获取该类通知器的拦截器
if (adapter.supportsAdvice(advice)) {
interceptors.add(adapter.getInterceptor(advisor));
}
}
...
}

我们知道,proceed处理拦截器的时候是通过dm.interceptor.invoke(this)拦截器的回调方法。下面举一个例子。

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
MethodBeforeAdviceAdapter() {
} public boolean supportsAdvice(Advice advice) {
return advice instanceof MethodBeforeAdvice;
} 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;
} // 先触发advice的before,然后才是proceed调用!
// 因为这是method before!如果是其他的拦截器,顺序、具体实现又不一样了
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
return mi.proceed();
}
}

spring(五):AOP的更多相关文章

  1. Spring(五)AOP简述

    一.AOP简述 AOP全称是:aspect-oriented programming,它是面向切面编号的思想核心, AOP和OOP既面向对象的编程语言,不相冲突,它们是两个相辅相成的设计模式型 AOP ...

  2. 跟着刚哥学习Spring框架--AOP(五)

    AOP AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善.OOP引入 ...

  3. Spring框架第五篇之Spring与AOP

    一.AOP概述 AOP(Aspect Orient Programming),面向切面编程,是面向对象编程OOP的一种补充.面向对象编程是从静态角度考虑程序的结构,而面向切面编程是从动态角度考虑程序运 ...

  4. Spring 学习十五 AOP

    http://www.hongyanliren.com/2014m12/22797.html 1: 通知(advice): 就是你想要的功能,也就是安全.事物.日子等.先定义好,在想用的地方用一下.包 ...

  5. Spring学习之旅(五)--AOP

    什么是 AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是 OOP(Object-Oriented Programing,面向对象编程)的补充和完善. OO ...

  6. Spring实现AOP的4种方式

    了解AOP的相关术语:1.通知(Advice):通知定义了切面是什么以及何时使用.描述了切面要完成的工作和何时需要执行这个工作.2.连接点(Joinpoint):程序能够应用通知的一个“时机”,这些“ ...

  7. spring的AOP

    最近公司项目中需要添加一个日志记录功能,就是可以清楚的看到谁在什么时间做了什么事情,因为项目已经运行很长时间,这个最初没有开来进来,所以就用spring的面向切面编程来实现这个功能.在做的时候对spr ...

  8. Spring 实践 -AOP

    Spring 实践 标签: Java与设计模式 AOP引介 AOP(Aspect Oriented Programing)面向切面编程采用横向抽取机制,以取代传统的纵向继承体系的重复性代码(如性能监控 ...

  9. Spring实现AOP的4种方式(转)

    转自:http://blog.csdn.net/udbnny/article/details/5870076 Spring实现AOP的4种方式 先了解AOP的相关术语:1.通知(Advice):通知定 ...

  10. spring(二) AOP之AspectJ框架的使用

    前面讲解了spring的特性之一,IOC(控制反转),因为有了IOC,所以我们都不需要自己new对象了,想要什么,spring就给什么.而今天要学习spring的第二个重点,AOP.一篇讲解不完,所以 ...

随机推荐

  1. 将IMAGE转为PDF后上传

    using iTextSharp.text; using iTextSharp.text.pdf; /// <summary> /// 将IMAGE转为PDF后上传 /// </su ...

  2. js参数自定义

    function test(){ //利用对象自定义参数名称 var t = {P1:"a",P2:"b"} //返回的数据 var ttt = {a:&quo ...

  3. sqlserver中判断是数字(会自动将.3识别为0.3)

    SQL Server 检测是不是数字型的数据(两种方法) 检测是不是数字型的数据, 两种方法 1. ISNUMERIC ( expression ) 2. PATINDEX ( '%pattern%' ...

  4. dcloud_base连接失败(root:admin123!@#qwe@tcp(192.168.8.205:3306)/dcloud_base) Error 1129: Host '192.168.8.205' is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts'

    mysql -uroot -p admin123!@#qwe show global variables like '%max_connect_errors%'; set global max_con ...

  5. 接口测试(http 和 rpc)

    接口测试主要分HTTP和RPC两类,RPC类型里面以Dubbo较为知名.互联网微服务架构,两种接口都需要做接口测试的,不管是业务测试还是回归测试: Dubbo:Java栈的互联网公司比如阿里.美团.5 ...

  6. LeetCode 第四题 Median of Two Sorted Arrays 二人 渣渣选手乱七八糟分析发现基本回到思路1

    题目 There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of the ...

  7. MATLAB用“fitgmdist”函数拟合高斯混合模型(一维数据)

    MATLAB用“fitgmdist”函数拟合高斯混合模型(一维数据) 作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/ 在MATLAB中“fitgmdis ...

  8. SpringBoot2.x打包成war(看这篇就够了)

    springboot默认打包成jar,如果想打包成war,则需要做以下三步. 1.修改pom.xml文件 a.将jar改成war <groupId>com.test</groupId ...

  9. 获取properties文件的内容

    获取properties文件的内容 public void test() throws Exception{ String resource = "application.propertie ...

  10. Monkey and Banana HDU - 1069 有点像背包,又像最长上升序列

    #include<iostream> #include<algorithm> #include<cstring> #include<vector> us ...