springboot知识

2024-05-12 07:51:54

spring boot quick start

在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar/war是可以直接启动的,不需要另外配置一个Web Server。

如果之前没有使用过spring boot可以通过下面的demo来感受下。 
下面以这个工程为例,演示如何启动Spring boot项目:

git clone git@github.com:hengyunabc/spring-boot-demo.git
mvn spring-boot-demo
java -jar target/demo-0.0.1-SNAPSHOT.jar123

如果使用的IDE是spring sts或者idea,可以通过向导来创建spring boot项目。

也可以参考官方教程: 
http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#getting-started-first-application

对spring boot的两个疑问

刚开始接触spring boot时,通常会有这些疑问

  • spring boot如何启动的?

  • spring boot embed tomcat是如何工作的? 静态文件,jsp,网页模板这些是如何加载到的?

下面来分析spring boot是如何做到的。

打包为单个jar时,spring boot的启动方式

maven打包之后,会生成两个jar文件:

demo-0.0.1-SNAPSHOT.jardemo-0.0.1-SNAPSHOT.jar.original12

其中demo-0.0.1-SNAPSHOT.jar.original是默认的maven-jar-plugin生成的包。

demo-0.0.1-SNAPSHOT.jar是spring boot maven插件生成的jar包,里面包含了应用的依赖,以及spring boot相关的类。下面称之为fat jar。

先来查看spring boot打好的包的目录结构(不重要的省略掉):

├── META-INF
│   ├── MANIFEST.MF
├── application.properties
├── com
│   └── example
│       └── SpringBootDemoApplication.class
├── lib
│   ├── aopalliance-1.0.jar
│   ├── spring-beans-4.2.3.RELEASE.jar
│   ├── ...└── org└── springframework└── boot└── loader├── ExecutableArchiveLauncher.class├── JarLauncher.class├── JavaAgentDetector.class├── LaunchedURLClassLoader.class├── Launcher.class├── MainMethodRunner.class├── ...                123456789101112131415161718192021

依次来看下这些内容。

MANIFEST.MF

Manifest-Version: 1.0Start-Class: com.example.SpringBootDemoApplicationImplementation-Vendor-Id: com.exampleSpring-Boot-Version: 1.3.0.RELEASECreated-By: Apache Maven 3.3.3Build-Jdk: 1.8.0_60Implementation-Vendor: Pivotal Software, Inc.Main-Class: org.springframework.boot.loader.JarLauncher12345678

可以看到有Main-Class是org.springframework.boot.loader.JarLauncher ,这个是jar启动的Main函数。

还有一个Start-Class是com.example.SpringBootDemoApplication,这个是我们应用自己的Main函数。

@SpringBootApplicationpublic class SpringBootDemoApplication {public static void main(String[] args) {SpringApplication.run(SpringBootDemoApplication.class, args);}
}1234567

com/example 目录

这下面放的是应用的.class文件。

lib目录

这里存放的是应用的Maven依赖的jar包文件。 
比如spring-beans,spring-mvc等jar。

org/springframework/boot/loader 目录

这下面存放的是Spring boot loader的.class文件。

Archive的概念

  • archive即归档文件,这个概念在linux下比较常见

  • 通常就是一个tar/zip格式的压缩包

  • jar是zip格式

在spring boot里,抽象出了Archive的概念。

一个archive可以是一个jar(JarFileArchive),也可以是一个文件目录(ExplodedArchive)。可以理解为Spring boot抽象出来的统一访问资源的层。

上面的demo-0.0.1-SNAPSHOT.jar 是一个Archive,然后demo-0.0.1-SNAPSHOT.jar里的/lib目录下面的每一个Jar包,也是一个Archive。

