学习Spring Boot前需要了解的Spring基础知识
Spring
核心概念
DI: dependency injection
AOP: aspect oriented programming
container: 负责对象的生命周期,从new到finalize
有两种container:
- BeanFactory
- ApplicationContext基于BeanFactory
ApplicationContext主要有FileSystemXmlApplicationContext和ClassPathXmlApplicationContext这两个是从xml配置文件中加载,还有从Java配置中加载的使用AnnotationConfigApplicationContext
bean的生命周期
- 实例化
- 填充属性
- 调用BeanNameAware的setBeanName()方法
- 调用BeanFactoryAware的setBeanFactory()方法
- 调用ApplicationContextAware的setApplicationContext()方法
- 调用BeanPostProcessor的预初始化方法
- 调用InitializingBean的afterPropertiesSet()方法
- 调用自定义的初始化方法
- 调用BeanPostProcessor的初始化后方法
bean可以使用了
容器关闭
- 调用DisposableBean的destroy()方法
- 调用自定义的销毁方法
Spring模块
核心容器
最核心部分,包含Beans, Core, Context, Expression, ContextSupport
AOP模块
包含AOP和Aspects
数据访问与集成模块
Web与远程调用
Instrumentation
提供了为JVM添加代理的功能,为Tomcat提供了一个织入代理,能够为Tomcat传递类文件
测试
装配Bean
三种装配方式
- 在xml中进行显示配置
- 在Java中进行显示配置
- 隐式的bean发现机制和自动装配
自动装配
注解的形式
关键的注解:@Autowired, @ComponentScan
Spring从两个角度来实现自动化装配:
- 组件扫描(component scanning):自动发现应用上下文创建的bean
- 自动装配(autowiring):Spring自动满足bean之间的依赖
package soudsystem;
public interface CompactDisc {void play();
}
package soundsystem;
import org.springframework.stereotype.Component;@Component
public class SgtPeppers implements CompactDisc {private String title = "abc";private String artist = "the beatles";public void play() {System.out.println(title + artist);}
}
package soundsystem;
import org.springframework.context.annotation.componentScan;
import org.springframework.context.annotation.Configuration;@Configuration
@ComponentScan
public class CDPlayerConfig {}
这是用Java配置类来开启组件扫描,还有一种用XML的方式开启组件扫描
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns=""
<!-- 引入context的命名空间xmlns,可以在官网上找到-->
>
<context:component-scan base-package="soudsystem"/>
</beans>
接着就是编写测试类
package soundsystem;
/**
*这里有一系列的包导入
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(class=CDPlayerConfig.class)
public class CDPlayerTest {@Autowiredprivate CompactDisc cd;@Testpublic void cdShouldNotBeNull() {assertNotNull(cd);}
}
bean的命名
Spring会自动生成bean的ID,要想自定义ID只需要在@Component(“name”) 括号中填写ID就行了
设置组件扫描的基础包
@ComponentScan(basePackages=“soundsystem”)
@ComponentScan(basePackages={"soundsystem", "video"})
@ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class})
为bean添加注解实现自动装配
@Autowired
@Autowired不仅可以用在属性上,也可以用在方法上,对方法的参数实现自动装配
Java代码装配
创建配置类
package soundsystem;
import org.springframework.context.annotation.Configuration@Configuration
public class CDPlayerConfig {}
声明Bean
在JavaConfig中声明bean
@Bean
public CompactDisc sgtPeppers() {return new SgtPeppers();
}
bean的ID和方法名一样,也可以通过@Bean(name=“”)来指定方法名
实现注入
装配Bean最简单的方式就是引用创建bean的方法
@Bean
public CDPlayer cdPlayer() {return new CDPlayer(sgtPeppers());
}
因为sgtPeppers()方法上添加了@Bean注解,Spring会拦截所有对它的调用,并确保直接返回该方法所创建的bean,而不是每次都对其进行实际的调用。
因为默认情况下Spring中的bean都是单例的。
还有另外一种引用bean的方式
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {return new CDPlayer(compactDisc);
}
通过XML装配bean
使用XML配置要在配置文件顶部声明多个XML模式(XSD)文件。
可以借助Spring Tool Suite来生成,IDEA也可以自动生成。
声明一个简单的bean
<bean id="compactDisc" class="soundsystem.SgtPeppers"/>
两个缺点:
- bean的创建被动,功能不够强大
- 不能进行编译期的类型检查
借助构造器注入初始化bean
使用元素
<bean id="cdPlayer" class="soundsystem.CDPlayer"><constructor-arg ref="compactDisc"/>
</bean>
以字面量的形式注入到构造器
<bean id="cdPlayer" class="soundsystem.CDPlayer"><constructor-arg value="compactDisc"/>
</bean>
注入的是列表,集合,数组
<constructor-arg><set><value></value></set>
</constructor-arg>
<constructor-arg><list><value></value></list>
</constructor-arg>
c-命名空间
<bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc"/>
c-: 是命名空间的前缀 cd: 构造器参数名 -ref: 注入bean引用
构造器参数名也可以换成参数的索引,因为XML不支持第一个字为数字,要加一个_,比如第一个参数用_0
以字面量的形式注入到构造器
<bean id="cdPlayer" class="soundsystem.CDPlayer" c:_title="compactDisc"/>
属性注入
类中 setter方法
<bean id="cdPlayer" class="soundsystem.CDPlayer"><property name="compactDisc" ref="compactDisc"/>
</bean>
p命名空间
首先要在头对其进行声明,然后使用
<bean id="cdPlayer" class="soundsystem.CDPlayer"
p:compactDisc-ref="compactDisc"/>
命名规则类比c命名空间
util命名空间
可以协助我们进行list,map,set的注入
<util:list id="trackList"><value></value><value></value>
</util:list>
Spring util-命名空间中的元素
|元素|描述|
|—|—|
|util:constant|引用某个类型的public static域|
|util:list|java.util.List|
|util:map|java.util.Map|
|util:properties|java.util.Properties|
|util:property-path|引用一个bean的属性|
|util:set|java.util.Set|
导入和混合配置
在JavaConfig中引用XML配置
如果引入另一个Java配置类使用@Import(CDConfig.class)
引入配置在XML之中的Bean用@ImportResource注解
@ImportResource(“classpath:cd-config.xml”)
在XML配置中引用JavaConfig
引用另一个XML
<import resource="cdplayer-config.xml"/>
引用JavaConfig
<bean class="soundsystem.CDConfig"/>
高级装配
开发和上线部署环镜会需要不同的bean。通过profile可以给不同环境配置不同的bean
配置profile bean
Java配置
使用@Profile注解
@Configuration
@Profile("dev")
public class DevelopmentProfileConfig {}
Spring3.2以后可以在方法上使用@Profile
在XML配置
<beans profile=“dev”> </beans>
激活profile
Spring确定激活哪个profile依赖两个属性:
- spring.profiles.active
- spring.profiles.default
可以通过Web应用的context来设置这两个属性
<context-param><param-name>spring.profiles.default</param-name><param-value>dev</param-value>
</context-param>
通过Servlet的初始化参数
<servlet><servlet-name>appServlet</servlet-name><servlet-class></servlet-class><init-param><param-name>spring.profiles.default</param-name><param-value>dev</param-value></init-param>
</servlet>
要在测试类设置通过@ActiveProfiles(“dev”)
条件化bean
我们需要在某些条件下生成Bean,在某些条件下不生成
使用@Conditional注解,给定的条件计算结果为true就会创建bean,否则就不会创建
@Bean
@Conditional(MagicExistsCondition.class)
public MagicBean magicBean() {return new MagicBean();
}
@Contional是通过Condition接口进行的条件对比:
public interface Condition {boolean matches(ConditionContext ctxt, AnnotatedTypeMedtadata metadata);
}
我们需要通过实现这个接口来完成具体的条件计算:
public class MagicExistsCondition implements Condition {public boolean matches(ConditionContext context, AnnotatedTypeMedtadata metadata) {Environment env = context.getEnvironment();return env.containsProperty("magic"); }
}
ConditionContext也是一个接口,源码:
public interface ConditonContext {/***检查Bean定义*/BeanDefinitionRegistry getRegistry();/***检查bean是否存在,甚至探查bean的属性*/ConfigurableListableBeanFactory getBeanFactory();/***检查环境变量是否存在以及它的值是什么*/Environment getEnvironment();/***所加载的资源*/ResourceLoader getResourceLoader();/***返回类加载器,并检查类是否存在*/ClassLoader getClassLoader();
}
AnnotatedTypeMetadata用来检查@Bean方法上还有什么其他注解
public interface AnnotatedTypeMetadata {boolean isAnnotated(String annotationType);Map<String, Object> getAnnotationAttributes(String annotationType);Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString);MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType);MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString);
}
从Spring4开始, @Profile基于@Conditional和@Condition实现
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {String[] value();
}
ProfileCondition
class ProfileCondition implements Condition {public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {if (context.getEnvironment() != null) {MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());if (attrs != null) {for (Object value : attrs.get("value")) {if (context.getEnvironment()
.acceptsProfiles(((String[]) value))) {return true;}}return false;}}return true;}
}
处理自动装配的歧义性
歧义性发生情况:一个接口有多个实现类,同时多个实现类都是bean,自动注入接口的bean时便无法判断注入哪个bean
标示首选bean
- @Primary和@Component|@Bean组合使用
<bean id=“iceCream” class=“com.desserteater.IceCream” primary=“true”/>
限定自动装配的bean
@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) {this.dessert = dessert;
}
“iceCream”是bean的ID,我们可以自定义限定符
@Component
@Qualifier("cold")
public class IceCream implements Dessert {}
当我们多个bean都想要用这个限定符,使用时也会发生歧义,所以我们必须要多加个@Qualifier,但是编译器不允许添加多个@Qualifier,可以通过自定义注解来完成。
@Target({ElementType.CONSTRUCTOR, ELementType.FIELD,ElementType.METHOD, ELementType.TYPE})
@Rentention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {}
bean的作用域
默认的bean是单例的,但是对于易变类型单例并不合适
Spring定义了多种作用域
- Singleton
- Prototype
- Session
- Request
对于使用组件扫描来发现和声明bean,可以使用@Scope注解
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad {}
使用Java配置
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public NotePad notepad() {return new Notepad();
}
使用xml
<bean id="notepad" class="com.myapp.Notepad" scope="prototype"/>
使用会话和请求作用域
以电子商务作为例子,在电子商务中购物车往往都不是单例的,而购物网站服务往往是单例的。
购物车代码:
@Component
@Scope(value=WebApplicationContext.SCOPE_SESSION,proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart() {}
proxyMode这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题。
网站服务代码:
@Component
public class StoreService {@Autowiredpublic void setShoppingCart(ShoppingCart shoppingCart) {this.shoppingCart = shoppingCart;}
}
StoreService是个单例的bean,单例的bean会在Spring应用上下文加载的时候创建。但是当它创建时ShoppingCart bean并不存在,直到有对话创建才会存在。而且系统中会有多个ShoppingCart实例,无法注入某个固定的ShoppingCart实例到StoreService中。
Spring注入的实际上是一个到ShoppingCart bean的代理,代理暴露了接口的所有方法
[image:25F34FB8-8997-48E3-8AE4-4593E4B9F12A-743-000008F9ED04D19F/78D6077F-5506-41F6-BA48-1BBDF924A240.png]
proxyMode属性被设置成了ScopedProxyMode.INTERFACES,这表明这个代理要实现ShoppingCart接口。如果要生成基于类的代理要使用CGLib,proxyMode属性设置为ScopedProxyMode.TARGET_CLASS
在XML中声明作用域代理
<bean id="cart" class="com.myapp.ShoppingCart" scope="session">
<aop:scoped-proxy/>
默认是类的代理,实现接口的代理将proxy-target-class属性设置为false,注意使用前同样要声明aop命名空间。
运行时值的注入
注入外部的值
声明属性源并通过Spring的Environment来检索属性
@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")
public class ExpressiveConfig {@AutowiredEnvironment env;@Beanpublic BlankDisc disc() {return new BlankDisc(env.getProperty("disc.title"),env.getProperty("disc.artist"));}
}
app.properties:
disc.title=abc
disc.artist=abc
使用属性占位符
${}
xml
<bean id="sgtPeppers" class="soundsystem.BlankDisc" c:_title="${disc.title}" c:_artist="${disc.artist}"/>
组件扫描和自动装配
public BlankDisc (@Value("${disc.title}") String title, @Value("${disc.artist}") String artist) {this.title = title;this.artist = artist;
}
要使用占位符还需要配置PropertyPlaceholderConfigurer bean或PropertySourcePlaceholderConfigurer bean
@Bean
public static PropertySourcePlaceholderConfigurer placeholderConfigurer() {return new PropertySourcePlaceholderConfigurer();
}
or
<context:property-placeholder/>
使用Spring表达式语言(SpEL)进行装配
#{}
SpEL
表示字面值
#{3.1415}
#{'Hello'}
#{false}
通过ID引用其他的bean
#{sgtPeppers}
引用bean的属性或方法
#{sgtPeppers.artist}
#{sgtPeppers.selectArtist()}
#{sgtPeppers.selectArtist()?.toUpperCase()}
T()运算符,T()运算符的结果会是一个Class对象
T(java.lang.Math)
ternary && Elvis
#{scoredboard.score > 1000 ? "Winner!" : "Losser!"}
#{disc.title ?: "Rattle"}
regular expression
#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}
查询运算符
#{jukebox.songs.?[artist eq 'qbc']}
#{jukebox.songs.^[artist eq 'qbc']}
#{jukebox.songs.$[artist eq 'qbc']}
投影运算符
#{jukebox.songs.![title]}
Spring的Environment
Environment提供了四种获得属性的方式
String getProperty(String key)
String getProperty(String key, String defaultValue)
T getProperty(String key, Class<T> type)
T getProperty(String key, Class<T> type, T defaultValue)
获得的属性必须要定义
String getRequiredProperty(String key)
检查某个属性是否存在
boolean containsProperty(String key)
将属性解析为类
Class<CompactDisc> cdClass = env.getPropertyasClass("disc.class", CompactDisc.class);
检查哪些profile处于激活状态
String[] getActiveProfiles()
String[] getDefaultProfiles()
boolean acceptsProfiles(String... profiles)
面向切面的Spring
散布于应用中多处的功能被称为横切关注点(cross-cutting concern),这些横切关注点是与应用业务逻辑相分离的。横切关注点可以模块化为类,这些类被称为切面
AOP术语
[image:7077B788-D72D-4014-830C-9E0D950712BC-743-000021664EE25806/A322F226-A662-46B7-933A-F270E9778123.png]
- Advice: 切面的工作。通知定义了切面是什么以及何时使用。5种通知类型:前置通知,后置通知,返回通知,异常通知,环绕通知
- Join point: 连接点是应用执行过程中能够插入切面的一个点
- Point cut: 切点的定义会匹配通知所要织入的一个或多个连接点
- Aspect: 切面是通知和切点的结合
- Introduction: 引入允许我们向现有的类添加新方法和属性
- Weaving: 织入是把切面应用到目标对象并创建新的代理对象的过程。织入时期有:编译期,类加载期,运行期
通过切点来选择连接点
编写切点
定义一个Performance接口
package concert;
public interface Performance {public void perform();
}
使用AspectJ切点表达式来选择
execution(* concert.Performance.perform(..))
让切点仅匹配concert包
execution(* concert.Performance.perform(..)) && within(concert.*)
在切点中选择bean
execution(* concert.Performance.perform(..)) and bean('woodstock')
使用注解创建切面
定义切面
package concert
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;@Aspect
public class Audience {@Before("execution(** concert.Performance.perform(..))")public void silenceCellPhones() {}@AfterReturning("execution(** concert.Performance.perform(..))")public void applause() {}@AfterThrowing("execution(** concert.Performance.perform(..))")public void demandRefund() {}
}
上面重复写了相同的切点表达式,我们可以通过@Pointcut定义可重用切点
package concert
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;@Aspect
public class Audience {@Pointcut("execution(** conert.Performance.perform(..))")public void performance() {}@Before("performance()")public void silenceCellPhones() {}@AfterReturning("performance()")public void applause() {}@AfterThrowing("performance()")public void demandRefund() {}
}
在JavaConfig启用自动代理
package concert;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class ConcertConfig {@Beanpublic Audience audience() {return new Audience();}
}
在XML中启用自动代理
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="..."><context:component-scan base-package="concert"/><aop:aspectj-autoproxy/><bean class="concert.Audience"/>
</beans>
创建环绕通知
环绕通知=前置通知+后置通知
package concert;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;@Aspect
public class Audience {@Pointcut("execution(** concert.Performance.perform(..))")public void performance();@Around("performance()")public void watchPerformance(ProceedingJoinPoint jp) {try {System.out.println("before");//通过它来调用被通知的方法jp.proceed();System.out.println("after");} catch (Throwable e) {System.out.println("exception");}}
}
处理通知中的参数
使用args()的AspectJ切点表达式
@Pointcut("execution(* soundsystem.CompactDisc.playTrack(int)) && args(trackNumber)")
public void trackPlayed(int trackNumber) {}
通过注解引入新功能
[image:40F5713D-BAEB-422E-BB68-DCDFB5424506-743-0000367A8E10EAB8/ECD87204-224B-470F-914E-E9459C8DAFC4.png]
假设某些类要实现下面的接口
package concert;
public interface Encoreable {void performEncore();
}
新建一个切面
package concert;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;@Aspect
public class EncoreableIntroducer {@DeclareParents(value="concert.Performance+", defaultImpl=DefaultEncoreable.class)public static Encoreable encoreable;
}
- value属性指定了哪种类型的bean要引入该接口
- defaultImpl属性指定了为引入功能提供的类
- @DeclareParents注解所标注的静态属性指明了要引入的接口
我们同样要把EncoreableIntroducer声明为一个bean。
<bean class="concert.EncoreableIntroducer"/>
在XML中声明切面
好处:能够以非入侵的方式声明切面
声明前置和后置通知
Java类是上面用的Audience
通过XML将无注解的Audience声明为切面
<aop:config><aop:aspect ref="audience"><aop:before pointcut="execution(** concert.Performance.perform(..))" method="silenceCellPhones"/><aop:after-returnning pointcut="execution(** concert.Performance.perform(..))" method="applause"/><aop:after-throwing pointcut="execution(** concert.Performance.perform(..))" method="demandRefund"/></aop:aspect>
</aop:config>
同样有aop:pointcut来声明重复的切点
<aop:config><aop:aspect ref="audience"><aop:pointcut id="performance" expression="execution(** concert.Performance.perform(..))"/><aop:before pointcut-ref="performance" method="silenceCellPhones"/><aop:after-returnning pointcut-ref="performance" method="applause"/><aop:after-throwing pointcut-ref="performance" method="demandRefund"/></aop:aspect>
</aop:config>
声明环绕通知
用<aop:around poincut-ref=“” method=“”/>
为通知传递参数
和bean的装配一样
通过切面引入新的功能
<aop:aspect><aop:declare-parents types-matching="concert.Performance+" implement-interface="concert.Encoreable" default-impl="concert.DefaultEncoreable"/>
</aop:aspect>
types-matching指定类型匹配接口 implement-interface增加Encoreable接口。
Encoreable接口的方法指定通过default-impl或者delegate-ref来指定,delegate-ref引用的是一个bean的ID。
注入AspectJ切面
使用AspectJ实现表演的评论员
package concert;public aspect CriticAspect {public CriticAspect() {}pointcut performance() : execution(* perform(..));afterReturning(): performance() {System.out.println(criticismEngine.getCriticism());}private CriticismEngine criticismEngine;public void setCriticismEngine(CriticsmEngine criticismEngine) {this.criticismEngine = criticismEngine;}
}
在XML中声明这个aspect
<bean class="com.springinaction.springidol.CriticAspect" factory-method="aspectOf"><property name="criticismEngine" ref="criticismEngine"/>
</bean>
学习Spring Boot前需要了解的Spring基础知识相关推荐
- spring boot 前后端分离项目(商城项目)学习笔记
spring boot 前后端分离项目(商城项目)学习笔记 目录 spring boot 前后端分离项目(商城项目)学习笔记 后端配置 springboot项目 pom.xml文件 maven 配置文 ...
- sm4 前后端 加密_这7个开源的Spring Boot前后端分离项目整理给你
来源|公众号:江南一点雨 前后端分离已经开始逐渐走进各公司的技术栈,不少公司都已经切换到前后端分离开发技术栈上面了,因此建议技术人学习前后端分离开发以提升自身优势.同时,也整理了 7 个开源的 Spr ...
- 《Vue+Spring Boot前后端分离开发实战》专著累计发行上万册
杰哥的学术专著<Vue+Spring Boot前后端分离开发实战>由清华大学出版社于2021年3月首次出版发行,虽受疫情影响但热度不减,受到业界读者的热捧,截至今日 ...
- Vue+Spring boot前后端响应流程总结
Vue+Spring boot前后端响应流程总结 前端请求页面路径,首先会经过路由: 经过解决跨域问题以后,就会请求到后端接口,后端接口返回的数据会封装到then回调方法的res参数中. 经过回调函数 ...
- Spring Boot前后端分离项目Session问题解决
Spring Boot前后端分离项目Session问题解决 参考文章: (1)Spring Boot前后端分离项目Session问题解决 (2)https://www.cnblogs.com/sooo ...
- Spring Boot前后端分离之后端开发
Spring Boot前后端分离开发之后端开发 前后端分离开发概述 相关术语 前后端分离开发概述 接口规范 RESTful API的理解 RESTful风格的特点 URI规范 路径 请求方式 过滤条件 ...
- Spring Boot 2.0(三):Spring Boot 开源软件都有哪些?
2016年 Spring Boot 还没有被广泛使用,在网上查找相关开源软件的时候没有发现几个,到了现在经过2年的发展,很多互联网公司已经将 Spring Boot 搬上了生产,而使用 Spring ...
- 使用 Spring Boot CLI 运行第一个Spring boot程序
简介 Spring Boot CLI是Spring Boot的命令行界面.它可以用来快速启动Spring. 它可以运行Groovy脚本. Spring Boot CLI是创建基于Spring的应用 ...
- spring boot jar包_「Spring Boot 新特性」 jar 大小自动瘦身
自动分析瘦身 Spring Boot 项目最终构建处理 JAR 包大小一直是个诟病,需要把所有依赖包内置最终输出可运行的 jar.当然可以使用其他的插件扩展 实现依赖 JAR 和 可运行 jar 分离 ...
最新文章
- 《OpenCV3编程入门》学习笔记5 Core组件进阶(四)图像对比度、亮度值调整
- [inside]MySQL 5.7 并行复制实现原理与调优
- Error was tenMinuteCache Cache: The Disk store is not active.
- QT的QTextLayout类的使用
- C++ inline
- win mysql 2003错误_windows MySql 报1067错误 2003错误
- vue-cli2定制ant-design-vue主题
- FastDFS 入门简介
- ES6 学习笔记 (1)
- 三种坐标系经纬度转化小工具
- sar图像matlab,用Matlab制作SAR仿真图像
- 在js中调用dede标签
- 神经网络及其变种串联
- uni-app云开发的网盘助手微信小程序源码抓取网盘资源引流好助手
- Opencv3.2移植到arm板
- 计算机显示器的ppt,计算机硬之显示器.ppt
- 语音朗读html的源码,在网页上通过JS实现文本的语音朗读
- WebRequest 类和 WebResponse 类
- C语言数据结构一元多项式
- win 2016 ssh_【Win】Print Conductor 全能批量打印工具兼容所有打印机