public abstract class Archive {public abstract URL getUrl();    public String getMainClass();    public abstract Collection<Entry> getEntries();    public abstract List<Archive> getNestedArchives(EntryFilter filter);12345

可以看到Archive有一个自己的URL,比如:

jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/1

还有一个getNestedArchives函数,这个实际返回的是demo-0.0.1-SNAPSHOT.jar/lib下面的jar的Archive列表。它们的URL是:

jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/lib/aopalliance-1.0.jarjar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/lib/spring-beans-4.2.3.RELEASE.jar12

JarLauncher

从MANIFEST.MF可以看到Main函数是JarLauncher,下面来分析它的工作流程。

JarLauncher类的继承结构是:

class JarLauncher extends ExecutableArchiveLauncher
class ExecutableArchiveLauncher extends Launcher12

以demo-0.0.1-SNAPSHOT.jar创建一个Archive:

JarLauncher先找到自己所在的jar,即demo-0.0.1-SNAPSHOT.jar的路径,然后创建了一个Archive。

下面的代码展示了如何从一个类找到它的加载的位置的技巧:

    protected final Archive createArchive() throws Exception {ProtectionDomain protectionDomain = getClass().getProtectionDomain();CodeSource codeSource = protectionDomain.getCodeSource();URI location = (codeSource == null ? null : codeSource.getLocation().toURI());String path = (location == null ? null : location.getSchemeSpecificPart());        if (path == null) {            throw new IllegalStateException("Unable to determine code source archive");}File root = new File(path);        if (!root.exists()) {            throw new IllegalStateException(                    "Unable to determine code source archive from " + root);}        return (root.isDirectory() ? new ExplodedArchive(root): new JarFileArchive(root));}12345678910111213141516

获取lib/下面的jar,并创建一个LaunchedURLClassLoader

JarLauncher创建好Archive之后,通过getNestedArchives函数来获取到demo-0.0.1-SNAPSHOT.jar/lib下面的所有jar文件,并创建为List。

注意上面提到,Archive都是有自己的URL的。

获取到这些Archive的URL之后,也就获得了一个URL[]数组,用这个来构造一个自定义的ClassLoader:LaunchedURLClassLoader。

创建好ClassLoader之后,再从MANIFEST.MF里读取到Start-Class,即com.example.SpringBootDemoApplication,然后创建一个新的线程来启动应用的Main函数。

    /*** Launch the application given the archive file and a fully configured classloader.*/protected void launch(String[] args, String mainClass, ClassLoader classLoader)            throws Exception {Runnable runner = createMainMethodRunner(mainClass, args, classLoader);Thread runnerThread = new Thread(runner);runnerThread.setContextClassLoader(classLoader);runnerThread.setName(Thread.currentThread().getName());runnerThread.start();}    /*** Create the {@code MainMethodRunner} used to launch the application.*/protected Runnable createMainMethodRunner(String mainClass, String[] args,ClassLoader classLoader) throws Exception {Class<?> runnerClass = classLoader.loadClass(RUNNER_CLASS);Constructor<?> constructor = runnerClass.getConstructor(String.class,String[].class);        return (Runnable) constructor.newInstance(mainClass, args);}12345678910111213141516171819202122

LaunchedURLClassLoader

LaunchedURLClassLoader和普通的URLClassLoader的不同之处是,它提供了从Archive里加载.class的能力。

结合Archive提供的getEntries函数,就可以获取到Archive里的Resource。当然里面的细节还是很多的,下面再描述。

spring boot应用启动流程总结

看到这里,可以总结下Spring Boot应用的启动流程:

  1. spring boot应用打包之后,生成一个fat jar,里面包含了应用依赖的jar包,还有Spring boot loader相关的类

  2. Fat jar的启动Main函数是JarLauncher,它负责创建一个LaunchedURLClassLoader来加载/lib下面的jar,并以一个新线程启动应用的Main函数。

spring boot loader里的细节

代码地址:https://github.com/spring-projects/spring-boot/tree/master/spring-boot-tools/spring-boot-loader

JarFile URL的扩展

Spring boot能做到以一个fat jar来启动,最重要的一点是它实现了jar in jar的加载方式。

JDK原始的JarFile URL的定义可以参考这里:

http://docs.oracle.com/javase/7/docs/api/java/net/JarURLConnection.html

原始的JarFile URL是这样子的:

jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/1

jar包里的资源的URL:

jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/com/example/SpringBootDemoApplication.class1

可以看到对于Jar里的资源,定义以’!/’来分隔。原始的JarFile URL只支持一个’!/’。

Spring boot扩展了这个协议,让它支持多个’!/’,就可以表示jar in jar,jar in directory的资源了。

比如下面的URL表示demo-0.0.1-SNAPSHOT.jar这个jar里lib目录下面的spring-beans-4.2.3.RELEASE.jar里面的MANIFEST.MF:

jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/lib/spring-beans-4.2.3.RELEASE.jar!/META-INF/MANIFEST.MF1

自定义URLStreamHandler,扩展JarFile和JarURLConnection

在构造一个URL时,可以传递一个Handler,而JDK自带有默认的Handler类,应用可以自己注册Handler来处理自定义的URL。

public URL(String protocol,String host,           int port,String file,URLStreamHandler handler)    throws MalformedURLException123456

参考: 
https://docs.oracle.com/javase/8/docs/api/java/net/URL.html#URL-java.lang.String-java.lang.String-int-java.lang.String-

Spring boot通过注册了一个自定义的Handler类来处理多重jar in jar的逻辑。

这个Handler内部会用SoftReference来缓存所有打开过的JarFile。

在处理像下面这样的URL时,会循环处理’!/’分隔符,从最上层出发,先构造出demo-0.0.1-SNAPSHOT.jar这个JarFile,再构造出spring-beans-4.2.3.RELEASE.jar这个JarFile,然后再构造出指向MANIFEST.MF的JarURLConnection。

jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/lib/spring-beans-4.2.3.RELEASE.jar!/META-INF/MANIFEST.MF1
//org.springframework.boot.loader.jar.Handlerpublic class Handler extends URLStreamHandler {private static final String SEPARATOR = "!/";    private static SoftReference<Map<File, JarFile>> rootFileCache;    @Overrideprotected URLConnection openConnection(URL url) throws IOException {        if (this.jarFile != null) {            return new JarURLConnection(url, this.jarFile);}        try {            return new JarURLConnection(url, getRootJarFileFromUrl(url));}        catch (Exception ex) {            return openFallbackConnection(url, ex);}}    public JarFile getRootJarFileFromUrl(URL url) throws IOException {String spec = url.getFile();        int separatorIndex = spec.indexOf(SEPARATOR);        if (separatorIndex == -1) {            throw new MalformedURLException("Jar URL does not contain !/ separator");}String name = spec.substring(0, separatorIndex);        return getRootJarFile(name);}12345678910111213141516171819202122232425

ClassLoader如何读取到Resource

对于一个ClassLoader,它需要哪些能力?

  • 查找资源

  • 读取资源

对应的API是:

public URL findResource(String name)public InputStream getResourceAsStream(String name)12

上面提到,Spring boot构造LaunchedURLClassLoader时,传递了一个URL[]数组。数组里是lib目录下面的jar的URL。

对于一个URL,JDK或者ClassLoader如何知道怎么读取到里面的内容的?

实际上流程是这样子的:

  • LaunchedURLClassLoader.loadClass

  • URL.getContent()

  • URL.openConnection()

  • Handler.openConnection(URL)

最终调用的是JarURLConnection的getInputStream()函数。

//org.springframework.boot.loader.jar.JarURLConnection@Overridepublic InputStream getInputStream() throws IOException {connect();        if (this.jarEntryName.isEmpty()) {            throw new IOException("no entry name specified");}        return this.jarEntryData.getInputStream();}123456789

从一个URL,到最终读取到URL里的内容,整个过程是比较复杂的,总结下:

  • spring boot注册了一个Handler来处理”jar:”这种协议的URL

  • spring boot扩展了JarFile和JarURLConnection,内部处理jar in jar的情况

  • 在处理多重jar in jar的URL时,spring boot会循环处理,并缓存已经加载到的JarFile

  • 对于多重jar in jar,实际上是解压到了临时目录来处理,可以参考JarFileArchive里的代码

  • 在获取URL的InputStream时,最终获取到的是JarFile里的JarEntryData

这里面的细节很多,只列出比较重要的一些点。

然后,URLClassLoader是如何getResource的呢?

URLClassLoader在构造时,有URL[]数组参数,它内部会用这个数组来构造一个URLClassPath:

URLClassPath ucp = new URLClassPath(urls);1

在 URLClassPath 内部会为这些URLS 都构造一个Loader,然后在getResource时,会从这些Loader里一个个去尝试获取。 
如果获取成功的话,就像下面那样包装为一个Resource。

Resource getResource(final String name, boolean check) {    final URL url;    try {url = new URL(base, ParseUtil.encodePath(name, false));} catch (MalformedURLException e) {        throw new IllegalArgumentException("name");}    final URLConnection uc;    try {        if (check) {URLClassPath.check(url);}uc = url.openConnection();InputStream in = uc.getInputStream();        if (uc instanceof JarURLConnection) {            /* Need to remember the jar file so it can be closed* in a hurry.*/JarURLConnection juc = (JarURLConnection)uc;jarfile = JarLoader.checkJar(juc.getJarFile());}} catch (Exception e) {        return null;}    return new Resource() {        public String getName() { return name; }        public URL getURL() { return url; }        public URL getCodeSourceURL() { return base; }        public InputStream getInputStream() throws IOException {            return uc.getInputStream();}        public int getContentLength() throws IOException {            return uc.getContentLength();}};
}123456789101112131415161718192021222324252627282930313233343536

从代码里可以看到,实际上是调用了url.openConnection()。这样完整的链条就可以连接起来了。

注意,URLClassPath这个类的代码在JDK里没有自带,在这里看到 http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/sun/misc/URLClassPath.java#506

在IDE/开放目录启动Spring boot应用

在上面只提到在一个fat jar里启动Spring boot应用的过程,下面分析IDE里Spring boot是如何启动的。

在IDE里,直接运行的Main函数是应用自己的Main函数:

@SpringBootApplicationpublic class SpringBootDemoApplication {public static void main(String[] args) {SpringApplication.run(SpringBootDemoApplication.class, args);}
}1234567

其实在IDE里启动Spring boot应用是最简单的一种情况,因为依赖的Jar都让IDE放到classpath里了,所以Spring boot直接启动就完事了。

还有一种情况是在一个开放目录下启动Spring boot启动。所谓的开放目录就是把fat jar解压,然后直接启动应用。

java org.springframework.boot.loader.JarLauncher1

这时,Spring boot会判断当前是否在一个目录里,如果是的,则构造一个ExplodedArchive(前面在jar里时是JarFileArchive),后面的启动流程类似fat jar的。

Embead Tomcat的启动流程

判断是否在web环境

spring boot在启动时,先通过一个简单的查找Servlet类的方式来判断是不是在web环境:

private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",    "org.springframework.web.context.ConfigurableWebApplicationContext" };private boolean deduceWebEnvironment() {    for (String className : WEB_ENVIRONMENT_CLASSES) {        if (!ClassUtils.isPresent(className, null)) {            return false;}}    return true;
}1234567891011

如果是的话,则会创建AnnotationConfigEmbeddedWebApplicationContext,否则Spring context就是AnnotationConfigApplicationContext:

//org.springframework.boot.SpringApplicationprotected ConfigurableApplicationContext createApplicationContext() {Class<?> contextClass = this.applicationContextClass;        if (contextClass == null) {            try {contextClass = Class.forName(this.webEnvironment? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);}            catch (ClassNotFoundException ex) {                throw new IllegalStateException(                        "Unable create a default ApplicationContext, "+ "please specify an ApplicationContextClass",ex);}}        return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);}1234567891011121314151617

获取EmbeddedServletContainerFactory的实现类

spring boot通过获取EmbeddedServletContainerFactory来启动对应的web服务器。

常用的两个实现类是TomcatEmbeddedServletContainerFactory和JettyEmbeddedServletContainerFactory。

启动Tomcat的代码:

//TomcatEmbeddedServletContainerFactory@Overridepublic EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers) {Tomcat tomcat = new Tomcat();File baseDir = (this.baseDirectory != null ? this.baseDirectory: createTempDir("tomcat"));tomcat.setBaseDir(baseDir.getAbsolutePath());Connector connector = new Connector(this.protocol);tomcat.getService().addConnector(connector);customizeConnector(connector);tomcat.setConnector(connector);tomcat.getHost().setAutoDeploy(false);tomcat.getEngine().setBackgroundProcessorDelay(-1);    for (Connector additionalConnector : this.additionalTomcatConnectors) {tomcat.getService().addConnector(additionalConnector);}prepareContext(tomcat.getHost(), initializers);    return getTomcatEmbeddedServletContainer(tomcat);
}1234567891011121314151617181920

会为tomcat创建一个临时文件目录,如: 
/tmp/tomcat.2233614112516545210.8080,做为tomcat的basedir。里面会放tomcat的临时文件,比如work目录。

还会初始化Tomcat的一些Servlet,比如比较重要的default/jsp servlet:

private void addDefaultServlet(Context context) {Wrapper defaultServlet = context.createWrapper();defaultServlet.setName("default");defaultServlet.setServletClass("org.apache.catalina.servlets.DefaultServlet");defaultServlet.addInitParameter("debug", "0");defaultServlet.addInitParameter("listings", "false");defaultServlet.setLoadOnStartup(1);    // Otherwise the default location of a Spring DispatcherServlet cannot be setdefaultServlet.setOverridable(true);context.addChild(defaultServlet);context.addServletMapping("/", "default");
}private void addJspServlet(Context context) {Wrapper jspServlet = context.createWrapper();jspServlet.setName("jsp");jspServlet.setServletClass(getJspServletClassName());jspServlet.addInitParameter("fork", "false");jspServlet.setLoadOnStartup(3);context.addChild(jspServlet);context.addServletMapping("*.jsp", "jsp");context.addServletMapping("*.jspx", "jsp");
}1234567891011121314151617181920212223

spring boot的web应用如何访问Resource

当spring boot应用被打包为一个fat jar时,是如何访问到web resource的?

实际上是通过Archive提供的URL,然后通过Classloader提供的访问classpath resource的能力来实现的。

index.html

比如需要配置一个index.html,这个可以直接放在代码里的src/main/resources/static目录下。

对于index.html欢迎页,spring boot在初始化时,就会创建一个ViewController来处理:

//ResourcePropertiespublic class ResourceProperties implements ResourceLoaderAware {private static final String[] SERVLET_RESOURCE_LOCATIONS = { "/" };    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {            "classpath:/META-INF/resources/", "classpath:/resources/",            "classpath:/static/", "classpath:/public/" };123456789
//WebMvcAutoConfigurationAdapter@Overridepublic void addViewControllers(ViewControllerRegistry registry) {Resource page = this.resourceProperties.getWelcomePage();            if (page != null) {logger.info("Adding welcome page: " + page);registry.addViewController("/").setViewName("forward:index.html");}}123456789

template

像页面模板文件可以放在src/main/resources/template目录下。但这个实际上是模板的实现类自己处理的。比如ThymeleafProperties类里的:

public static final String DEFAULT_PREFIX = "classpath:/templates/";1

jsp

jsp页面和template类似。实际上是通过spring mvc内置的JstlView来处理的。

可以通过配置spring.view.prefix来设定jsp页面的目录:

spring.view.prefix: /WEB-INF/jsp/1

spring boot里统一的错误页面的处理

对于错误页面,Spring boot也是通过创建一个BasicErrorController来统一处理的。

@Controller@RequestMapping("${server.error.path:${error.path:/error}}")public class BasicErrorController extends AbstractErrorController 1234

对应的View是一个简单的HTML提醒:

    @Configuration@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)    @Conditional(ErrorTemplateMissingCondition.class)    protected static class WhitelabelErrorViewConfiguration {private final SpelView defaultErrorView = new SpelView(                "<html><body><h1>Whitelabel Error Page</h1>"+ "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"+ "<div id='created'>${timestamp}</div>"+ "<div>There was an unexpected error (type=${error}, status=${status}).</div>"+ "<div>${message}</div></body></html>");        @Bean(name = "error")        @ConditionalOnMissingBean(name = "error")        public View defaultErrorView() {            return this.defaultErrorView;}1234567891011121314151617

spring boot的这个做法很好,避免了传统的web应用来出错时,默认抛出异常,容易泄密。

spring boot应用的maven打包过程

先通过maven-shade-plugin生成一个包含依赖的jar,再通过spring-boot-maven-plugin插件把spring boot loader相关的类,还有MANIFEST.MF打包到jar里。

spring boot里有颜色日志的实现

当在shell里启动spring boot应用时,会发现它的logger输出是有颜色的,这个特性很有意思。

可以通过这个设置来关闭:

spring.output.ansi.enabled=false1

原理是通过AnsiOutputApplicationListener ,这个来获取这个配置,然后设置logback在输出时,加了一个 ColorConverter,通过org.springframework.boot.ansi.AnsiOutput ,对一些字段进行了渲染。

一些代码小技巧

实现ClassLoader时,支持JDK7并行加载

可以参考LaunchedURLClassLoader里的LockProvider

public class LaunchedURLClassLoader extends URLClassLoader {private static LockProvider LOCK_PROVIDER = setupLockProvider();    private static LockProvider setupLockProvider() {        try {ClassLoader.registerAsParallelCapable();            return new Java7LockProvider();}        catch (NoSuchMethodError ex) {            return new LockProvider();}}    @Overrideprotected Class<?> loadClass(String name, boolean resolve)            throws ClassNotFoundException {        synchronized (LaunchedURLClassLoader.LOCK_PROVIDER.getLock(this, name)) {Class<?> loadedClass = findLoadedClass(name);            if (loadedClass == null) {Handler.setUseFastConnectionExceptions(true);                try {loadedClass = doLoadClass(name);}                finally {Handler.setUseFastConnectionExceptions(false);}}            if (resolve) {resolveClass(loadedClass);}            return loadedClass;}}123456789101112131415161718192021222324252627282930313233

检测jar包是否通过agent加载的

InputArgumentsJavaAgentDetector,原理是检测jar的URL是否有”-javaagent:”的前缀。

private static final String JAVA_AGENT_PREFIX = "-javaagent:";1

获取进程的PID

ApplicationPid,可以获取PID。

    private String getPid() {        try {String jvmName = ManagementFactory.getRuntimeMXBean().getName();            return jvmName.split("@")[0];}        catch (Throwable ex) {            return null;}}123456789

包装Logger类

spring boot里自己包装了一套logger,支持java, log4j, log4j2, logback,以后有需要自己包装logger时,可以参考这个。

在org.springframework.boot.logging包下面。

获取原始启动的main函数

通过堆栈里获取的方式,判断main函数,找到原始启动的main函数。

private Class<?> deduceMainApplicationClass() {    try {StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();        for (StackTraceElement stackTraceElement : stackTrace) {            if ("main".equals(stackTraceElement.getMethodName())) {                return Class.forName(stackTraceElement.getClassName());}}}    catch (ClassNotFoundException ex) {        // Swallow and continue}    return null;
}1234567891011121314

spirng boot的一些缺点:

当spring boot应用以一个fat jar方式运行时,会遇到一些问题。以下是个人看法:

  • 日志不知道放哪,默认是输出到stdout的

  • 数据目录不知道放哪, jenkinns的做法是放到 ${user.home}/.jenkins 下面

  • 相对目录API不能使用,servletContext.getRealPath(“/”) 返回的是NULL

  • spring boot应用喜欢把配置都写到代码里,有时会带来混乱。一些简单可以用xml来表达的配置可能会变得难读,而且凌乱。

转载于:https://blog.51cto.com/17099933344/1979180

springboot知识相关推荐

  1. springboot知识一

    一.什么是Springboot 1.1 springboot以约定大于配置的核心思想 spring boot的优点 业务:service:userService:===>模块! springmv ...

  2. SpringBoot知识清单

    一.Spring IOC容器 1.1Spring IOC 你只需要告诉它你需要哪个bean,他就会把具体的实例发给你,至于这个实例是否依赖其他bean,如何完成初始化,你根本不需要关心 IoC容器想要 ...

  3. 使用IDEA搭建第一个SpringBoot程序

    近来在研究SpringBoot的使用,现在把使用IDEA搭建一个SpringBoot的HelloWorld程序记录下来 新建一个SpringBoot环境 打开你的IntelliJ IDEA,然后选择C ...

  4. SpringBoot企业级开发

    根据书籍<SpringBoot企业级开发教程>梳理SpringBoot开发框架,代码在本人Mac运行通过,IDE是IntelliJ. SpringBoot知识图谱: 链接:https:// ...

  5. (附源码)Springboot大学生综合素质测评系统 毕业设计 162308

    摘 要 本论文主要论述了如何使用java语言开发一个Springboot大学生综合素质测评系统,本系统将严格按照软件开发流程进行各个阶段的工作,采用B/S架构,面向对象编程思想进行项目开发.本文将论述 ...

  6. 计算机毕业设计springboot化妆品商城网站

    最新计算机专业原创毕业设计参考选题都有源码+数据库是近期作品 你的选题刚好在下面有,有时间看到机会给您发 1 ssm毕业生实习管理系统 2 jsp试题题库管理和试卷生成系统 3 ssm基于网络C++实 ...

  7. (附源码)springboot福佳生活超市进销存管理系统 毕业设计261620

    福佳生活超市进销存管理系统 摘 要 随着超市规模的发展不断扩大,商品数量急剧增加,有关商品的各种信息量也成倍增长.超市时时刻刻都需要对商品各种进销存信息进行统计分析.超市进销存管理系统开发的主要目是使 ...

  8. springboot福佳生活超市进销存管理系统毕业设计源码261620

    福佳生活超市进销存管理系统 摘 要 随着超市规模的发展不断扩大,商品数量急剧增加,有关商品的各种信息量也成倍增长.超市时时刻刻都需要对商品各种进销存信息进行统计分析.超市进销存管理系统开发的主要目是使 ...

  9. Springboot福佳生活超市进销存管理系统 毕业设计-附源码261620

    福佳生活超市进销存管理系统 摘 要 随着超市规模的发展不断扩大,商品数量急剧增加,有关商品的各种信息量也成倍增长.超市时时刻刻都需要对商品各种进销存信息进行统计分析.超市进销存管理系统开发的主要目是使 ...

最新文章

  1. 英国脱欧但网络安全领域重视未减
  2. UVa10970 - Big Chocolate(计数问题)
  3. 成功解决使用jupyter notebook命令打开原先存的.ipynb文件突然消失的问题
  4. Python - 列表与字符串的互相转换
  5. Android-JSNative交互的几种可行性方案H5白屏问题解决方式
  6. MFC中给单文档程序添加背景图片
  7. Oracle 游标的练习
  8. Stream操作Collection集合
  9. netty ssl 服务器
  10. net 架构师-数据库-sql server-002-工具
  11. 解读clickhouse存算分离在华为云实践
  12. 操作系统—死锁的检测和解除
  13. Android入门笔记09
  14. C#中手动引用COM组建的例子
  15. oracle数据库开机自启,oracle数据库开机自动启动
  16. (CCNET)criss-cross attention network学习笔记
  17. 李智慧-我的全栈之路导师之一
  18. 函数对称性常见公式_高中函数对称性总结
  19. android恢复出厂设置
  20. 测试工作中比较好用的几款对比工具

热门文章

  1. ICLR 2019论文解读:量化神经网络
  2. 空洞卷积(Dilated Convolution):有之以为利,无之以为用
  3. 李彦宏候选中国工程院院士
  4. 【风之语】至贱城市之成都
  5. 一份关于kaggle特征构建技巧和心得
  6. 吴恩达机器学习笔记 —— 18 大规模机器学习
  7. SAP MM 103 + 105 移动类型组合初探
  8. 盘点丨机器学习2017年重大进展汇总
  9. 业界丨2018深度学习十大趋势:元学习成新SGD,多数硬件创企将失败
  10. 如何将一个列表当作元组的一个元素