-
SpringMVC 什么是MVC模型 MVC全称Model View Controller,是一种设计创建Web应用程序的模式。这三个单词分别代表Web应用程序的三个部分: Model(模型):指数据模型。用于存储数据以及处理用户请求的业务逻辑。在Web应用中,JavaBean对象,业务模型等都属于Model。 View(视图):用于展示模型中的数据的,一般为jsp或html文件。 Controller(控制器):是应用程序中处理用户交互的部分。接受视图提出的请求,将数据交给模型处理,并将处理后的结果交给视图显示。 SpringMVC SpringMVC是一个基于MVC模式的轻量级Web框架,是Spring框架的一个模块,和Spring可以直接整合使用,我们使用的版本是Spring6,所以JDK需要17以上。SpringMVC代替了Servlet技术,它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无须实现任何接口。 案例 使用maven创建web项目,补齐包结构。 引入相关依赖 <dependencies> <!-- Spring核心依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.11</version> </dependency> <!-- SpringMVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>6.0.11</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>6.0.11</version> </dependency> <!-- Servlet --> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>6.0.0</version> <scope>provided</scope> </dependency> <!-- JSP --> <dependency> <groupId>jakarta.servlet.jsp</groupId> <artifactId>jakarta.servlet.jsp-api</artifactId> <version>3.0.0</version> <scope>provided</scope> </dependency> </dependencies> 在web.xml中配置前端控制器DispatcherServlet。 <web-app> <display-name>Archetype Created Web Application</display-name> <!--SpringMVC前端控制器,本质是一个Servlet,接收所有请求,在容器启动时就会加载--> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> 4.编写SpringMVC核心配置文件springmvc.xml,该文件和Spring配置文件写法一样。 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 扫描包 --> <context:component-scan base-package="com.jjy"/> <!-- 开启SpringMVC注解的支持 --> <mvc:annotation-driven/> </beans> 5.编写控制器 @Controller public class MyController1 { // 该方法的访问路径是/c1/hello1 @RequestMapping("/c1/hello1") public void helloMVC(){ System.out.println("hello SpringMVC!"); } } 配置tomcat10启动项目,访问 http://localhost:8080/c1/hello1 SpringMVC执行流程 SpringMVC的组件 DispatcherServlet:前端控制器,接受所有请求,调用其他组件。 HandlerMapping:处理器映射器,根据配置找到方法的执行链。 HandlerAdapter:处理器适配器,根据方法类型找到对应的处理器。 ViewResolver:视图解析器,找到指定视图。 组件的工作流程 客户端将请求发送给前端控制器。 前端控制器将请求发送给处理器映射器,处理器映射器根据路径找到方法的执行链,返回给前端控制器。 前端控制器将方法的执行链发送给处理器适配器,处理器适配器根据方法类型找到对应的处理器。 处理器执行方法,将结果返回给前端控制器。 前端控制器将结果发送给视图解析器,视图解析器找到视图文件位置。 视图渲染数据并将结果显示到客户端。 SpringMVC封装参数 简单数据类型 在Servlet中我们通过request.getParameter(name)获取请求参数。该方式存在两个问题: 请求参数较多时会出现代码冗余。 与容器紧耦合。 而SpringMVC支持参数注入的方式用于获取请求数据,即将请求参数直接封装到方法的参数当中。用法如下: 编写控制器方法 // 封装为简单数据类型类型参数 @RequestMapping("/c1/param1") public void simpleParam(String username,int age){ System.out.println(username); System.out.println(age); 访问该方法时,请求参数名和方法参数名相同,即可完成自动封装。 http://localhost:8080/c1/param1?username=bz&age=10 简单对象 SpringMVC支持将参数直接封装为对象,写法如下: 编写实体类 public class Student { private int id; private String name; private String sex; // 省略getter/setter/tostring } 编写控制器方法 // 封装为简单对象类型参数 @RequestMapping("/c1/param2") public void objParam(Student student){ System.out.println(student); } 访问该方法时,请求参数名和对象参数的属性名相同,即可完成自动封装。 http://localhost:8080/c1/param2?id=1&name=bz&sex=female 关联对象 SpringMVC还可以将请求参数封装为关联对象,即对象的属性也是一个对象。写法如下: 编写实体类 public class Student { private int id; private String name; private String sex; private Address address; // 地址对象 // 省略getter/setter/tostring } public class Address { private String info; //地址信息 private String postcode; //邮编 // 省略getter/setter/tostring } 编写控制器方法 // 获取关联对象类型参数 @RequestMapping("/c1/param3") public void objParam2(Student student){ System.out.println(student); } 访问该方法时,请求参数名和方法参数的属性名相同,即可完成自动封装。 http://localhost:8080/c1/param3?id=1&name=bz&sex=female&address.info=beijing&address.postcode=030000 我们也可以使用表单发送带有参数的请求: <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>表单提交</title> </head> <body> <form action="/c1/param3" method="post"> id:<input name="id"> 姓名:<input name="name"> 性别:<input name="sex"> 住址:<input name="address.info"> 邮编:<input name="address.postcode"> <input type="submit"> </form> </body> </html> 原文链接:https://blog.csdn.net/m0_74436895/article/details/142927915
-
一、SpringMVC简介 1、什么是MVC MVC是一种软件架构模式(是一种软件架构设计思想,不止Java开发中用到,其它语言也需要用到),它将应用分为三块: M:Model(模型),负责业务处理及数据的收集 V:View(视图),负责数据的展示 C:Controller(控制器),负责调度。它是一个调度中心,它来决定什么时候调用Model来处理业务,什么时候调用View视图来展示数据 MVC架构模式的描述:前端浏览器发送请求给web服务器,web服务器中的Controller接收到用户的请求,Controller负责将前端提交的数据进行封装,然后Controller调用Model来处理业务,当Model处理完业务后会返回处理之后的数据给Controller,Controller再调用View来完成数据的展示,最终将结果响应给浏览器,浏览器进行渲染展示页面。 2、MVC架构模式与三层模型的区别 什么是三层模型 三层模型就是由Controller控制器和View视图组成的表现层,将Model数据模型拆封为业务层和与数据库交互的持久层 MVC架构模式与三层模型的区别? MVC和三层模型都采用了分层结构来设计应用程序,都是降低耦合度,提高扩展力,提高组件复用性 区别在于他们的关注点不同 三层模型更加关注业务逻辑组件的划分 MVC架构模式关注的是整个应用程序的层次关系和分离思想 现代的开发方式大部分都是MVC架构模式结合三层模型一起用 3、什么是SpringMVC SpringMVC是一个实现了MVC架构模式的Web框架,底层基于Servlet实现 SpringMVC已经将MVC架构模式实现了,因此只要我们是基于SpringMVC框架写代码 Spring框架中有一个子项目叫做Spring Web,Spring Web子项目当中包含很多模块 Spring MVC Spring WebFlux Spring Web Services Spring Web Flow Spring WebSocket Spring Web Services Client Spring架构图如下,其中Web中的servlet指的就是Spring MVC 二、HelloWorld程序 1、pom文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.xc</groupId> <artifactId>springmvc-xml</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <!--springmvc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.1</version> </dependency> <!--servletAPI--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <!--spring5和thymeleaf整合--> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> <version>3.0.11.RELEASE</version> </dependency> </dependencies> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> </project> 2、springmvc.xml 组件扫描。spring扫描这个包中的类,将这个包中的类实例化并纳入IoC容器的管理 视图解析器。视图解析器(View Resolver)的作用主要是将Controller方法返回的逻辑视图名称解析成实际的视图对象。视图解析器将解析出的视图对象返回给DispatcherServlet,并最终由DispatcherServlet将该视图对象转化为响应结果,呈现给用户 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--组件扫描--> <context:component-scan base-package="com.xc.controller"/> <!--视图解析器--> <bean id="thymeleafViewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> <!--作用于视图渲染的过程中,可以设置视图渲染后输出时采用的编码字符集--> <property name="characterEncoding" value="UTF-8"/> <!--如果配置多个视图解析器,它来决定优先使用哪个视图解析器,它的值越小优先级越高--> <property name="order" value="1"/> <!--当 ThymeleafViewResolver 渲染模板时,会使用该模板引擎来解析、编译和渲染模板--> <property name="templateEngine"> <bean class="org.thymeleaf.spring5.SpringTemplateEngine"> <!--用于指定 Thymeleaf 模板引擎使用的模板解析器。模板解析器负责根据模板位置、模板资源名称、文件编码等信息,加载模板并对其进行解析--> <property name="templateResolver"> <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"> <!--设置模板文件的位置(前缀)--> <property name="prefix" value="/WEB-INF/templates/"/> <!--设置模板文件后缀(后缀),Thymeleaf文件扩展名不一定是html,也可以是其他,例如txt,大部分都是html--> <property name="suffix" value=".html"/> <!--设置模板类型,例如:HTML,TEXT,JAVASCRIPT,CSS等--> <property name="templateMode" value="HTML"/> <!--用于模板文件在读取和解析过程中采用的编码字符集--> <property name="characterEncoding" value="UTF-8"/> </bean> </property> </bean> </property> </bean> </beans> 3、配置web.xml文件 Spring MVC是一个web框架,在javaweb中谁来负责接收请求,处理请求,以及响应呢?当然是Servlet 在SpringMVC框架中已经为我们写好了一个Servlet,它的名字叫做:DispatcherServlet,我们称其为前端控制器 既然是Servlet,那么它就需要在web.xml文件中进行配置: <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd" version="5.0"> <!--配置前端控制器--> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--手动设置springmvc配置文件的路径及名字--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <!--为了提高用户的第一次访问效率,建议在web服务器启动时初始化前端控制器--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <!-- /* 表示任何一个请求都交给DispatcherServlet来处理 --> <!-- / 表示当请求不是xx.jsp的时候,DispatcherServlet来负责处理本次请求--> <!-- jsp本质就是Servlet,因此如果请求是jsp的话,应该走它自己的Servlet,而不应该走DispatcherServlet --> <!-- 因此我们的 url-pattern 使用 / --> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> DispatcherServlet是SpringMVC框架为我们提供的最核心的类,它是整个SpringMVC框架的前端控制器,负责接收HTTP请求、将请求路由到处理程序、处理响应信息,最终将响应返回给客户端。 接收客户端的HTTP请求:DispatcherServlet监听来自Web浏览器的HTTP请求,Tomcat已经将请求数据解析为Request对象 处理请求的URL:DispatcherServlet将请求的URL与处理程序进行匹配,确定要调用哪个控制器(Controller)来处理此请求 调用相应的控制器:DispatcherServlet将请求发送给找到的控制器处理,控制器将执行业务逻辑,然后返回一个模型对象(Model) 渲染视图:DispatcherServlet将调用视图引擎,将模型对象呈现为用户可以查看的HTML页面 返回响应给客户端:DispatcherServlet将为用户生成的响应发送回浏览器,响应可以包括表单、JSON、XML、HTML以及其它类型的数据 4、html文件 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> hello world </body> </html> 原文链接:https://xuchang.blog.csdn.net/article/details/139730755
-
一.spring是什么? Spring是一个轻量级的IoC/DI和AOP容器的开源框架 二.spring的优点 1.Spring 能帮我们低侵入/低耦合地根据配置文件创建及组装对象之间的依赖关系 //解耦 2.Spring 面向切面编程能帮助我们无耦合的实现日志记录,性能统计,安全控制等 //提高代码重用性 3.Spring 能非常简单的且强大的声明式事务管理 //使用xml或注解的方法,底层用到动态代理 4.Spring 提供了与第三方数据访问框架(如JPA)无缝集成,且自己也提供了一套JDBC模板来方便数据库访问 5.Spring 提供与第三方Web框架无缝集成,且自己也提供了一套 SpringMVC 框架,来方便 Web 层搭建 三.Spring Ioc/DI Ioc: 1.控制反转,是一种设计思想,就是原本在程序中需要手动创建对象的权利,交给spring框架管理 2.Ioc容器是实现Ioc的载体,Ioc容器实际上就是个Map(key,value),map中存放各种对象 3.Ioc容器就像一个工厂一样,当我们需要创建一个对象时,只需要配置好配置文件或贴注解即可, 完全不用考虑对象时如何被创建出来的 4.将对象相互依赖的关系交给ioc容器管理,这样可以很大程度简化应用开发 5.使用Ioc的话,我们只需要配置好,在需要的地方引用就好,这大大的增加了项目的可维护性且降低了开发难度 DI: 1.指 Spring 创建对象的过程中,将对象依赖属性通过配置设值给该对象 2.就是给对象的属性设置属性值 //Ioc创建对象,Di给属性设置值 四.Spring AOP Aop:面向切面编程 1.能够将那些与业务无关,却为业务模块所共同调用的逻辑和责任封装起来, 便于减少重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性 2.Spring Aop就是基于动态代理的,当要代理的对象实现了某个接口,就会使用Jdk动态代理 3.如果没有某个接口,就是使用cglib生成一个被代理对象的子类作为代理 //使用aop之后我们可以把一些通用的功能抽象出来,在需要的时候自己使用即可,这样大大的简化了代码量 //常用的aop的有 事务处理 日志管理 权限控制 五.Spring 中的 bean 的作用域 //作用域:在 Spring 容器中是指其创建的 bean 对象相对于其他 bean 对象的请求可见范围 singleton : 唯一bean 实例,Spring 中的 bean 默认都是单例的。 prototype : 多例,每次请求都会创建一个新的 bean 实例,多例只会初始化和销毁,不会存到容器中进行管理 request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。 session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。 /*使用总结:在开发中主要使用 scope="singleton"。对于 Struts1 的 Action 使用 request, Struts2 中的 Action 使 用 prototype 类型,其他使用 singleton,即不配置 六. SpringMVC工作原理 1、客户端发出http请求给web服务器,web服务器对http请求进行解析,如果匹配DispatcherServlet的请求映射路径(在web.xml中指定),web容器将请求转交给DispatcherServlet. 2、DipatcherServlet接收到这个请求之后将根据请求的信息(包括URL、Http方法、请求报文头和请求参数Cookie等)以及HandlerMapping的配置找到处理请求的处理器Controller(Handler)。 3、DispatclierServlet 根据 HandlerMapping 找到对应的Handler,处理权交给Handler ( Handler将具体的处理进行封装),再由具体的处理适配器 HandlerAdapter对Handler进行具体的调用。 5、Handler对数据处理完成以后将返回一个ModeAndView对象给DispatcherServlet. 6、Handler返回的ModelAndView只是一个逻辑视图并不是一个正式的视图,dspatcherSevlet通过ViewResolver将邀辑视图转化为真正的视图view(这句话简单的就是返回一个要返回的页面的相对路径,通过试图解析器转换成具体那个页面)。 7、DispatcherServlet通过model解析出ModeAndView中的参数进行解析最终展现出完整的view并返回给客户端 //简化 1. 客户端(浏览器)发送请求到dispatcherServlet前端控制器 2. DispatcherServlet 根据请求信息调用 HandlerMapping ,解析请求对应的 Handler 3. 解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由HandlerAdapter 适配器处理 4. HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求,并处理相应的业务逻辑 5. 处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View 6. ViewResolver 会根据逻辑 View 查找实际的 View 7. DispaterServlet 把返回的 Model 传给 View (视图渲染) 8. 把 View 返回给请求者(浏览器) 七.Spring 管理事务的方式 1. 编程式事务,在代码中硬编码。(不推荐使用) 2. 声明式事务,在配置文件中配置(推荐使用) //声明式事务又分为两种: 1. 基于XML的声明式事务 2. 基于注解的声明式事务 八.Spring 框架中用到的设计模式 1.工厂设计模式 : Spring使用工厂模式通过 BeanFactory 、ApplicationContext 创建 bean 对象 2.代理设计模式 : Spring AOP 功能的实现 3.单例设计模式 : Spring 中的 Bean 默认都是单例的 4.模板方法模式 : Spring 中 jdbcTemplate 、hibernateTemplate 等以Template 结尾的对数据库操作类, 它们就使用到了模板模式 5.包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库, 这种模式让我们可以根据客户的需求能够动态切换不同的数据源。 6.观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。 7.适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式,spring MVC中也是用到了适配器模式适配 Controller 九.小结 1.spring的核心思想Ioc/DI和aop 2.使用spring不用处理对象之间的依赖关系,能够大大的降低我们代码的耦合度,增加项目的可维护性 3.使用spring我们不用关心对象是怎么创建的,只需要配置好配置文件或贴注解即可,很大程度简化应用开发 4.使用spring能够将与业务无关的代码,,却为业务模块所共同调用的逻辑使用aop将其抽象出来,提高代码可重用性 5.spring提供与第三方web框架无缝集成,并且自己也提供一套SpringMVC框架 使用spring需要配置大量的xml文件,其实也是有点头疼的,但是springboot可以帮我们完美的解决这些问题 原文连接:https://devpress.csdn.net/cloudnative/66d41b140bfad230b8b2e176.html
-
spring是什么? pring是一个开源框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。 Spring项目出发点: 目的:提供一种贯穿始终的解决方面,将各种专用框架整合成一个连贯的整体框架,简化企业级应用的开发(有点像粘合剂) 鼓励最佳实践: 例如spring将“针对接口编程”的成本降到了最小 非侵入性: 应用对象尽量避免依赖框架,IoC和AOP是避免依赖的关键技术 统一配置: 好的框架应该让应用配置灵活且统一 易于测试: 使单元测试尽可能简单 可扩展: 简单来说,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。 1.核心容器(Spring Core) 是Spring的核心容器,提供了Spring框架的基本功能; 此模块包含 的BeanFactory类是Spring的核心类,负责产生和管理Bean,是工程模式的实现; 采用Factory(工厂模式)实现了IOC(控制反转)将应用的配置和依赖性规范与实际的应用程序代码分开; Spring以bean的方式组织和管理Java应用中发各个组件及其关系。 2.应用上下文(Spring Context) 是一个配置文件,向Spring框架提供上下文信息; SpringContext模块继承BeanFactory类,添加了事件处理、国际化、资源装载、透明装载、以及数据校验等功能; 还提供了框架式的Bean的访问方式和企业级的功能,如JNDI访问,支持EJB、远程调用、继承模板框架、Email和定时任务调度等; 3.面向切面编程(Spring AOP) Spring AOP直接将面向方面的编程功能集成到了Spring框架中,所以很容易的使Spring框架管理的任何对象支持AOP(Spring集成了所有AOP功能。通过事务管理可以使任意Spring管理的对象AOP化); Spring AOP为基于Spring的应用程序中的对象提供了事务管理服务; 通过使用Spring AOP,不用依赖EJB组件,就可以将声明性事务管集成到应用程序中。 4.JDBC和DAO模块(Spring DAO) DAO(DataAccessObject)模式思想是将业务逻辑代码与数据库交互代码分离,降低两者耦合; 通过DAO模式可以使结构变得更为清晰,代码更为简; DAO模块中的JDBC的抽象层,提供了有意义的异常层次结构,用该结构来管理异常处理,和不同数据库供应商所抛出的错误信息; 异常层次结构简化了数据库厂商的异常错误(不再从SQLException继承大批代码),极大的降低了需要编写的代码数量,并且提供了对声明式事务和编程式事务的支持; 5.对象实体映射(Spring ORM) SpringORM模块提供了对现有ORM框架的支持; 提供了ORM对象的关系工具,其中包括了Hibernate、JDO和 IBatis SQL Map等,所有的这些都遵从Spring的通用事务和DAO异常层次结构; 注意这里Spring是提供各类的接口(support),目前比较流行的下层数据库封闭映射框架,如mybatis,Hibernate等; 6.Web模块(Spring Web) 此模块建立在SpringContext基础之上,提供了Servlet监听器的Context和Web应用的上下文; 对现有的Web框架,如JSF、Tapestry、Structs等提供了集成; SpringWeb模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。 7.MVC模块(Spring Web MVC) SpringWebMVC模块建立在Spring核心功能之上,拥有Spring框架的所有特性,能够适应多种多视图、模板技术、国际化和验证服务,实现控制逻辑和业务逻辑的清晰分离; 通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI; IOC 控制反转(IoC=Inversion of Control)IoC,用白话来讲,就是由容器控制程序之间的(依赖)关系,而非传统实现中,由程序代码直接操控。这也就是所谓“控制反转”的概念所在:(依赖)控制权由应用代码中转到了外部容器,控制权的转移,是所谓反转。 IoC还有一个另外的名字:“依赖注入 (DI=Dependency Injection)” ,即由容器动态的将某种依赖关系注入到组件之中。 示例 创建工程---创建maven的web项目 pom文件 <properties> <spring.version>5.3.6</spring.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> </dependencies> spring配置文件 创建beans.xml(名称可以任意)配置文件,文件内容如下 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> </beans> 示例代码 1.代码 /** * 第一个Spring,输出"Hello,Spring!"。 */ public class HelloSpring { // 定义who属性,该属性的值将通过Spring框架进行设置 private String who = null; public HelloSpring(){ } public HelloSpring(String who){ this.who = who; } /** * 定义打印方法,输出一句完整的问候。 */ public void print() { System.out.println("Hello," + who + "!"); } public String getWho() { return who; } public void setWho(String who) { this.who = who; } } 2.beans.xml配置文件中配置 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- id要唯一,class:指定类,不能是接口或者抽象类;默认情况,spring调用class指定类中不带参的构造方法 --> <bean id="helloSpring" class="com.zking.springdemo.HelloSpring"> <!-- 调用class类中的set+name方法,传value指定的值 --> <property name="who" value="spring"/> </bean> </beans> 配置文件说明: id:在容器中查找Bean的id(唯一、且不能以/开头) class:bean的完整类名 scope:(singleton|prototype)默认是singleton singleton: 单例模式, 在每个Spring IoC容器中一个bean定义对应一个对象实例;spring在容器启动时,创建实例 ; prototype: 原型模式/多例模式, 一个bean定义对应多个对象实例;在用户使用bean对象时才创建 3.通过Spring的ApplicationContex 获取Bean public class HelloSpringTest { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); HelloSpring h1 = (HelloSpring)ctx.getBean("helloSpring"); h1.print(); } } 注入方式 设值注入--通过调用对应的set方法注入 <bean id="helloSpring" class="com.zking.springdemo.HelloSpring"> <property name="who" value="spring"/> </bean> 构造注入 <bean id="helloSpring2" class="com.zking.springdemo.HelloSpring"> <constructor-arg value="spring"></constructor-arg> </bean> p注入--在beans.xml文件中额外引入p命名空间 xmlns:p="http://www.springframework.org/schema/p" p注入--beans.xml中bean的配置 <bean id="helloSpring3" class="com.zking.springdemo.HelloSpring" p:who="spring"> </bean> 复杂属性的配置 依赖bean对象(重点) <bean id="userDao" class="com.zking.springdemo.dao.impl.UserDaoImpl"></bean> <bean id="userService" class="com.zking.springdemo.service.impl.UserServiceImpl"> <!--ref="userDao"依赖spring容器中的id为userDao的bean对象--> <property name="userDao" ref="userDao"></property> </bean> <bean id="userService2" class="com.zking.springdemo.service.impl.UserServiceImpl" p:userDao-ref="userDao"/> List或数组 声明属性 private String[] array; private List list; 配置文件 <property name="array"> <array> <value>足球</value> <value>蓝球</value> </array> </property> <property name="list"> <list> <value>jack</value> <value>rose</value> </list> </property> Map private Map map; 配置文件 <property name="map"> <map> <entry key="en" value="english"></entry> <entry key="ch" value="chinese"></entry> </map> </property> Properties private Properties prop; 配置文件 <property name="props"> <props> <prop key="en">english</prop> <prop key="ch">chinese</prop> </props> </property> 多配置文件 系统一般会被分成多个模块,可以为每个模块配置一个配置文件,便于管理,在版本控制软件中也可以减少冲突 beans.xml配置文件 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <import resource="spring_ioc.xml"/> </beans> spring_ioc.xml配置文件 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- id要唯一,class:指定类,不能是接口或者抽象类;默认情况,spring调用class指定类中不带参的构造方法 --> <bean id="helloSpring" class="com.zking.springdemo.HelloSpring"> <!-- 调用class类中的set+name方法,传value指定的值 --> <property name="who" value="spring"/> </bean> <bean id="helloSpring2" class="com.zking.springdemo.HelloSpring"> <constructor-arg value="spring"></constructor-arg> </bean> <bean id="helloSpring3" class="com.zking.springdemo.HelloSpring" p:who="spring"></bean> <bean id="userDao" class="com.zking.springdemo.dao.impl.UserDaoImpl"></bean> <bean id="userService" class="com.zking.springdemo.service.impl.UserServiceImpl"> <!--ref="userDao"依赖spring容器中的id为userDao的bean对象--> <property name="dao" ref="userDao"></property> </bean> <bean id="userService2" class="com.zking.springdemo.service.impl.UserServiceImpl" p:dao-ref="userDao"/> <!--复杂属性的配置--> <bean id="testBean" class="com.zking.springdemo.TestBean"> <property name="list"> <list> <value>jack</value> <value>rose</value> </list> </property> <property name="array"> <array> <value>足球</value> <value>蓝球</value> </array> </property> <property name="map"> <map> <entry key="en" value="english"></entry> <entry key="ch" value="chinese"></entry> </map> </property> <property name="props"> <props> <prop key="en">english</prop> <prop key="ch">chinese</prop> </props> </property> </bean> </beans> ———————————————— 原文链接:https://blog.csdn.net/weixin_59196705/article/details/123842794
-
1. Spring 的介绍 1.1 Spring是什么? Spring: Spring Framework(Spring 框架)Spring ⽀持⼴泛的应⽤场景,它可以让 Java 企业级的 应⽤程序开发起来更简单。用一句简单的话来说,Spring就是一个拥有众多工具的ioc容器。 1.2 什么是容器,什么是IOC 容器? 容器: ⽤来容纳某种物品的(基本)装置 在我们学习Java基础的时候,就接触过一些关于数据结构的容器,他们都是根据一定的组织结构来组织众多的数据,例如:Map,List,Stack,Queue... 它们是组织数据的容器。 Spring也是一个容器。是一个IOC容器。 IOC(Inversion of control) 翻译为控制反转,IOC不是一种技术而是一种设计思想,其实这里的控制反转指的是,我们在创建对象的时候,对象的创建控制权交给了外部,在使用对象时,在程序中不要自己使用new创建对象,转换成为由外部主动提供对象。 ioc容器用于充当ioc思想中的外部 IOC容器负责对象的创建,初始化等一系列的工作,被创建或者被管理的对象在IOC容器中统称为Bean 在传统程序设计,我们直接在对象内部通过new进行创建对象。如果我们在A类中需要一个B类中的对象,那按照以前我们在A类中就可以直接new出一个B类型的对象,那么此时我们不妨想一下,如果这个在A类中需要B类对象的时候,直接new 出B类型对象的思想,放到复杂的项目中,那么就有可能程序的调用链就会很长。例如:在A类中new一个B类型的对象,在B类中new一个C类型的对象,在C类中new一个D类型的对象… 那么如果此时的程序需求一旦有了变化,那么就会牵一发而动全身,这些类中的代码都需要改动。传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象 那么既然Spring是一个拥有众多工具的ioc容器,那么Spring这个容器就有控制反转的思想,那么是谁控制谁,又控制了什么,为什么在Spring中需要控制反转。 是谁控制谁?控制了什么? 其实Spring的ioc容器,控制了在类中创建和管理对象的权限。也就是ioc容器控制了在该类中new出其他类型的对象。在传统的程序设计中,如果要想在A类中得到B类型的对象,那么就会直接new出B类型的对象,但是在这样的操作在后期维护代码的时候是非常费劲的。只要在A类中依赖的B对象的成员属性如果需要改动,那么整个代码就要进行大规模的更改。 但是如果使用的ioc容器,创建和管理对象的权限就交给了ioc容器。把B对象传给ioc容器,当我们在A类中需要B对象的时候,只要引用Spring ioc 容器中的B对象即可。 为什么Spring需要控制反转,在Spring中又控制反转了什么? 因为在传统的程序设计中,如果要想在A类中得到B类型的对象,那么就会直接new出B类型的对象,但是在这样的操作在后期维护代码的时候是非常费劲的。只要在A类中依赖的B对象的成员属性如果需要改动,那么整个代码就要进行大规模的更改。,在ioc容器中创建和管理对象,在容器中创建对象A,并且有该容器管理A对象。当在类A中需要对象B的时候,那么此时就不需要在类A中创建出一个对象B,此时对象A中的引用就可以被动的接受ioc容器中传来的对象B. spring ioc 优点: 成功实现了解耦, 耦合是指:两个或两个以上对象存在依赖,当一方修改之后会影响另一方,那么就说这些对象间存在耦合。我们知道较好的代码应该是 "高内聚,低耦合 "的。这里把创建和管理对象的权限交给了ioc容器。 1.3 Spring IOC 的核心理念 我们目前知道了Spring是一个拥有众多工具的IOC容器,那么怎样理解这就话呢? 所谓的容器:最大的用处就是把某物添加到容器中,再从容器中把某物取出来,这里的spring ioc容器也是一样的。IOC的核心:就是把bean添加到ioc容器中,然后在从ioc容器中把bean对象取出来。 其实Spring ioc 是一种具体的思想。它不是一种具体的行为措施。 将对象存放到容器中的好处:将对象存储在 IoC 容器相当于 将以后可能⽤的所有⼯具制作好都放到仓库中,需要的时候直接取就⾏了,⽤完再把它放回到仓库。 ⽽ new 对象的⽅式相当于,每次需要⼯具 了,才现做,⽤完就扔掉了也不会保存,下次再⽤的时候还得重新做,这就是 IoC 容器和普通程序开 发的区别。 1.4 什么是DI? DI (Dependency Injection) “依赖注⼊” 那么什么又是“依赖注入”? 在ioc容器中创建bean 和 bean之间依赖关系的整个过程 ,使用对象的时候不仅可以直接从ioc容器获取,并且获取到的bean已经绑定了所有的依赖关系。 所谓依赖注⼊,就是由 IoC 容器在运⾏期间,动态地将某种依赖关系注⼊到对象之中。所以, 赖注⼊(DI)和控制反转(IoC)是从不同的⻆度的描述的同⼀件事情,就是指通过引⼊ IoC 容 器,利⽤依赖关系注⼊的⽅式,实现对象之间的解耦。 1.5 经典面试题:说一说 IOC 和 DI 的区别 依赖注入不是一种设计实现,而是一种具体的技术,它是在 IoC 容器运行期间,动态地将某个依赖对象注入到当前对象的技术就叫做DI(依赖注入)。 IoC 和 DI 都是 Spring 框架中的重要概念,它们都是用来实现对象解耦的,其中 IoC(控制反转)是一种设计思想,而 DI(依赖注入)是一种具体的实现手段。 2. 手把手创建一个Spring项目 第一步:先创建一个maven 项目 第二步:在spring_test项目中配置pom.xml文件,因为我们要基于maven创建一个Spring项目,所以要引入一个spring-context(Spring上下文)依赖,还有要把bean对象添加到spring中,就需要引入spring_beans依赖我们也可以在maven的官网去找。 这里就是spring-context和spring-beans依赖,可以直接添加到pom.xml文件中,加入之后在idea的右边栏maven栏中点击重新加载按钮。 <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <ersion>5.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.2.3.RELEASE</version> </dependency> </dependencies> 到此我们的第一个spring项目基本就创建完成了,但是我们还要往spring ioc容器中添加对象。 往spring ioc 容器中添加依赖对象: 在 src --> main --> resource中添加配置,在resource下创建一个文件,这个文件名可以随便起,但是在这里还是简明知意的好,在这里我们创建一个spring-config.xml文件,并且在这个.xml文件中写入如下内容。 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:content="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> </beans> 此时还没有引入bean对象到ioc 容器中。那么就要在这个spring-config.xml中写入一下信息,即就把bean对象添加到了spring ioc容器中了。 如果要想再spring ioc 容器中添加对象,com.model包下先创建一个User类,在这个类中创建一个sayHi()方法 package com.model; public class User { public void sayHi(){ System.out.println("say hi"); } } 那么就在<bean> 标签中添加对象即可,其中在<bean>标签中的属性有 id 其中 这个 id表示的是在从spring ioc 容器中得到相应对象的时候的beanName,就是通过这个beanName就可以找到spring ioc 容器中找到这个对象 还有一个属性 class 表示的是这个spring ioc 容器中的user对象所在的类,就是一个具体的类的路径 <beans> <bean id = "user" class="com.model.User"></bean> </beans> 从spring ioc 容器中得到对象 创建一个App类,在该类中得到存入spring ioc 容器中的user对象,那么此时就不得不介绍一下spring的上下文对象了 spring上下文即是Spring容器抽象的一种实现;而我们常见的ApplicationContext本质上说就是一个维护Bean定义以及对象之间协作关系的高级接口 一种是不常用的BeanFactory,这是最简单的容器,只能提供基本的DI功能;还有一种就是继承了BeanFactory后派生而来的应用上下文,其抽象接口也就是我们上面提到的的ApplicationContext,它能提供更多企业级的服务,例如解析配置文本信息等等,这也是应用上下文实例对象最常见的应用场景。有了上下文对象,我们就能向容器注册需要Spring管理的对象了。 ApplicationContext 和 BeanFactory 的区别: 相同点: 都可以实现从容器中获取Bean,都提供了getBean()方法。 不同点: ApplicationContext属于BeanFactory的子类,而ApplicationContext除了拥有BeanFactory 的所有功能之外,所提供了更多的方法实现,比如对国际化的支持,资源访问的支持,以及事件和传播等方面的支持 从性能方面来说,二者是不同的,BeanFactory是按需加载Bean,ApplicationContext是饿汉模式,在创建时会将所有的Bean都加载起来,以备以后使用。 如果使用BeanFactory作为spring的上下文对象,在加载spring-config配置文件的时候,是按需加载的,也就是说我现在在这个类中需要使用到spring ioc 容器所管理的某个对象,那么此时只把我要的Bean对象进行加载,这种就属于懒汉模式,你要的时候,我才给你拿。但是每次加载spring-config.xml文件中的某个对象到spring ioc容器会消耗一部分时间,相比,AppliactionContext作为spring的上下文对象,在spring ioc 容器加载时,就会把spring-config.xml文件中的全部Bean对象加载进来。如果在以后从spring ioc 容器中获取对象的时候,直接获取即可,无需加载,这样只是第一次加载spring-config.xml文件的时候,时间会消耗长一点。所以说在计算机中,懒汉模式不一定比饿汉模式好 在model包下,在创建出一个User2类,并且把这个user2对象,添加到 spring ioc 容器中。 package com.model; public class User2 { //在spring ioc 容器加载user2 对象到 spring ioc 容器的时候就会调用User2类中的构造方法 public User2(){ System.out.println("加载User2类"); } public void sayHi(){ System.out.println("hello world"); } } //把user2对象添加到spring ioc 容器中 <bean id="user2" class="com.model.User2"></bean> 在User类中,添加一个无参的构造方法 package com.model; public class User { public User(){ System.out.println("加载User类"); } public void sayHi(){ System.out.println("say hi"); } } 在AppliactionContext作为spring上下文对象的时候,会一次性加载spring-config.xml中的所有Bean 在BeanFactory作为spring上下文对象的时候,会按需加载spring-config.xml中的Bean对象。 言回正传,从spring ioc 容器中获取对象。 public class App { public static void main(String[] args) { // 使用ApplicationContext作为spring的上下文对象,把spring-config.xml文件作为参数传给这个对象。就能通过getBean()方法,得到spring ioc 容器中所管理的对象。 ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); //因为getBean()方法的返回值是一个Object的类型,所以要在这里进行强制类型转换 User user = (User) context.getBean("user"); //得到spring ioc 中的对象,调用对象所在类的方法 user.sayHi(); } } 在上述到代码中,一定要注意的是在getBean()方法中,所传入的参数一定要和spring-config.xml文件中Bean对象的id是一样的。 getBean()方法的更多用法: 第一种: UserController user = context.getBean(UserController.class); 根据类的类型,在spring ioc 容器中获得Bean对象 第二种: UserController user = context.getBean("user", UserController.class); 根据类的类型,和在spring-config.xml文件中的Bean的id在spring ioc 容器中获得Bean对象 第三种: UserController user = context.getBean("user") 根据spring-config.xml文件中的Bean的id在spring ioc 容器中获得Bean对象 但是第一种方法获得Bean对象,有一个诟病,那就是如果在spring-config.xml配置文件中出现了两个相同的类型网spring ioc容器中添加Bean对象,那么在按照对象的类型在spring ioc 容器中获得对象的时候,就不知道你此时要获取那个对象(此时spring ioc 容器中有多个类型相同的对象) ———————————————— 原文链接:https://blog.csdn.net/qq_54883034/article/details/126458733
-
先来看看大数据的概念。根据维基百科,大数据是庞大或复杂的数据集的广义术语,因此传统的数据处理程序不足以支持如此庞大的体量。 在许多情况下,使用SQL数据库存储/检索数据都是很好的选择。而现如今的很多情况下,它都不再能满足我们的目的,这一切都取决于用例的变化。 现在来讨论一些不同的非SQL存储/处理数据工具,例如,NoSQL数据库,全文搜索引擎,实时流式处理,图形数据库等。 1、MongoDB——最受欢迎的,跨平台的,面向文档的数据库。 MongoDB是一个基于分布式文件存储的数据库,使用C++语言编写。旨在为Web应用提供可扩展的高性能数据存储解决方案。应用性能高低依赖于数据库性能,MongoDB则是非关系数据库中功能最丰富,最像关系数据库的,随着MongDB 3.4版本发布,其应用场景适用能力得到了进一步拓展。 MongoDB的核心优势就是灵活的文档模型、高可用复制集、可扩展分片集群。你可以试着从几大方面了解MongoDB,如实时监控MongoDB工具、内存使用量和页面错误、连接数、数据库操作、复制集等。 2、Elasticsearch ——为云构建的分布式RESTful搜索引擎。 ElasticSearch是基于Lucene的搜索服务器。它提供了分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是比较流行的企业级搜索引擎。 ElasticSearch不仅是一个全文本搜索引擎,还是一个分布式实时文档存储,其中每个field均是被索引的数据且可被搜索;也是一个带实时分析功能的分布式搜索引擎,并且能够扩展至数以百计的服务器存储及处理PB级的数据。ElasticSearch在底层利用Lucene完成其索引功能,因此其许多基本概念源于Lucene。 3、Cassandra——开源分布式数据库管理系统。 最初是由Facebook开发的,旨在处理许多商品服务器上的大量数据,提供高可用性,没有单点故障。 Apache Cassandra是一套开源分布式NoSQL数据库系统。集Google BigTable的数据模型与Amazon Dynamo的完全分布式架构于一身。于2008开源,此后,由于Cassandra良好的可扩展性,被Digg、Twitter等Web 2.0网站所采纳,成为了一种流行的分布式结构化数据存储方案。 因Cassandra是用Java编写的,所以理论上在具有JDK6及以上版本的机器中都可以运行,官方测试的JDK还有OpenJDK 及Sun的JDK。 Cassandra的操作命令,类似于我们平时操作的关系数据库,对于熟悉MySQL的朋友来说,操作会很容易上手。 4、Redis ——开源(BSD许可)内存数据结构存储,用作数据库,缓存和消息代理。 Redis是一个开源的使用ANSI C语言编写的、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。Redis 有三个主要使其有别于其它很多竞争对手的特点:Redis是完全在内存中保存数据的数据库,使用磁盘只是为了持久性目的; Redis相比许多键值数据存储系统有相对丰富的数据类型; Redis可以将数据复制到任意数 5、Hazelcast ——基于Java的开源内存数据网格。 Hazelcst 是一种内存数据网格 in-memory data grid,提供Java程序员关键任务交易和万亿级内存应用。虽然Hazelcast没有所谓的“Master”,但是仍然有一个Leader节点(the oldest member),这个概念与ZooKeeper中的Leader类似,但是实现原理却完全不同。同时,Hazelcast中的数据是分布式的,每一个member持有部分数据和相应的backup数据,这点也与ZooKeeper不同。 Hazelcast的应用便捷性深受开发者喜欢,但如果要投入使用,还需要慎重考虑。 6、Ehcache——广泛使用的开源Java分布式缓存。 主要面向通用缓存、Java EE和轻量级容器。 EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是hibernate中默认的CacheProvider。主要特性有:快速简单,具有多种缓存策略;缓存数据有两级,内存和磁盘,因此无需担心容量问题;缓存数据会在虚拟机重启的过程中写入磁盘;可以通过RMI、可插入API等方式进行分布式缓存;具有缓存和缓存管理器的侦听接口;支持多缓存管理器实例,以及一个实例的多个缓存区域;提供Hibernate的缓存实现。 7、Hadoop ——用Java编写的开源软件框架。 用于分布式存储,并对非常大的数据用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群进行高速运算和存储。Hadoop实现了一个分布式文件系统(Hadoop Distributed File System),简称HDFS。Hadoop的框架最核心的设计就是:HDFS和MapReduce。HDFS为海量的数据提供了存储,MapReduce则为海量的数据提供了计算。 8、Solr ——开源企业搜索平台,用Java编写,来自Apache Lucene项目。 Solr是一个独立的企业级搜索应用服务器,它对外提供类似于Web-service的API接口。用户可以通过http请求,向搜索引擎服务器提交一定格式的XML文件,生成索引;也可以通过Http Get操作提出查找请求,并得到XML格式的返回结果。 与ElasticSearch一样,同样是基于Lucene,但它对其进行了扩展,提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展并对查询性能进行了优化。 9、Spark ——Apache Software Foundation中最活跃的项目,是一个开源集群计算框架。 Spark 是一种与 Hadoop 相似的开源集群计算环境,但是两者之间还存在一些不同之处,这些不同之处使 Spark 在某些工作负载方面表现得更加优越,换句话说,Spark 启用了内存分布数据集,除了能够提供交互式查询外,它还可以优化迭代工作负载。 Spark 是在 Scala 语言中实现的,它将 Scala 用作其应用程序框架。与 Hadoop 不同,Spark 和 Scala 能够紧密集成,其中的 Scala 可以像操作本地集合对象一样轻松地 10、Memcached ——通用分布式内存缓存系统。 Memcached是一套分布式快取系统,当初是Danga Interactive为了LiveJournal所发展的,但被许多软件(如MediaWiki)所使用。Memcached作为高速运行的分布式缓存服务器,具有以下的特点:协议简单,基于libevent的事件处理,内置内存存储方式。 ———————————————— 原文链接:https://blog.csdn.net/qq_43556680/article/details/142631934
-
一、Spring框架概述 Spring是轻量级的开源的JAVAEE框架 Spring可以解决企业应用开发的复杂性 Spring有两个核心部分:IOC和Aop IOC:控制反转,把创建对象的过程交个Spring进行管理 Aop:面向切面,不修改源代码进行功能增强 Spring特点 方便解耦,简化开发 Aop编程支持 方便程序测试 方便和其他框架进行整合 方便进行事务操作 降低API的使用难度 IOC容器 IOC底层原理 IOC接口(BeanFactory) IOC操作Bean管理(基于XML) IOC操作Bean管理(基于注解) 二、IOC概念和原理 2.1、什么是IOC 控制反转,把对象创建和对象之间调用过程,交给Spring进行管理 使用IOC目的:为了耦合度降低 IOC底层原理 xml解析,工厂模式,反射 2.2、IOC接口 IOC思想基于IOC容器完成,IOC容器底层就是对象工厂 Spring提供IOC容器实现两种方式:(两个接口) 2.1 BeanFactory: IOC容器基本实现方式,是spring内部使用接口,不提供开发人员进行使用 加载配置文件不会创建对象,在获取对象才去创建对象 2.2 ApplicationContext: BeanFactory接口的子接口,提供更多更强大的功能,一般是由开发人员进行使用 加载配置文件时候就会把配置文件对象进行创建 使用ApplicationContext把加载过程交给启动服务器,不要留给运行中。 ApplicationContext接口有实现类 盘路径 类路径 IOC操作Bean管理 什么是Bean管理 Spring创建对象 Spring注入属性 Bean管理操作有两种方式 基于xml配置文件方式实现 基于注解方式实现 IOC操作Bean管理(xml) 基于xml方式创建队形 在Spring配置文件中,使用bean标签,标签里面添加对应属性,就可以实现对象创建 在bean标签有很多属性,常用属性: id属性:唯一标识 class属性:类全路径 创建对象时候,默认是执行无参构造 基于xml方式注入属性 DI:依赖注入,注入属性 使用set方法注入 属性:类全路径 创建对象时候,默认是执行无参构造 三、深入理解Java基础中的集合框架 Java集合框架 (Java Collections Framework, JCF) 也称容器,这里可以类比 C++ 中的 STL,在市面上似乎还没能找到一本详细介绍的书籍。在这里主要对如下部分进行源码分析,及在面试中常见的问题。 例如,在阿里面试常问到的 HashMap 和 ConcurrentHashMap 原理等等。 Java集合框架提供了数据持有对象的方式,提供了对数据集合的操作。Java 集合框架位于java.util包下,主要有三个大类:Collection(接口)、Map(接口)、集合工具类。 3.1、Collection ArrayList:线程不同步。默认初始容量为 10,当数组大小不足时容量扩大为 1.5 倍。为追求效率,ArrayList 没有实现同步(synchronized),如果需要多个线程并发访问,用户可以手动同步,也可使用 Vector 替代。 LinkedList:线程不同步。双向链接实现。LinkedList 同时实现了 List 接口和 Deque 接口,也就是说它既可以看作一个顺序容器,又可以看作一个队列(Queue),同时又可以看作一个栈(Stack)。这样看来,LinkedList 简直就是个全能冠军。当你需要使用栈或者队列时,可以考虑使用 LinkedList,一方面是因为 Java 官方已经声明不建议使用 Stack 类,更遗憾的是,Java 里根本没有一个叫做 Queue 的类(它是个接口名字)。关于栈或队列,现在的首选是 ArrayDeque,它有着比 LinkedList(当作栈或队列使用时)有着更好的性能。 Stack and Queue:Java 里有一个叫做 Stack 的类,却没有叫做 Queue 的类(它是个接口名字)。当需要使用栈时,Java 已不推荐使用 Stack,而是推荐使用更高效的 ArrayDeque;既然 Queue 只是一个接口,当需要使用队列时也就首选 ArrayDeque 了(次选是 LinkedList )。 Vector:线程同步。默认初始容量为 10,当数组大小不足时容量扩大为 2 倍。它的同步是通过Iterator方法加synchronized实现的。 Stack:线程同步。继承自 Vector,添加了几个方法来完成栈的功能。现在已经不推荐使用 Stack,在栈和队列中有限使用 ArrayDeque,其次是 LinkedList。 TreeSet:线程不同步,内部使用NavigableMap操作。默认元素 “自然顺序” 排列,可以通过Comparator改变排序。TreeSet 里面有一个 TreeMap(适配器模式) HashSet:线程不同步,内部使用 HashMap 进行数据存储,提供的方法基本都是调用 HashMap 的方法,所以两者本质是一样的。集合元素可以为 NULL。 Set:Set 是一种不包含重复元素的 Collection,Set 最多只有一个 null 元素。Set 集合通常可以通过 Map 集合通过适配器模式得到。 PriorityQueue:Java 中 PriorityQueue 实现了 Queue 接口,不允许放入 null 元素;其通过堆实现,具体说是通过完全二叉树(complete binary tree)实现的小顶堆(任意一个非叶子节点的权值,都不大于其左右子节点的权值),也就意味着可以通过数组来作为 PriorityQueue 的底层实现。 优先队列的作用是能保证每次取出的元素都是队列中权值最小的(Java 的优先队列每次取最小元素,C++ 的优先队列每次取最大元素)。这里牵涉到了大小关系,元素大小的评判可以通过元素本身的自然顺序(natural ordering),也可以通过构造时传入的比较器(Comparator,类似于 C++ 的仿函数)。 NavigableSet:添加了搜索功能,可以对给定元素进行搜索:小于、小于等于、大于、大于等于,放回一个符合条件的最接近给定元素的 key。 EnumSet:线程不同步。内部使用 Enum 数组实现,速度比HashSet快。只能存储在构造函数传入的枚举类的枚举值。 3.2、Map TreeMap:线程不同步,基于红黑树(Red-Black tree)的 NavigableMap 实现,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。 TreeMap 底层通过红黑树(Red-Black tree)实现,也就意味着containsKey(),get(),put(),remove()都有着log(n)的时间复杂度。其具体算法实现参照了《算法导论》。 HashTable:线程安全,HashMap 的迭代器 (Iterator) 是fail-fast迭代器。HashTable 不能存储 NULL 的 key 和 value。 HashMap:线程不同步。根据key的hashcode进行存储,内部使用静态内部类Node的数组进行存储,默认初始大小为 16,每次扩大一倍。当发生 Hash 冲突时,采用拉链法(链表)。JDK 1.8中:当单个桶中元素个数大于等于8时,链表实现改为红黑树实现;当元素个数小于6时,变回链表实现。由此来防止hashCode攻击。 Java HashMap 采用的是冲突链表方式。 HashMap 是 Hashtable 的轻量级实现,可以接受为 null 的键值 (key) 和值 (value),而 Hashtable 不允许。 LinkedHashMap:保存了记录的插入顺序,在用 Iterator 遍历 LinkedHashMap 时,先得到的记录肯定是先插入的。也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比 HashMap 慢,不过有种情况例外,当 HashMap 容量很大,实际数据较少时,遍历起来可能会比 LinkedHashMap 慢,因为 LinkedHashMap 的遍历速度只和实际数据有关,和容量无关,而 HashMap 的遍历速度和他的容量有关。 WeakHashMap:从名字可以看出它是某种 Map。它的特殊之处在于 WeakHashMap 里的 entry 可能会被 GC 自动删除,即使程序员没有调用remove()或者clear()方法。 WeakHashMap 的存储结构类似于HashMap 既然有 WeekHashMap,是否有 WeekHashSet 呢?答案是没有!不过 Java Collections 工具类给出了解决方案,Collections.newSetFromMap(Map<E,Boolean> map)3.3、集合工具类 Collections、Arrays:集合类的一个工具类帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。 Comparable、Comparator:一般是用于对象的比较来实现排序,两者略有区别。 类设计者没有考虑到比较问题而没有实现 Comparable 接口。这是我们就可以通过使用 Comparator,这种情况下,我们是不需要改变对象的。 一个集合中,我们可能需要有多重的排序标准,这时候如果使用 Comparable 就有些捉襟见肘了,可以自己继承 Comparator 提供多种标准的比较器进行排序。 说明:线程不同步的时候可以通过,Collections.synchronizedList() 方法来包装一个线程同步方法 四、练习写一个SpringMVC框架 1、介绍 熟悉SpringMVC框架的同学一定清楚下面这张图, 这张图就是 SpringMVC 在处理 http 请求的整个流程中所做的一些事情。 1、用户发送请求至前端控制器DispatcherServlet 2、DispatcherServlet收到请求调用HandlerMapping处理器映射器。 3、处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。 4、DispatcherServlet通过HandlerAdapter处理器适配器调用处理器 5、执行处理器(Controller,也叫后端控制器)。 6、Controller执行完成返回ModelAndView 7、HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet 8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器 9、ViewReslover解析后返回具体View 10、DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。 11、DispatcherServlet响应用户。 DispatcherServlet 主要承担接收请求、响应结果、转发等作用,剩下的就交给容器来处理! 基于上面的流程,我们可以编写出一款简化版的Spring MVC框架。 2、程序实践 首先上图! 这个就是我们简易版的Spring MVC框架的实现流程图! 1、首先创建一个DispatcherServlet类,在服务启动的时候,读取要扫描的包路径,然后通过反射将类信息存储到ioc容器,同时通过@Autowired注解,实现自动依赖注入,最后读取@RequestMapping注解中的方法,将映射路径与类的关系存储到映射容器中。 2、当用户发起请求的时候,通过请求路径到映射容器中找到对应的执行类,然后调用具体的方法,发起逻辑处理,最后将处理结果返回给前端用户! 以下是具体实践过程! 2.1、创建扫描注解 因为Spring MVC基本全部都是基于注解开发,因此我们事先也需要创建对应的注解,各个含义与Spring MVC一致! 控制层注解 /** * 控制层注解 * @Controller */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Controller { String value() default ""; } 请求路径注解 /** * 请求路径注解 * @RequestMapping */ @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestMapping { String value() default ""; }参数注解 /** * 参数注解 * @RequestParam */ @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestParam { String value() default ""; } 服务层注解 /** * 服务层注解 * @Controller */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Service { String value() default ""; } 自动装载注解 /** * 自动装载注解 * @Autowrited */ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { String value() default ""; } 2.2、编写 DispatcherServlet 类 DispatcherServlet是一个Servlet类,主要承担的任务是:接受前端用户的请求,然后进行转发,最后响应结果给前端用户! 详细代码如下: /** * servlet跳转层 */ @WebServlet(name = "DispatcherServlet",urlPatterns = "/*", loadOnStartup = 1, initParams = {@WebInitParam(name="scanPackage", value="com.example.mvc")}) public class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final Logger logger = LoggerFactory.getLogger(DispatcherServlet.class); /**请求方法映射容器*/ private static List<RequestHandler> handlerMapping = new ArrayList<>(); /** * 服务启动的时候,进行初始化,流程如下: * 1、扫描指定包下所有的类 * 2、通过反射将类实例,放入ioc容器 * 3、通过Autowired注解,实现自动依赖注入,也就是set类中的属性 * 4、通过RequestMapping注解,获取需要映射的所有方法,然后将类信息存放到容器中 * @param config * @throws ServletException */ @Override public void init(ServletConfig config) throws ServletException { try { //1、扫描指定包下所有的类 String scanPackage = config.getInitParameter("scanPackage"); //1、扫描指定包下所有的类 List<String> classNames = doScan(scanPackage); //2、初始化所有类实例,放入ioc容器,也就是map对象中 Map<String, Object> iocMap = doInstance(classNames); //3、实现自动依赖注入 doAutowired(iocMap); //5、初始化方法mapping initHandleMapping(iocMap); } catch (Exception e) { logger.error("dispatcher-servlet类初始化失败!",e); throw new ServletException(e.getMessage()); } } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { doPost(request, response); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { //跳转 doDispatch(request, response); } /** * 扫描指定包下的类文件 * @param packageName * @return */ private List<String> doScan(String packageName){ if(StringUtils.isBlank(packageName)){ throw new RuntimeException("mvc配置文件中指定扫描包名为空!"); } return PackageHelper.getClassName(packageName); } private Map<String, Object> doInstance(List<String> classNames) { Map<String, Object> iocMap = new HashMap<>(); if(!CollectionUtils.isNotEmpty(classNames)){ throw new RuntimeException("获取的类为空!"); } for (String className : classNames) { try { //通过反射机制构造对象 Class<?> clazz = Class.forName(className); if(clazz.isAnnotationPresent(Controller.class)){ //将类名第一个字母小写 String baneName = firstLowerCase(clazz.getSimpleName()); iocMap.put(baneName, clazz.newInstance()); }else if(clazz.isAnnotationPresent(Service.class)){ //服务层注解判断 Service service = clazz.getAnnotation(Service.class); String beanName = service.value(); //如果该注解上没有自定义类名,则默认首字母小写 if(StringUtils.isBlank(beanName)){ beanName = clazz.getName(); } Object instance = clazz.newInstance(); iocMap.put(beanName, instance); //如果注入的是接口,可以巧妙的用接口的类型作为key Class<?>[] interfaces = clazz.getInterfaces(); for (Class<?> clazzInterface : interfaces) { iocMap.put(clazzInterface.getName(), instance); } } } catch (Exception e) { logger.error("初始化mvc-ioc容器失败!",e); throw new RuntimeException("初始化mvc-ioc容器失败!"); } } return iocMap; } /** * 实现自动依赖注入 * @throws Exception */ private void doAutowired(Map<String, Object> iocMap) { if(!MapUtils.isNotEmpty(iocMap)){ throw new RuntimeException("初始化实现自动依赖失败,ioc为空!"); } for(Map.Entry<String, Object> entry : iocMap.entrySet()){ //获取对象下所有的属性 Field[] fields = entry.getValue().getClass().getDeclaredFields(); for (Field field : fields) { //判断字段上有没有@Autowried注解,有的话才注入 if(field.isAnnotationPresent(Autowired.class)){ try { Autowired autowired = field.getAnnotation(Autowired.class); //获取注解上有没有自定义值 String beanName = autowired.value().trim(); if(StringUtils.isBlank(beanName)){ beanName = field.getType().getName(); } //如果想要访问到私有的属性,我们要强制授权 field.setAccessible(true); field.set(entry.getValue(), iocMap.get(beanName)); } catch (Exception e) { logger.error("初始化实现自动依赖注入失败!",e); throw new RuntimeException("初始化实现自动依赖注入失败"); } } } } } /** * 初始化方法mapping */ private void initHandleMapping(Map<String, Object> iocMap){ if(!MapUtils.isNotEmpty(iocMap)){ throw new RuntimeException("初始化实现自动依赖失败,ioc为空"); } for(Map.Entry<String, Object> entry:iocMap.entrySet()){ Class<?> clazz = entry.getValue().getClass(); //判断是否是controller层 if(!clazz.isAnnotationPresent(Controller.class)){ continue; } String baseUrl = null; //判断类有没有requestMapping注解 if(clazz.isAnnotationPresent(RequestMapping.class)){ RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class); baseUrl= requestMapping.value(); } Method[] methods = clazz.getMethods(); for (Method method : methods) { //判断方法上有没有requestMapping if(!method.isAnnotationPresent(RequestMapping.class)){ continue; } RequestMapping requestMethodMapping = method.getAnnotation(RequestMapping.class); //"/+",表示将多个"/"转换成"/" String regex = (baseUrl + requestMethodMapping.value()).replaceAll("/+", "/"); Pattern pattern = Pattern.compile(regex); handlerMapping.add(new RequestHandler(pattern, entry.getValue(), method)); } } } /** * servlet请求跳转 * @param request * @param response * @throws IOException */ private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws IOException { try { request.setCharacterEncoding("UTF-8"); response.setHeader("Cache-Control", "no-cache"); response.setHeader("Pragma", "no-cache"); response.setDateHeader("Expires", -1); response.setContentType("text/html"); response.setHeader("content-type", "text/html;charset=UTF-8"); response.setCharacterEncoding("UTF-8"); RequestHandler handle = getHandleMapping(request); if(Objects.isNull(handle)){ //异常请求地址 logger.warn("异常请求地址!地址:" + request.getRequestURI()); response.getWriter().append("error request url"); return; } //获取参数列表 Object[] paramValues = RequestParamHelper.buildRequestParam(handle, request, response); Object result = handle.getMethod().invoke(handle.getController(), paramValues); if(result != null){ PrintWriter out = response.getWriter(); out.println(result); out.flush(); out.close(); } } catch (Exception e) { logger.error("接口请求失败!",e); PrintWriter out = response.getWriter(); out.println("请求异常,请稍后再试"); out.flush(); out.close(); } } /** * 将类名第一个字母小写 * @param clazzName * @return */ private String firstLowerCase(String clazzName){ char[] chars = clazzName.toCharArray(); chars[0] += 32; return String.valueOf(chars); } /** * 获取用户请求方法名 * 与handlerMapping中的路径名进行匹配 * @param request * @return */ private RequestHandler getHandleMapping(HttpServletRequest request){ if(CollectionUtils.isNotEmpty(handlerMapping)){ //获取用户请求路径 String url = request.getRequestURI(); String contextPath = request.getContextPath(); String serviceUrl = url.replace(contextPath, "").replaceAll("/+", "/"); for (RequestHandler handle : handlerMapping) { //正则匹配请求方法名 Matcher matcher = handle.getPattern().matcher(serviceUrl); if(matcher.matches()){ return handle; } } } return null; } } 这里要重点介绍一下初始化阶段所做的操作! DispatcherServlet在服务启动阶段,会调用init方法进行服务初始化,此阶段所做的事情主要有以下内容: 1、扫描指定包下所有的类信息,返回的结果主要是包名 + 类名 2、通过反射机制,将类进行实例化,将类实例化对象存储到ioc容器中,其中key是类名(小些驼峰),value是类对象 3、通过Autowired注解找到类对象中的属性,通过小驼峰从ioc容器中寻找对应的属性值,然后进行set操作 4、通过Controller和RequestMapping注解寻找需要暴露的方法,并获取对应的映射路径,最后将映射路径 5、最后,当前端用户发起一个请求时,DispatcherServlet获取到请求路径之后,通过与RequestMapping中的路径进行匹配,找到对应的controller类中的方法,然后通过invoke完成方法调用,将调用结果返回给前端! 2.3、编写 controller 类 当DispatcherServlet编写完成之后,紧接着我们需要编写对应的controller控制类来接受前端用户请求,下面我们以用户登录为例,程序示例如下: 编写一个LoginController控制类,接受前端用户调用 @Controller @RequestMapping("/user") public class LoginController { @Autowired private UserService userService; /** * 用户登录 * @param request * @param response * @param userName * @param userPwd * @return */ @RequestMapping("/login") public String login(HttpServletRequest request, HttpServletResponse response, @RequestParam("userName") String userName, @RequestParam("userPwd") String userPwd){ boolean result = userService.login(userName, userPwd); if(result){ return "登录成功!"; } else { return "登录失败!"; } } } 编写一个UserService服务类,用于判断账户、密码是否正确 public interface UserService { /** * 登录 * @param userName * @param userPwd * @return */ 最后,将项目打包成war,通过tomcat启动服务! 在浏览器中访问http://localhost:8080/user/login?userName=hello&userPwd=123,结果显示如下: 当我们将userName和userPwd换成正确的数据,访问地址如下:http://localhost:8080/user/login?userName=zhangsan&userPwd=12345可以很清晰的看到,服务调用正常! 3、总结 本文主要以Spring MVC框架为背景,手写了一个简易版的Spring MVC框架,虽然功能简陋了一点,但是基本无张俱全,里面讲解了ioc和自动依赖注入的实现过程,还有前端发起一个路径请求,是如何映射到对应的controller类中的方法上! 当然实际的Spring MVC框架的跳转流程比这个复杂很多很多,里面包括各种拦截器、权限安全管理等等。 ———————————————— 原文链接:https://blog.csdn.net/qq_43556680/article/details/142631934
-
Spring 什么是Spring Spring是一个开源框架,为简化企业级开发而生。它以IOC(控制反转)和AOP(面向切面)为思想内核,提供了控制层SpringMVC、数据层SpringData、服务层事务管理等众多技术,并可以整合众多第三方框架。 Spring将很多复杂的代码变得优雅简洁,有效的降低代码的耦合度,极大的方便项目的后期维护、升级和扩展。 Spring官网地址:https://spring.io/.\ Spring体系结构 Spring框架根据不同的功能被划分成了多个模块,这些模块可以满足一切企业级应用开发的需求,在开发过程中可以根据需求有选择性地使用所需要的模块。 Core Container:Spring核心模块,任何功能的使用都离不开该模块,是其他模块建立的基础。 Data Access/Integration:该模块提供了数据持久化的相应功能。 Web:该模块提供了web开发的相应功能。 AOP:提供了面向切面编程实现 Aspects:提供与AspectJ框架的集成,该框架是一个面向切面编程框架。 Instrumentation:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。 Messaging:为Spring框架集成一些基础的报文传送应用 dTest:提供与测试框架的集成 IOC__控制反转思想 IOC(Inversion of Control) :程序将创建对象的权利交给框架。 之前在开发过程中,对象实例的创建是由调用者管理的,代码如下 public interface StudentDao { // 根据id查询学生 Student findById(int id); } public class StudentDaoImpl implements StudentDao{ @Override public Student findById(int id) { // 模拟从数据库查找出学生 return new Student(1,"zbdx","山西"); } }public class StudentService { public Student findStudentById(int id){ // 此处就是调用者在创建对象 StudentDao studentDao = new StudentDaoImpl(); return studentDao.findById(1); } } 这种写法有两个缺点: 浪费资源:StudentService调用方法时即会创建一个对象,如果不断调用方法则会创建大量StudentDao对象。 代码耦合度高:假设随着开发,我们创建了StudentDao另一个更加完善的实现类StudentDaoImpl2,如果在StudentService中想使用StudentDaoImpl2,则必须修改源码。 而IOC思想是将创建对象的权利交给框架,框架会帮助我们创建对象,分配对象的使用,控制权由程序代码转移到了框架中,控制权发生了反转,这就是Spring的IOC思想。而IOC思想可以完美的解决以上两个问题。 自定义对象容器 接下来我们通过一段代码模拟IOC思想。创建一个集合容器,先将对象创建出来放到容器中,需要使用对象时,只需要从容器中获取对象即可,而不需要重新创建,此时容器就是对象的管理者。 1.创建实体类 public class Student { private int id; private String name; private String address; // 省略getter/setter/构造方法/tostring } 2.创建Dao接口和实现类 public interface StudentDao { // 根据id查询学生 Student findById(int id); } public class StudentDaoImpl implements StudentDao { @Override public Student findById(int id) { // 模拟从数据库查找出学生 return new Student(1,"zbdx","山西"); } } public class StudentDaoImpl2 implements StudentDao { @Override public Student findById(int id) { // 模拟从数据库查找出学生 System.out.println("新方法!!!"); return new Student(1,"zbdx","山西"); } } 3.创建配置文件bean.properties,该文件中定义管理的对象 4.创建容器管理类,该类在类加载时读取配置文件,将配置文件中配置的对象全部创建并放入容器中。 public class Container { static Map<String,Object> map = new HashMap<>(); static { // 读取配置文件 InputStream is = Container.class.getClassLoader().getResourceAsStream("bean.properties"); Properties properties = new Properties(); try { properties.load(is); } catch (IOException e) { throw new RuntimeException(e); } // 遍历配置文件的所有配置信息 Enumeration<Object> keys = properties.keys(); while (keys.hasMoreElements()){ String key = keys.nextElement().toString(); String value = properties.getProperty(key); try { // 创建对象,将对象放入集合 Object o = Class.forName(value).newInstance(); map.put(key,o); } catch (Exception e) { e.printStackTrace(); } } } // 从容器中获取对象 public static Object getBean(String key){ return map.get(key); } } 创建Dao对象的调用者StudentService public class StudentService { public Student findStudentById(int id){ StudentDao studentDao = (StudentDao) Container.getBean("studentDao"); System.out.println(studentDao.hashCode()); return studentDao.findById(id); } } 测试StudentService public class Test { public static void main(String[] args) { StudentService studentService = new StudentService(); System.out.println(studentService.findStudentById(1)); System.out.println(studentService.findStudentById(1)); } } 测试结果: StudentService从容器中每次拿到的都是同一个StudentDao对象,节约了资源。 如果想使用StudentDaoImpl2对象,只需要修改bean.properties的内容为 studentDao=com.itbaizhan.dao.StudentDaoImpl2 即可,无需修改Java源码。 Spring实现IOC 接下来我们使用Spring实现IOC,Spring内部也有一个容器用来管理对象。 1.创建Maven工程,引入依赖 <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.11</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> 2.创建POJO类、Dao类和接口 public class Student { private int id; private String name; private String address; // 省略getter/setter/构造方法/tostring } public interface StudentDao { // 根据id查询学生 Student findById(int id); } public class StudentDaoImpl implements StudentDao{ @Override public Student findById(int id) { // 模拟从数据库查找出学生 return new Student(1,"zbdx","山西"); } } 3.编写xml配置文件,配置文件中配置需要Spring帮我们创建的对象。 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="studentDao" class="com.jjy.dao.StudentDaoImpl"></bean> </beans> 4.测试从Spring容器中获取对象。 public class TestContainer { @Test public void t1(){ // 创建Spring容器 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); // 从容器获取对象 StudentDao studentDao1 = (StudentDao) ac.getBean("studentDao"); StudentDao studentDao2 = (StudentDao) ac.getBean("studentDao"); System.out.println(studentDao1.hashCode()); System.out.println(studentDao2.hashCode()); System.out.println(studentDao1.findById(1)); } } Spring容器类型 容器接口 BeanFactory:BeanFactory是Spring容器中的顶层接口,它可以对Bean对象进行管理。 ApplicationContext:ApplicationContext是BeanFactory的子接口。它除了继承 BeanFactory的所有功能外,还添加了对国际化、资源访问、事件传播等方面的良好支持。 ApplicationContext有以下三个常用实现类: 容器实现类 ClassPathXmlApplicationContext:该类可以从项目中读取配置文件 FileSystemXmlApplicationContext:该类从磁盘中读取配置文件 AnnotationConfigApplicationContext:使用该类不读取配置文件,而是会读取注解 @Test public void t2(){ // 创建spring容器 // ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); ApplicationContext ac = new FileSystemXmlApplicationContext("C:\\Users\\a\\IdeaProjects\\spring_demo\\src\\main\\resources\\bean.xml"); // 从容器中获取对象 StudentDao userDao = (StudentDao) ac.getBean("studentDao"); System.out.println(userDao); System.out.println(userDao.findById(1)); } 对象的创建方式 Spring会帮助我们创建bean,那么它底层是调用什么方法进行创建的呢? 使用构造方法 Spring默认使用类的空参构造方法创建bean: // 假如类没有空参构造方法,将无法完成bean的创建 public class StudentDaoImpl implements StudentDao{ public StudentDaoImpl(int a){} @Override public Student findById(int id) { // 模拟根据id查询学生 return new Student(1,"zbdx","山西"); } } 使用工厂类的方法 Spring可以调用工厂类的方法创建bean: 1.创建工厂类,工厂类提供创建对象的方法: public class StudentDaoFactory { public StudentDao getStudentDao(){ return new StudentDaoImpl(1); } } 2.在配置文件中配置创建bean的方式为工厂方式。 <!-- id:工厂对象的id,class:工厂类 --> <bean id="studentDaoFactory" class="com.jjy.dao.StudentDaoFactory"></bean> <!-- id:bean对象的id,factory-bean:工厂对象的id,factory-method:工厂方法 --> <bean id="studentDao" factory-bean="studentDaoFactory" factory-method="getStudentDao"></bean> 使用工厂类的静态方法 Spring可以调用工厂类的静态方法创建bean: 1.创建工厂类,工厂提供创建对象的静态方法。 public class StudentDaoFactory2 { public static StudentDao getStudentDao2() { return new StudentDao 1Impl(); } }2.在配置文件中配置创建bean的方式为工厂静态方法。 <!-- id:bean的id class:工厂全类名 factory-method:工厂静态方法 --> <bean id="studentDao" class="com.jjy.dao.StudentDaoFactory2" factory-method="getStudentDao2"></bean> 对象的创建策略 Spring通过配置<bean>中的scope属性设置对象的创建策略,共有五种创建策略: singleton:单例,默认策略。整个项目只会创建一个对象,通过<bean>中的lazy-init属性可以设置单例对象的创建时机: lazy-init=“false”(默认):立即创建,在容器启动时会创建配置文件中的所有Bean对象。 lazy-init=“true”:延迟创建,第一次使用Bean对象时才会创建。 配置单例策略: <!-- <bean id="studentDao" class="com.itbaizhan.dao.StudentDaoImpl2" scope="singleton" lazy-init="true"></bean> --> <bean id="studentDao" class="com.jjy.dao.StudentDaoImpl2" scope="singleton" lazy-init="false"></bean> 测试单例策略: // 为Bean对象的类添加构造方法 public StudentDaoImpl2(){ System.out.println("创建StudentDao!!!"); } @Test public void t2(){ // 创建Spring容器 ApplicationContext ac = new ClassPathXmlApplicationContext("bean1.xml"); // 从容器获取对象 StudentDao studentDao1 = (StudentDao) ac.getBean("studentDao"); StudentDao studentDao2 = (StudentDao) ac.getBean("studentDao"); StudentDao studentDao3 = (StudentDao) ac.getBean("studentDao"); System.out.println(studentDao1.hashCode()); System.out.println(studentDao2.hashCode()); System.out.println(studentDao3.hashCode()); } prototype:多例,每次从容器中获取时都会创建对象。 <!-- 配置多例策略 --> <bean id="studentDao" class="com.itbaizhan.dao.StudentDaoImpl2" scope="prototype"></bean> request:每次请求创建一个对象,只在web环境有效。 session:每次会话创建一个对象,只在web环境有效。 gloabal-session:一次集群环境的会话创建一个对象,只在web环境有效。 对象的创建策略不同,销毁时机也不同: singleton:对象随着容器的销毁而销毁。 prototype:使用JAVA垃圾回收机制销毁对象。 request:当处理请求结束,bean实例将被销毁。 session:当HTTP Session最终被废弃的时候,bean也会被销毁掉。 gloabal-session:集群环境下的session销毁,bean实例也将被销毁。 对象的生命周期 Bean对象的生命周期包含创建——使用——销毁,Spring可以配置Bean对象在创建和销毁时自动执行的方法: 定义生命周期方法 public class StudentDaoImpl2 implements StudentDao{ // 创建时自动执行的方法 public void init(){ System.out.println("创建StudentDao!!!"); } // 销毁时自动执行的方法 public void destory(){ System.out.println("销毁StudentDao!!!"); } } 配置生命周期方法 <!-- init-method:创建对象时执行的方法 destroy-method:销毁对象时执行的方法 --> <bean id="studentDao" class="com.jjy.dao.StudentDaoImpl2" scope="singleton" init-method="init" destroy-method="destory"></bean> 测试 @Test public void t3(){ // 创建Spring容器 ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean1.xml"); // 销毁Spring容器,ClassPathXmlApplicationContext才有销毁容器的方法 ac.close(); } 获取对象的方式 Spring有多种获取容器中对象的方式: 1.通过id/name获取 配置文件 <bean name="studentDao" class="com.jjy.dao.StudentDaoImpl2"></bean> <bean id="studentDao" class="com.jjy.dao.StudentDaoImpl2"></bean>获取对象 StudentDao studentDao = (StudentDao) ac.getBean("studentDao"); 通过类型获取 配置文件 <bean name="studentDao" class="com.jjy.dao.StudentDaoImpl2"></bean> 获取对象 StudentDao studentDao2 = ac.getBean(StudentDao.class); 可以看到使用类型获取不需要强转。 2.通过类型+id/name获取 虽然使用类型获取不需要强转,但如果在容器中有一个接口的多个实现类对象,则获取时会报错,此时需要使用类型+id/name获取 配置文件 <bean name="studentDao" class="com.itbaizhan.dao.StudentDaoImpl2"></bean> <bean name="studentDao1" class="com.itbaizhan.dao.StudentDaoImpl"></bean> 获取对象 StudentDao studentDao2 = ac.getBean("studentDao",StudentDao.class); DI_依赖注入 什么是依赖注入 依赖注入(Dependency Injection,简称DI),它是Spring控制反转思想的具体实现。 控制反转将对象的创建交给了Spring,但是对象中可能会依赖其他对象。比如service类中要有dao类的属性,我们称service依赖于dao。之前需要手动注入属性值,代码如下: public interface StudentDao { Student findById(int id); } public class StudentDaoImpl implements StudentDao{ @Override public Student findById(int id) { // 模拟根据id查询学生 return new Student(1,"jjy","北京"); } } public class StudentService { // service依赖dao,手动注入属性值,即手动维护依赖关系 private StudentDao studentDao = new StudentDaoImpl(); public Student findStudentById(int id){ return studentDao.findById(id); } } 此时,当StudentService的想要使用StudentDao的另一个实现类如StudentDaoImpl2时,则需要修改Java源码,造成代码的可维护性降低。 而使用Spring框架后,Spring管理Service对象与Dao对象,此时它能够为Service对象注入依赖的Dao属性值。这就是Spring的依赖注入。简单来说,控制反转是创建对象,依赖注入是为对象的属性赋值。 Setter注入 在之前开发中,我们可以通过setter方法或构造方法设置对象属性值: // setter方法设置属性 StudentService studentService = new StudentService(); StudentDao studentDao = new StudentDao(); studentService.setStudentDao(studentDao); // 构造方法设置属性 StudentDao studentDao = new StudentDao(); StudentService studentService = new StudentService(studentDao);Spring可以通过调用setter方法或构造方法给属性赋值 被注入类编写属性的setter方法 public class StudentService { private StudentDao studentDao; public void setStudentDao(StudentDao studentDao) { this.studentDao = studentDao; } public Student findStudentById(int id){ return studentDao.findById(id); } } 编写Java代码 准备数据库 CREATE DATABASE `student`; USE `student`; DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `sex` varchar(10) DEFAULT NULL, `address` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; insert into `student`(`id`,`name`,`sex`,`address`) values (1,'中南大学','男','北京'),(2,'中北大学','女','北京'); 创建Spring配置文件applicationContext.xml,注册定时任务方法。 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd"> <context:component-scan base-package="com.jjy"></context:component-scan> <!-- 配置执行定时任务的对象、方法、时间 --> <task:scheduled-tasks> <task:scheduled ref="mySpringTask" method="printTime" cron="* * * * * *" /> </task:scheduled-tasks> </beans> 编写测试类,读取Spring配置文件,开启定时任务 public class Test { public static void main(String[] args) { new ClassPathXmlApplicationContext("applicationContext.xml"); } } Spring Task_Cron表达式 Spring Task依靠Cron表达式配置定时规则。Cron表达式是一个字符串,分为6或7个域,每一个域代表一个含义,以空格隔开。有如下两种语法格式: Seconds Minutes Hours DayofMonth Month DayofWeek Year Seconds Minutes Hours DayofMonth Month DayofWeek Seconds(秒):域中可出现, - * /四个字符,以及0-59的整数 *:表示匹配该域的任意值,在Seconds域使用,表示每秒钟都会触发 ,:表示列出枚举值。在Seconds域使用5,20,表示在5秒和20秒各触发一次。 -:表示范围。在Seconds域使用5-20,表示从5秒到20秒每秒触发一次 /:表示起始时间开始触发,然后每隔固定时间触发一次。在Seconds域使用5/20, 表示5秒触发一次,25秒,45秒分别触发一次。 Minutes(分):域中可出现, - * /四个字符,以及0-59的整数 Hours(时):域中可出现, - * /四个字符,以及0-23的整数 DayofMonth(日期):域中可出现, - * / ? L W C八个字符,以及1-31的整数 C:表示和当前日期相关联。在DayofMonth域使用5C,表示在5日后的那一天触发,且每月的那天都会触发。比如当前是10号,那么每月15号都会触发。 L:表示最后,在DayofMonth域使用L,表示每个月的最后一天触发。 W:表示工作日,在DayofMonth域用15W,表示最接近这个月第15天的工作日触发,如果15号是周六,则在14号即周五触发;如果15号是周日,则在16号即周一触发;如果15号是周二则在当天触发。 注: 1.该用法只会在当前月计算,不会到下月触发。比如在DayofMonth域用31W,31号是周日,那么会在29号触发而不是下月1号。 2.在DayofMonth域用LW,表示这个月的最后一个工作日触发。 Month(月份):域中可出现, - * /四个字符,以及1-12的整数或JAN-DEC的单词缩写 DayofWeek(星期):可出现, - * / ? L # C八个字符,以及1-7的整数或SUN-SAT 单词缩写,1代表星期天,7代表星期六 C:在DayofWeek域使用2C,表示在2天后的那一天触发,且每周的那天都会触发。比如当前是周一,那么每周三都会触发。 L :在DayofWeek域使用L,表示在一周的最后一天即星期六触发。在DayofWeek域使用5L,表示在一个月的最后一个星期四触发。 #:用来指定具体的周数,#前面代表星期几,#后面代表一个月的第几周,比如5#3表示一个月第三周的星期四。 ?:在无法确定是具体哪一天时使用,用于DayofMonth和DayofWeek域。一般定义了其中一个域,另一个域是不确定的,比如每月20日触发,无法确定20日是星期几,写法如下:0 0 0 20 * ?;或者在每周一触发,此时无法确定该日期是几号,写法如下:0 0 0 ? * 2 Year(年份):域中可出现, - * /四个字符,以及1970~2099的整数。该域可以省略,表示每年都触发。 ———————————————— 原文链接:https://blog.csdn.net/m0_74436895/article/details/140840574
-
使用 spring 的 IoC 的实现账户的CRUD 创建数据库和编写实体类 create table account( id int primary key auto_increment, name varchar(40), money float )character set utf8 collate utf8_general_ci; insert into account(name,money) values('aaa',1000); insert into account(name,money) values('bbb',1000); insert into account(name,money) values('ccc',1000); /** * 账户的实体类 * @author 黑马程序员 * @Company http://www.ithiema.com * @Version 1.0 */ public class Account implements Serializable { private Integer id; private String name; private Float money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Float getMoney() { return money; } public void setMoney(Float money) { this.money = money; } } 编写持久层代码 /** * 账户的持久层接口 * @author 黑马程序员 * @Company http://www.ithiema.com * @Version 1.0 */ public interface IAccountDao { /** * 保存 * @param account */ void save(Account account); /** * 更新 * @param account */ void update(Account account); /** * 删除 * @param accountId */ void delete(Integer accountId); /** * 根据 id 查询 * @param accountId * @return */ Account findById(Integer accountId); /** * 查询所有 * @return */ List<Account> findAll(); /** * 账户的持久层实现类 */ public class AccountDaoImpl implements IAccountDao { private QueryRunner runner; public void setRunner(QueryRunner runner) { this.runner = runner; } public void save(Account account) { try { runner.update("insert into account(name,money)values(?,?)", account.getName(), account.getMoney()); } catch (Exception e) { throw new RuntimeException(e); } } public void update(Account account) { try { runner.update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId()); } catch (Exception e) { throw new RuntimeException(e); } } public void delete(Integer accountId) { try { runner.update("delete from account where id=?", accountId); } catch (Exception e) { throw new RuntimeException(e); } } public Account fingById(Integer accountId) { try { return runner.query("select * from account where id = ? ", new BeanHandler<Account>(Account.class), accountId); } catch (Exception e) { throw new RuntimeException(e); } } public List<Account> findAll() { try { return runner.query("select * from account", new BeanListHandler<Account>(Account.class)); } catch (Exception e) { throw new RuntimeException(e); } } } 编写业务层代码 /** * 账户的业务层接口 * @author 黑马程序员 * @Company http://www.ithiema.com * @Version 1.0 */ public interface IAccountService { /** * 保存账户 * @param account */ void saveAccount(Account account); /** * 更新账户 * @param account */ void updateAccount(Account account); /** * 删除账户 * @param account */ void deleteAccount(Integer accountId); /** * 根据 id 查询账户 * @param accountId * @return */ Account findAccountById(Integer accountId); /** * 查询所有账户 * @return */ List<Account> findAllAccount(); } * 账户的业务层实现类 * @author 黑马程序员 * @Company http://www.ithiema.com * @Version 1.0 */ public class AccountServiceImpl implements IAccountService { private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } @Override public void saveAccount(Account account) { accountDao.save(account); } @Override public void updateAccount(Account account) { accountDao.update(account); } @Override public void deleteAccount(Integer accountId) { accountDao.delete(accountId); } @Override public Account findAccountById(Integer accountId) { return accountDao.findById(accountId); } @Override public List<Account> findAllAccount() { return accountDao.findAll(); } } 创建并编写配置文件 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- bean 标签: 用于配置让spring创建对象,并存入ioc容器中 id:对象的唯一标识 class:指定要创建的全限定类名 --> <!-- 配置 service --> <bean id="accountService" class="com.yg.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- 配置 dao --> <bean id="accountDao" class="com.yg.dao.impl.AccountDaoImpl"> <property name="runner" ref="runner"></property> </bean> <!--配置QueryRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!--注入数据源--> <constructor-arg name="ds" ref="dataSource"></constructor-arg> </bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db1"></property> <property name="user" value="root"></property> <property name="password" value="admin"></property> </bean> </beans> 测试类代码 import com.yg.domain.Account; import com.yg.service.IAccountService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.util.List; public class AccountServiceTest { /** * 测试保存 */ @Test public void testSaveAccount() { Account account = new Account(); account.setName("黑马程序员222"); account.setMoney(100000f); ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); IAccountService as = ac.getBean("accountService",IAccountService.class); as.saveAccount(account); } /** * 测试查询一个 */ @Test public void testFindAccountById() { ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); IAccountService as = ac.getBean("accountService",IAccountService.class); Account account = as.findById(1); System.out.println(account); } /** * 测试更新 */ @Test public void testUpdateAccount() { ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); IAccountService as = ac.getBean("accountService",IAccountService.class); Account account = as.findById(1); account.setMoney(20301050f); as.updateAccount(account); } /** * 测试删除 */ @Test public void testDeleteAccount() { ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); IAccountService as = ac.getBean("accountService",IAccountService.class); as.deleteAccount(1); } /** * 测试查询所有 */ @Test public void testFindAllAccount() { ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); IAccountService as = ac.getBean("accountService",IAccountService.class); List<Account> list = as.findAll(); for(Account account : list) { System.out.println(account); } } } 分析测试了中的问题 通过上面的测试类,我们可以看出,每个测试方法都重新获取了一次 spring 的核心容器,造成了不必要的重复代码,增加了我们开发的工作量。这种情况,在开发中应该避免发生。 有些同学可能想到了,我们把容器的获取定义到类中去。例如: /** 测试类 @author 黑马程序员 @Company http://www.ithiema.com @Version 1.0 */ public class AccountServiceTest { private ApplicationContext ac = new ClassPathXmlApplicationContext(“bean.xml”); private IAccountService as =ac.getBean(“accountService”,IAccountService.class); } 这种方式虽然能解决问题,但是扔需要我们自己写代码来获取容器。 能不能测试时直接就编写测试方法,而不需要手动编码来获取容器呢? 请在今天的最后一章节找答案 基于注解的 IOC 配置 明确:写在最前 学习基于注解的 IoC 配置,大家脑海里首先得有一个认知,即注解配置和 xml 配置要实现的功能都是一样 的,都是要降低程序间的耦合。只是配置的形式不一样。 关于实际的开发中到底使用xml还是注解,每家公司有着不同的使用习惯。所以这两种配置方式我们都需要掌 握。 我们在讲解注解配置时,采用上一章节的案例,把 spring 的 xml 配置内容改为使用注解逐步实现。 使用@Component 注解配置管理的资源 /** * 账户的业务层实现类 * @author 黑马程序员 * @Company http://www.ithiema.com * @Version 1.0 */ @Component("accountService") public class AccountServiceImpl implements IAccountService { private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } } /** * 账户的持久层实现类 */ @Component("accountDao2") public class AccountDaoImpl2 implements IAccountDao2 { private QueryRunner runner; } 注意: 1、当我们使用注解注入时,set 方法不用写 创建 spring 的 xml 配置文件并开启对注解的支持 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- bean 标签: 用于配置让spring创建对象,并存入ioc容器中 id:对象的唯一标识 class:指定要创建的全限定类名 --> <!--告知spring创建容器时扫描的包--> <context:component-scan base-package="com.yg"></context:component-scan> <!--配置QueryRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!--注入数据源--> <constructor-arg name="ds" ref="dataSource"></constructor-arg> </bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db1"></property> <property name="user" value="root"></property> <property name="password" value="admin"></property> </bean> </beans> 常用注解 用于创建对象的 @Component 作用: 把资源让 spring 来管理。相当于在 xml 中配置一个 bean。 属性: value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写。 @Controller @Service @Repository 他们三个注解都是针对一个的衍生注解,他们的作用及属性都是一模一样的。 他们只不过是提供了更加明确的语义化。 @Controller:一般用于表现层的注解。 @Service:一般用于业务层的注解。 @Repository:一般用于持久层的注解。 细节:如果注解中有且只有一个属性要赋值时,且名称是 value,value 在赋值是可以不写。 用于注入数据的 @Autowired 作用: 自动按照类型注入。当使用注解注入属性时,set 方法可以省略。它只能注入其他 bean 类型。当有多个 类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到 就报错。 @Qualifier 作用: 在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和 @Autowire 一起使用;但是给方法参数注入时,可以独立使用。 属性: value:指定 bean 的 id。 @Resource 作用: 直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型。 属性: name:指定 bean 的 id。 @Value 作用: 注入基本数据类型和 String 类型数据的 属性: value:用于指定值 用于改变作用范围的: @Scope 作用: 指定 bean 的作用范围。 属性: value:指定范围的值。 取值:singleton prototype request session globalsession 和生命周期相关的:(了解) @PostConstruct 作用: 用于指定初始化方法。 @PreDestroy 作用: 用于指定销毁方法。 测试: public class Client { public static void main(String[] args) { ApplicationContext ac=new ClassPathXmlApplicationContext("bean2.xml"); IAccountService2 as= (IAccountService2) ac.getBean("accountService2"); System.out.println("------------"); System.out.println(as); } } 输出结果: 没有销毁方法的执行 修改: public class Client { public static void main(String[] args) { ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("bean2.xml"); IAccountService2 as= (IAccountService2) ac.getBean("accountService2"); System.out.println("------------"); System.out.println(as); ac.close(); } } 关于 Spring 注解和 XML 的选择问题 注解的优势: 配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。 XML 的优势: 修改时,不用改源码。不涉及重新编译和部署。 Spring 管理 Bean 方式的比较: spring 管理对象细节 基于注解的 spring IoC 配置中,bean 对象的特点和基于 XML 配置是一模一样的。 spring 的纯注解配置 写到此处,基于注解的 IoC 配置已经完成,但是大家都发现了一个问题:我们依然离不开 spring 的 xml 配 置文件,那么能不能不写这个 bean.xml,所有配置都用注解来实现呢? 当然,同学们也需要注意一下,我们选择哪种配置的原则是简化开发和配置方便,而非追求某种技术。 待改造的问题 我们发现,之所以我们现在离不开 xml 配置文件,是因为我们有一句很关键的配置: <!-- 告知spring框架在,读取配置文件,创建容器时,扫描注解,依据注解创建对象,并存入容器中 --> <!--告知spring创建容器时扫描的包--> <context:component-scan base-package="com.yg"></context:component-scan> 如果他要也能用注解配置,那么我们就离脱离 xml 文件又进了一步。 另外,数据源和 JdbcTemplate 的配置也需要靠注解来实现 <!--配置QueryRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!--注入数据源--> <constructor-arg name="ds" ref="dataSource"></constructor-arg> </bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db1"></property> <property name="user" value="root"></property> <property name="password" value="admin"></property> </bean> 新注解说明 @Configuration 作用: 用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用 AnnotationApplicationContext(有@Configuration 注解的类.class)。 属性: value:用于指定配置类的字节码 示例代码: /** * spring 的配置类,相当于 bean.xml 文件 * @author 黑马程序员 * @Company http://www.ithiema.com * @Version 1.0 */ @Configuration public class SpringConfiguration { } 注意: 我们已经把配置文件用类来代替了,但是如何配置创建容器时要扫描的包呢? 请看下一个注解。 @ComponentScan 作用: 用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的: <context:component-scan base-package=“com.itheima”/>是一样的。 属性: basePackages:用于指定要扫描的包。和该注解中的 value 属性作用一样。 示例代码: /** spring 的配置类,相当于 bean.xml 文件 @author 黑马程序员 @Company http://www.ithiema.com @Version 1.0 */ @Configuration @ComponentScan(“com.itheima”) public class SpringConfiguration { } 注意: 我们已经配置好了要扫描的包,但是数据源和 JdbcTemplate 对象如何从配置文件中移除呢? 请看下一个注解。 @Bean 作用: 该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。 属性: name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。 @Configuration public class JDBCconfig { /** * 创建一个querurunner对象 * @param dataSource * @return */ @Bean(name = "runner") public QueryRunner creatQueryRunner(DataSource dataSource) { return new QueryRunner(dataSource); } @Bean(name = "dataSource")` // 方式1: ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class, JDBCconfig.class); ` public DataSource createDataSource() { try { ComboPooledDataSource ds=new ComboPooledDataSource(); ds.setDriverClass("com.mysql.jdbc.Driver"); ds.setJdbcUrl("jdbc:mysql://localhost:3306/db1"); ds.setUser("root"); ds.setPassword("admin"); return ds; } catch (Exception e) { throw new RuntimeException(e); } } } 多个配置类实现方法: 方式1: ApplicationContext ac = new AnnotationConfigApplicationContext (SpringConfiguration.class, JDBCconfig.class); 方式2:在扫描里加一个包@ComponentScan({"com.yg", "com.config"}) 方式3在主配置类加import @PropertySource 用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到 properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。 属性: value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath: 配置: /** * 连接数据库的配置类 * @author 黑马程序员 * @Company http://www.ithiema.com * @Version 1.0 */ public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; /** * 创建一个数据源,并存入 spring 容器中 * @return */ @Bean(name="dataSource") public DataSource createDataSource() { try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setDriverClass(driver); ds.setJdbcUrl(url); ds.setUser(username); ds.setPassword(password); return ds; } catch (Exception e) { throw new RuntimeException(e); } } } jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/db1 jdbc.username=root jdbc.password=admin 然后在主配置类写注解 @Configuration @ComponentScan("com.yg") @Import(JDBCconfig.class) @PropertySource("jdbcconfig.properties") public class SpringConfiguration { } Spring 整合 Junit[掌握] 问题 在测试类中,每个测试方法都有以下两行代码: ApplicationContext ac = new ClassPathXmlApplicationContext(“bean.xml”); IAccountService as = ac.getBean(“accountService”,IAccountService.class); 这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。 解决思路分析 针对上述问题,我们需要的是程序能自动帮我们创建容器。一旦程序能自动为我们创建 spring 容器,我们就 无须手动创建了,问题也就解决了。 我们都知道,junit 单元测试的原理(在 web 阶段课程中讲过),但显然,junit 是无法实现的,因为它自 己都无法知晓我们是否使用了 spring 框架,更不用说帮我们创建 spring 容器了。不过好在,junit 给我们暴露 了一个注解,可以让我们替换掉它的运行器。 这时,我们需要依靠 spring 框架,因为它提供了一个运行器,可以读取配置文件(或注解)来创建容器。我 们只需要告诉它配置文件在哪就行了。 第一步:拷贝整合 junit 的必备 jar 包到 lib 目录 第二步:使用@RunWith 注解替换原有运行器 /** * 测试类 * @author 黑马程序员 * @Company http://www.ithiema.com * @Version 1.0 */ @RunWith(SpringJUnit4ClassRunner.class) public class AccountServiceTest { } 第三步:使用@ContextConfiguration 指定 spring 配置文件的位置 /** * 测试类 * @author 黑马程序员 * @Company http://www.ithiema.com * @Version 1.0 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations= {"classpath:bean.xml"}) public class AccountServiceTest { } @ContextConfiguration 注解: locations 属性:用于指定配置文件的位置。如果是类路径下,需要用 classpath:表明 classes 属性:用于指定注解的类。当不使用 xml 配置时,需要用此属性指定注解类的位置 第四步:使用@Autowired 给测试类中的变量注入数据 /** * 测试类 * @author 黑马程序员 * @Company http://www.ithiema.com * @Version 1.0 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations= {"classpath:bean.xml"}) public class AccountServiceTest { @Autowired private IAccountService as ; } ———————————————— 原文链接:https://blog.csdn.net/weixin_44883727/article/details/107755428
-
Spring spring的官网网站: spring.io 里面是spring的官方学习文档 一、什么是spring? 1、Spring是什么? Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。其主要包括以下七个模块: Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等); Spring Core:核心类库,所有功能都依赖于该类库,提供IOC和DI服务; Spring AOP:AOP服务; Spring Web:提供了基本的面向Web的综合特性,提供对常见框架如Struts2的支持,Spring能够管理这些框架,将Spring的资源注入给框架,也能在这些框架的前后插入拦截器; Spring MVC:提供面向Web应用的Model-View-Controller,即MVC实现。 Spring DAO:对JDBC的抽象封装,简化了数据访问异常的处理,并能统一管理JDBC事务; Spring ORM:对现有的ORM框架的支持; 2、spring有什么用? (1)spring属于低侵入式设计,代码的污染极低; (2)spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性; (3)Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。 (4)spring对于主流的应用框架提供了集成支持。 IoC 控制反转的概念和作用 1\为什么要用ioc: 在没有用spring之前,我们用JavaWeb应用时的开发流程是这样的:编写一个xxDao类与xxService类, 然后直接在xxService类中创建xxDao类示例 : public xxService { xxDao xdao =new xxDao(); } 这样做导致代码的耦合度非常高,当这些类的路径或啥的有了变动之后,牵一发而动全身,整个项目的代码都要做相应的调整,开发维护成本极高,所以才会有springIOC来降低代码的耦合度(但还是会用耦合度)。 2\ioc是如何做到解耦合的:通过xml解析---工厂模式---反射 我们一般使用ioc的过程是这样的: 1. 创建xml文件配置要用ioc创建的对象 <bean id="user" class="bean.User"></bean> 2. ioc底层会创建一个工厂类,该工厂类利用反射机制来帮助我们创建我们想创建的对象 例如: class UserFactory{ public static UserDao getDao(){ String classValue =class属性值; //xml解析,利用class属性值得到UserDao类的全路径 Class clazz =Class.forName(classValue);//通过反射创建Dao的对象 return (UserDao)clazz.newInstance(); } } 3\Spring的IoC理解: (1)IOC就是控制反转,指创建对象的控制权转移给Spring框架进行管理,并由Spring根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部依赖。 (2)最直观的表达就是,以前创建对象的主动权和时机都是由自己把控的,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。 (3)Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。 ioc接口 1、ioc的思想要基于ioc容器来完成,ioc容器底层其实就是对象工厂 2、Spring提供了两个实现ioc容器的方式(也是两个接口) (1)、BeanFactory :IOC容器基本实现是Spring内部接口的使用接口,不提供给开发人员进行使用(加载配置文件时候不会创建对象,在获取对象时才会创建对象。) (2)、ApplicationContext :BeanFactory接口的子接口,提供更多更强大的功能,提供给开发人员使用(加载配置文件时候就会把在配置文件对象进行创建)推荐使用! ApplicationContext接口的实现类 : 在idea中把光标移到ApplicationContext类按住CTRL+H即可查看它的实现类 入门案例:使用spring的方式创建JavaBean对象 1. 在项目中引入以下四个spring开发的基本jar包 spring-beans-5.0.2.RELEASE.jar spring-context-5.0.2.RELEASE.jar spring-core-5.0.2.RELEASE.jar spring-expression-5.0.2.RELEASE.jar 除了上面四个jar外,还需要引入一个 commons-logging-1.2.jar (注意这个包并不是spring的) 2.编写bean类 public class User { int age; String name; public User() { System.out.println("niu pi"); } } 3.在src目录下创建bean.xml文件 文件内容如下: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> //class写User类的全路径 <bean id="user" class="bean.User"> <property name="age" value="18"></property> </bean> </beans> 4.测试代码: @Test public void test(){ //加载spring配置文件 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");//ClassPathXmlApplicationContext默认加载类路径下的xml文件 //获取创建对象 User user = (User)context.getBean("user"); System.out.println(user); } ①ioc这个容器,用来管理所有的组件(有功能的类),如与创建对象有关的操作[省去new,降低耦合] ②ioc入门案例细节: 1.组件(对象)的创建是容器完成的 2.User类对象是在容器对象创建的过程中容器顺便创建的,容器中的组件都创建完后容器对象才创建完成 3.同一个组件(对象(一个bean标签一个组件))在ioc容器中是单实例的,容器启动时就已经创建完成 4.容器中没有该组件时去获取该组件就会报NoSuchBeanDefinitionException错误,不获取也报异常 5.ioc容器在创建这个组件对象时,(property标签)会利用setter方法为JavaBean的属性赋值 6.JavaBean的属性名并不是由声明的属性决定的,而是由JavaBean的getter/setter方法后面的字母决定的 7.利用<constructor-arg >标签为对象赋值时调用的是该对象**参数对应**的类的构造器 ③ 通过IOC容器创建对象,并为属性赋值步骤: # 先在Src类路径下创建一个spring的xml配置文件 # 在配置文件中注册组件并赋值 <!-- 一个Bean标签可以注册一个组件(类) bean标签的class属性要写该类的全类路径 id属性是给该对象的唯一标识 --> # 在bean标签体中的使用property标签(或 <constructor-arg >标签)为对象属性赋值 <!-- 使用property标签为该对象的属性赋值 该标签的name属性为指定属性名 value为这个属性的值 # 测试能否从ioc容器中获取对象 @ 首先要创建IOC容器对象 //ApplicationContext代表ioc容器 //ClassPathXmlApplicationContext:当前应用的xml配置文件在类路ClassPath 下 //根据spring配置文件得到ioc容器对象 ApplicationContext ioc=new ClassPathXmlApplicationContext("bean.xml"); @ 然后根据ioc容器对象的getbean方法(参数可以是组件的id)获取组件对象 getBean方法的参数也可以是组件的类型(Object.class),但是ioc容器中[也就是spring的配置文件中] 同一个类型的类有多个组件,使用类型来获取对象就会报错 故getBean方法的参数可以是组件的类型加上组件的id值 @ 使用该组件对象 二、IOC容器-Bean管理 1、什么是IOC的Bean管理 a**)Bean管理就是两个操作:(1)Spring创建对象;(2)Spring注入类的属性** 2、基于xml配置文件的方式实现bean管理 2.1 创建对象 基于xml配置文件的方式实现bean管理其实就是在xml配置文件中配置自己想要springIOC容器帮忙创建的对象,例如在xml文件中写:其实就是实现了基于xml配置文件的方式实现Spring创建对象。 1、在xml文件中每一个bean标签对应一个bean对象 2、bean标签中的属性: id id是bean对象的唯一标识,不能添加特别字符 class 指定bean对应类的全路径 eg:com.fdk.been.Users scope 执行bean对象创建模式和生命周期 lazy-init 是否延时加载 默认值:false factory-method 加载静态方法 factory-bean 加载类 factory-method 加载类的非静态方法 init-method 初始化的方法 destroy-method 销毁时执行的方法 3、创建对象的时候默认调用无参构造器创建(ioc使用了反射机制),若对应的类中没有相应的无参构造器则会报错 2.2 注入属性(DI) *1 通过set方法注入 通过这种方式注入对象的属性时,该对象的类在定义时类的属性要有相应的set方法 实现方式:在spring 的配置文件中创建对象添加bean标签时在bean标签里添加注入属性的标签 <property name="age" value="18"></property> property标签的name属性是要创建的对象的属性名 ,value属性是要注入的属性值 例如:如下就是在创建user对象时给age属性赋值为18 <bean id="user" class="bean.User"> <property name="age" value="18"></property> </bean> *2 通过有参构造器完成注入 通过这种方式注入对象的属性时,该对象的类在定义时要有对应的重载构造器 实现方式:在spring 的配置文件中创建对象添加bean标签时在bean标签里添加构造器的标签 //name、value属性跟上面差不多 <bean id="user" class="bean.User"> <constructor-arg name="age" value="18"></constructor-arg> <constructor-arg name="name" value="lihua"></constructor-arg> </bean> 注意: bean.User 类中要有该构造器 public User(int age, String name) { this.age = age; this.name = name; } *3 通过p名称空间注入属性(底层也是set注入) 、、在xml文件中加入约束: xmlns:p="http://www.springframework.org/schema/p" 添加p名称空间 例如在配置文件中添加如下内容: <bean id="user" class="bean.User" p:age="18"></bean> 即是创建了一个user对象并给age属性赋值为18 *4 注入空值、特殊值 //给name属性赋值为空 <bean id="user" class="bean.User" > <property name="name"> <null/> </property> </bean> //特殊值中包含特殊符号,比如:<<南京>> <bean id="user" class="bean.User" > <property name="name"> <value><![CDATA[<<南京>>]]></value> </property> </bean> *5 注入外部bean Book类结构如下://get、set方法省略 public class Book { private String name; private User userOfbook; ....... } //现在给Book的User属性赋值: //这个user要给bookOfbook赋值 <bean id="user" class="bean.User" > <property name="name" value="李三"></property> </bean> <bean id="book" class="bean.Book"> //这个user就是上面创建的user <property name="userOfbook" ref="user"></property> </bean> //测试: @Test public void test1(){ //加载spring配置文件 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");//ClassPathXmlApplicationContext默认加载类路径下的xml文件 //获取创建对象 Book book = context.getBean("book", Book.class); System.out.println(book.getUserOfbook().getName()); } 6* 注入集合属性 测试类结构: public class Stu { private String [] arr; private List<String> list; private Map<String,Integer> map; private Set<String> set; private List<User> list1; ....//省略的set方法 } 配置文件内容: <bean class="bean.Stu" id="stu"> <!-- // *数组属性--> <property name="arr"> <array> <value>java</value> <value>javascripe</value> <value>js</value> </array> </property> <!-- list集合属性--> <property name="list"> <list> <value>大张</value> <value>小张</value> </list> </property> <property name="list1"> <list> <ref bean="user"></ref> <ref bean="user"></ref> </list> </property> <!-- map属性--> <property name="map"> <map> <entry key="李" value="3"></entry> <entry key="王" value="5"></entry> </map> </property> <!-- set集合注入--> <property name="set"> <set> <value>123</value> <value>159</value> </set> </property> </bean> <bean id="user" class="bean.User"></bean> 测试:... 在方法的形参位置使用@Qualifier注解 ¥在方法的形参上添加上@Autowired注解的话,这个方法上的每一个形参都可以自动注入值;而且这个方法在bean创建的时候自动运行 ¥在形参的具体位置使用@Qualifier注解的话,匹配的时候就会使用指定的id去注入了 public void creat(Person p,@Qualifie("newCar")Car car){ ...... } 4.完全使用注解创建对象与属性注入的开发模式 (1)创建配置类,替代 xml 配置文件 (若是不熟悉的话还是用xml与注解相结合的方式更好一点) @Configuration //作为配置类,替代 xml 配置文件 @ComponentScan(basePackages = {"com.service"}) public class SpringConfig { } (2)编写测试类 @Test public void testService2() { //加载配置类 ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); UserService userService = context.getBean("userService", UserService.class); System.out.println(userService); userService.add(); } 三、Spring-AOP面向切面编程 1、AOP 基本概念 (1)面向切面编程(方面),利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得 业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 (2)通俗描述:不通过修改源代码方式,在主干功能里面添加新功能 例如一个登录功能已经开发好了,但是你要在登入之前作对登录用户做一个管理员权限的判断,这时候我们当然不希望对已经开发好的登录模块再做修改,所以aop大概也是解决这类问题的,它可以在不修改原有的登录模块代码而在其基础上添加一个权限的判断 什么是面向切面编程? 要理解切面编程,就需要先理解什么是切面。用刀把一个西瓜分成两瓣,切开的切口就是切面;炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。 下面我举个例子给大家说明一下: 有A,B,C三个方法,但是在调用每一个方法之前,要求打印一个日志:某一个方法A被开始调用了! 在调用某个方法B之后,也要求打印日志:某个方法B被调用完了! 一般人都会在每一个方法的开始和结尾部分都会添加一句日志打印吧,这样做如果方法多了,就会有很多重复的代码,显得很麻烦,这时候有人会想到,为什么不把打印日志这个功能封装一下,然后让它能在指定的地方(比如执行方法前,或者执行方法后)自动的去调用呢?如果可以的话,业务功能代码中就不会掺杂这一下其他的代码,所以AOP就是做了这一类的工作,比如,日志输出,事务控制,异常的处理等。 aop的基本术语 第一种专业概念定义: 1.通知(Advice) 就是你想要的功能,也就是上面说的 安全,事物,日志等。你给先定义好把,然后在想用的地方用一下。 有五个类型: 1)前置通知:在连接点(Join point)之前执行的通知。 2)后置通知:当连接点退出的时候执行的通知(不论是正常返回还是异常退出)。 3)环绕通知:围一个连接点的通知,这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。 4)异常通知 :在连接点正常完成后执行的通知(如果连接点抛出异常,则不执行) 5)最终通知:在方法抛出异常退出时执行的通知 2.连接点(JoinPoint) 这个更好解释了,就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点.其他如aspectJ还可以让你在构造器或属性注入时都行 。 (是个方法都可以被增强,只不过你想不想让它增强罢了) 3.切入点(Pointcut) 上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。 (真正的你增强了的方法) 4.切面(Aspect) 切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。 (切面是一个把通知应用到切入点的过程) 5.引入(introduction) 允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗 6.目标(target) 引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。 7.代理(proxy) 怎么实现整套aop机制的,都是通过代理,这个一会给细说。 8.织入(weaving) 把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时,为什么是运行时,后面解释。 关键就是:切点定义了哪些连接点会得到通知 第二种专业概念定义: *Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。 *Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。 【类里面哪些方法可以被增强,这些方法称为连接点】 *Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。 【实际被真正增强的方法称为切入点】 *Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。 【实际增强的逻辑部分称为通知,且分为以下五种类型: 1)前置通知 2)后置通知 3)环绕通知 4)异常通知 5)最终通知 】 *Target(目标对象):织入 Advice 的目标对象.。 *Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程 1.AOP中的Joinpoint可以有多种类型:构造方法调用,字段的设置和获取,方法的调用,方法的执行,异常的处理执行,类的初始化。也就是说在AOP的概念中我们可以在上面的这些Joinpoint上织入我们自定义的Advice,但是在Spring中却没有实现上面所有的joinpoint,确切的说,Spring只支持方法执行类型的Joinpoint。 2.Advice 的类型: before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码) after return advice, 在一个 join point 正常返回后执行的 advice after throwing advice, 当一个 join point 抛出异常后执行的 advice after(final) advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice. around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice. introduction,introduction可以为原有的对象增加新的属性和方法。 3. execution切入点表达式,如下:(先看着) (1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强 (2)语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) ) (3)例子如下: 例 1:对 com.dao.BookDao 类里面的 add方法 进行增强 execution(* com.dao.BookDao.add(..)) 例 2:对 com.dao.BookDao 类里面的所有的方法进行增强 execution(* com.dao.BookDao.* (..)) 例 3:对 com.dao 包里面所有类,类里面所有方法进行增强 execution(* com.dao.*.* (..)) 2、Spring AOP在开发中的使用 用一个创建具体的切面类来说明这些基本概念: a)Spring 框架一般都是基于 AspectJ 实现 AOP 操作,AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使 用,进行 AOP 操作 b)基于 AspectJ 实现 AOP 操作: (1)基于 xml 配置文件实现 (2)基于注解方式实现(tuijian使用) 具体步骤: 1、引入jar /************/ //使用@Aspect方式需要另外添加这两个jar包 aopalliance-1.0.jar aspectjweaver-1.8.7.jar commons-logging-1.2.jar //使用AOP需要这2个包 spring-aop-5.0.2.RELEASE.jar spring-aspects-5.0.2.RELEASE.jar spring-beans-5.0.2.RELEASE.jar spring-context-5.0.2.RELEASE.jar spring-core-5.0.2.RELEASE.jar spring-expression-5.0.2.RELEASE.jar /****************/ 2、Spring的配置文件bean.xml文件中引入context、aop对应的名称空间;配置自动扫描包;同时使切面类中相关的方法的注解生效,需自动地为匹配到的方法所在的类生成代理对象 xml文件内容: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--开启组件扫描 --> <context:component-scan base-package="com.aop"></context:component-scan> <!-- 自动为切面类方法中匹配的方法所在的类生成代理对象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans> 3、创建一个接口和实现类(这个就跟平时创建service接口和实现类一样) UserService接口: public interface UserService { public void add(); } UserServiceImpl实现类: @Component public class UserServiceImpl implements UserService { @Override public void add() { System.out.println("add....."); } } 4、创建具体的切面类: 1.先创建一个类,比如:MyAspect.java 2.在类上使用 @Aspect 注解 使之成为切面类 3.在类上使用 @Component 注解 把切面类加入到IOC容器中,或者在spring配置文件中创建bean也可以,也可以在它上面加@Service注解,目的就是让它实例化 类中的方法名随意取,关键是方法上面的注解,用@Aspect注解方式来实现前置通知、返回通知、后置通知、异常通知、环绕通知!!!!!!! 切面类代码: import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.util.Arrays; @Aspect @Component public class MyAspect { /** * 前置通知:目标方法执行之前执行以下方法体的内容 * @param jp */ @Before("execution(* com.aop.service.*.*(..))") public void beforeMethod(JoinPoint jp){ String methodName = jp.getSignature().getName(); System.out.println("【前置通知】the method 【" + methodName + "】 begins with " + Arrays.asList(jp.getArgs())); } /** * 返回通知:目标方法正常执行完毕时执行以下代码 * @param jp * @param result */ @AfterReturning(value="execution(* com.aop.service.*.*(..))",returning="result") public void afterReturningMethod(JoinPoint jp, Object result) { String methodName = jp.getSignature().getName(); System.out.println("【返回通知】the method 【" + methodName + "】 ends with 【" + result + "】"); } /** * 后置通知:目标方法执行之后执行以下方法体的内容,不管是否发生异常。 * @param jp */ @After("execution(* com.aop.service.*.*(..))") public void afterMethod(JoinPoint jp) { System.out.println("【后置通知】this is a afterMethod advice..."); } /** * 异常通知:目标方法发生异常的时候执行以下代码 */ @AfterThrowing(value="execution(* com.aop.service.*.*(..))",throwing="e") public void afterThorwingMethod(JoinPoint jp, NullPointerException e) { String methodName = jp.getSignature().getName(); System.out.println("【异常通知】the method 【" + methodName + "】 occurs exception: " + e); } } //注解中的execution用来表示这个切面类中的该方法在哪里执行,也就是作用的目标 //这里的execution中我作用的路径是某个实现类下面的全部方法,还可以具体作用到某一个方法,详情稍后 5、测试代码: @Test public void test3(){ //加载spring配置文件 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");//ClassPathXmlApplicationContext默认加载类路径下的xml文件 //获取创建对象 UserService userService = context.getBean("userServiceImpl", UserService.class); userService.add(); } 输出结果: AOP 操作(AspectJ 配置文件) 。。。 了解 <!--1、创建两个类,增强类和被增强类,创建方法(同上一样)--> <!--2、在 spring 配置文件中创建两个类对象--> <!--创建对象--> <bean id="book" class="com.spring5.aopxml.Book"></bean> <bean id="bookProxy" class="com.spring5.aopxml.BookProxy"></bean> <!--3、在 spring 配置文件中配置切入点--> <!--配置 aop 增强--> <aop:config> <!--切入点--> <aop:pointcut id="p" expression="execution(* com.spring5.aopxml.Book.buy(..))"/> <!--配置切面--> <aop:aspect ref="bookProxy"> <!--增强作用在具体的方法上--> <aop:before method="before" pointcut-ref="p"/> </aop:aspect> </aop:config> 3、AOP(底层原理) a) Spring AOP 底层使用动态代理 ,动态代理有两种情况: 第一种 :有接口情况,使用 JDK 动态代理 ;创建接口的实现类的代理对象,来增强类的方法 第二种 : 没有接口情况,使用 CGLIB 动态代理;创建子类的代理对象,增强类的方法 b ) AOP(JDK 动态代理) JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。现在都推荐面向接口编程,我们做的项目都是各种接口+实现类,所以是不是觉得这种代理方式和现在的接口编程很符合呢! 所以一个spring项目有接口和实现类,如果不在spring配置文件中特殊配置的话(就是默认配置),默认的动态代理方式就是JDK动态代理。但是,如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。 通俗解释: 目标类(实现类)可以认作为厨师张三,张三能够具体实现接口的所有功能,比如老板让张三做饭,当AOP代理的时候,会根据张三所实现的接口,再创造一个张三A(代理对象)作为张三的分身,这时候张三和张三A具有相同的接口(多态的体现),两者长得一模一样,这时候张三A就可以在张三做饭之前把菜给洗干净了,然后张三本人来做饭。。但是在老板(调用者)看来,自始至终都是张三(目标类)一个人在洗菜做饭。 但是张三(目标类)知道,在他动手做饭之前他的代理对象帮他做了一些事情,代理对象也可以在他做饭之后帮他洗碗等等。 所以目标类要是没有实现接口,程序就不能根据接口再实现一个代理对象,也就不能代替目标类(实现类)去做一些事情。 这种代理方式,只有在通过接口调用方法的时候才会有效! 底层JDK 动态代理示例: 1)使用 JDK 动态代理,使用 Proxy 类里面的方法创建代理对象 调用 newProxyInstance 方法,方法有三个参数 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 第一参数,类加载器 第二参数,增强方法所在的类,这个类实现的接口,支持多个接口 第三参数,实现这个接口 InvocationHandler,创建代理对象,写增强的部分 2)编写 JDK 动态代理代码 //(1)创建接口,定义方法 public interface UserDao { public int add(int a,int b); public String update(String id); } //(2)创建接口实现类,实现方法 public class UserDaoImpl implements UserDao { @Override public int add(int a, int b) { System.out.println("add.....执行了"); return a+b; } @Override public String update(String id) { System.out.println("update.....执行了"); return id; } } //(3)使用 Proxy 类创建接口代理对象 public class JDKProxy { public static void main(String[] args) { //创建接口实现类代理对象 Class[] interfaces = {UserDao.class}; UserDaoImpl userDao = new UserDaoImpl(); /** 第一参数,类加载器 第二参数,增强方法所在的类,这个类实现的接口,(支持多个接口) 第三参数,实现这个接口 InvocationHandler,创建代理对象,写增强的部分 */ UserDao dao =(UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao)); int result = dao.add(1, 2); System.out.println("result:"+result); } } //创建代理对象代码 class UserDaoProxy implements InvocationHandler { //1 把创建的是谁的代理对象,把谁传递过来 //有参数构造传递 private Object obj; public UserDaoProxy(Object obj) { this.obj = obj; } //增强的逻辑 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //方法之前 System.out.println("方法之前执行...."+method.getName()+" :传递的参数..."+ Arrays.toString(args)); //被增强的方法执行 Object res = method.invoke(obj, args); //方法之后 System.out.println("方法之后执行...."+obj); return res; } }运行结果: c ) CGLIB动态代理 CGLIB(Code Generation Library),是一个代码生成的类库, 可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做 的动态代理,因此如果某个类被标记为`final`,那么它是无法使用CGLIB做 动态代理的。 通俗解释: 我们把目标类比作李刚(化名),代理的时候,程序会根据制造一个子类来 继承目标类,那么这个子类就是代理对象(李刚的儿子),所以李刚的儿子 就可以能替他爸收钱(因为他爸是李刚哈哈),因为多态,所以程序识别不出来 ,然后目标类再替人办事,在外人看来,就是李刚在收钱办事。但是李刚有很多 特权他儿子是没权限的,也就是目标类中有final方法,子类是无法继承的, 那么这个代理对象就不能代理这部分功能。 d )如何选择spring动态代理的方式 在spring配置文件中,有个配置如下 <aop:aspectj-autoproxy proxy-target-class="true" /> /* proxy-target-class默认是false,也就是默认使用JDK动态代理,但是如果目标类没有实现接口,会自动转为CGLIB代理; 设置为true,说明使用CGLIB代理! */ 4、Spring AOP多个切面的执行顺序 同一个Aspect,不同advice的执行顺序: (1)没有异常情况下的执行顺序: around before advice before advice target method 执行 around after advice after advice afterReturning (2)有异常情况下的执行顺序: around before advice before advice target method 执行 around after advice after advice afterThrowing java.lang.RuntimeException: 异常发生 4.1、配置AOP执行顺序的三种方式: 确实是order越小越是最先执行, 还有最先执行的最后结束。 通过实现org.springframework.core.Ordered接口 @Component @Aspect @Slf4j public class MessageQueueAopAspect1 implements Ordered{@Override public int getOrder() { // TODO Auto-generated method stub return 2; } } 通过注解 @Component @Aspect @Slf4j @Order(1) public class MessageQueueAopAspect1{ ... } 通过xml配置文件配置 <aop:config expose-proxy="true"> <aop:aspect ref="aopBean" order="0"> <aop:pointcut id="testPointcut" expression="@annotation(xxx.xxx.xxx.annotation.xxx)"/> <aop:around pointcut-ref="testPointcut" method="doAround" /> </aop:aspect> </aop:con fig> 4.2、存在两个aop时的执行顺序 存在多个aop时,spring aop就是一个同心圆,要执行的方法为圆心, 最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法, doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行 doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的, 一定后after。 如果我们要在同一个方法事务提交后执行自己的AOP, 那么把事务的AOP order设置为2,自己的AOP order设置为1, 然后在doAfterReturn里边处理自己的业务逻辑。 四、spring 事务管理 1、声明式事务环境搭建 (1)、数据库创建语句: /*!40101 SET NAMES utf8 */; /*!40101 SET SQL_MODE=''*/; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; CREATE DATABASE /*!32312 IF NOT EXISTS*/`tx` /*!40100 DEFAULT CHARACTER SET gb2312 */; USE `tx`; /*Table structure for table `account` */ DROP TABLE IF EXISTS `account`; CREATE TABLE `account` ( `username` varchar(50) NOT NULL, `balance` int(11) DEFAULT NULL, PRIMARY KEY (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=gb2312; insert into `account`(`username`,`balance`) values ('Jerry',800),('Tom',100000); DROP TABLE IF EXISTS `book`; CREATE TABLE `book` ( `isbn` varchar(50) NOT NULL, `book_name` varchar(100) DEFAULT NULL, `price` int(11) DEFAULT NULL, PRIMARY KEY (`isbn`) ) ENGINE=InnoDB DEFAULT CHARSET=gb2312; insert into `book`(`isbn`,`book_name`,`price`) values ('ISBN-001','book01',100),('ISBN-002','book02',200),('ISBN-003','book03',300),('ISBN-004','book04',400),('ISBN-005','book05',500); DROP TABLE IF EXISTS `book_stock`; CREATE TABLE `book_stock` ( `isbn` varchar(50) NOT NULL, `stock` int(11) DEFAULT NULL, PRIMARY KEY (`isbn`) ) ENGINE=InnoDB DEFAULT CHARSET=gb2312; insert into `book_stock`(`isbn`,`stock`) values ('ISBN-001',1000),('ISBN-002',2000),('ISBN-003',3000),('ISBN-004',4000),('ISBN-005',5000); (2) 、配置数据源 src下创建dbconfig.properties文件 jdbc.user=root jdbc.password=123456 jdbc.jdbcUrl=jdbc:mysql://localhost:3306/tx jdbc.driverClass=com.mysql.jdbc.Driver xml文件中加入数据源 <!-- 引入外部文件--> <context:property-placeholder location="classpath:dbconfig.properties"/> <!-- 配置数据源--> <bean id="pooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> </bean> 测试是否成功配置数据源 @Test public void test1() throws SQLException { ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml"); DataSource source = ioc.getBean(DataSource.class); Connection connection = source.getConnection(); System.out.println(connection); connection.close(); } 使用JdbcTemplate操作数据库需要导入spring数据库模块 导入: spring-jdbc-5.0.2.RELEASE.jar spring-orm-5.0.2.RELEASE.jar spring-tx-5.0.2.RELEASE.jar 在xml中加入如下内容: <!-- 配置JdbcTemplate --> <bean class="org.springframework.jdbc.core.JdbcTemplate"> <!--这个 pooledDataSource 是上面配置的数据源,表明连接到该数据源--> <property name="dataSource" ref="pooledDataSource"></property> </bean> (4)、编写代码模仿结账操作 目录结构: BookDao.java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class BookDao { @Autowired JdbcTemplate jdbcTemplate; /** * 1、减余额 * * 减去某个用户的余额 */ public void updateBalance(String userName,int price){ String sql = "UPDATE account SET balance=balance-? WHERE username=?"; jdbcTemplate.update(sql, price,userName); } /** * 2、按照图书的ISBN获取某本图书的价格 * @return */ public int getPrice(String isbn){ String sql = "SELECT price FROM book WHERE isbn=?"; return jdbcTemplate.queryForObject(sql, Integer.class, isbn); } /** * 3、减库存;减去某本书的库存;为了简单期间每次减一 */ public void updateStock(String isbn){ String sql = "UPDATE book_stock SET stock=stock-1 WHERE isbn=?"; jdbcTemplate.update(sql, isbn); } } BookService.java import com.book.dao.BookDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class BookService { @Autowired BookDao bookDao; /** * 结账;传入哪个用户买了哪本书 * @param username * @param isbn */ public void checkout(String username,String isbn){ //1、减库存 bookDao.updateStock(isbn); int price = bookDao.getPrice(isbn); //2、减余额 bookDao.updateBalance(username, price); } } xml文件加入自动包扫描 <context:component-scan base-package="com.book"></context:component-scan> 1 测试代码: @Test public void test2() { ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml"); BookService bean = ioc.getBean(BookService.class); bean.checkout("Tom","ISBN-001"); System.out.println("结账完成"); } 环境搭建完成; 2、声明式事务 以前通过复杂的编程来编写一个事务 编程式事务: TransactionFilter{ try{ //获取连接 //设置非自动 提交 chain.doFilter(); //提交 }catch(Exception e){ //回滚 }finllay{ //关闭连接释放资源 } } 现在为只需要告诉Spring哪个方法是事务方法即可, Spring会自动进行事务控制; AOP环绕通知可以去做这点; //获取连接 //设置非自动 提交 目标代码执行 //正常提交 //异常回滚 //最终关闭 是不是跟aop的通知方法很相似 开发中快速的为某个方法添加事务: 1)、配置出这个事务管理器让他工作; 2)、开启基于注解的事务 <!-- 配置数据源--> <bean id="pooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> </bean> <!-- 配置JdbcTemplate --> <bean class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="pooledDataSource"></property> </bean> <!-- 事务控制 --> <!--1:配置事务管理器(切面)让其进行事务控制;一定导入面向切面编程的几个包 spring-aspects-4.0.0.RELEASE.jar com.springsource.net.sf.cglib-2.2.0.jar com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar --> <bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 控制住数据源 --> <property name="dataSource" ref="pooledDataSource"></property> </bean> <!--2:开启基于注解的事务控制模式;依赖tx名称空间 --> <tx:annotation-driven transaction-manager="tm"/> <!--3:给事务方法加注解@Transactional --> 我们先测试一下: 在BookService的checkout方法中bookDao.updateBalance(username, price);语句前加入int i=1/0;这句,然后开始测试 @Test public void test2() { ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml"); BookService bean = ioc.getBean(BookService.class); bean.checkout("Tom","ISBN-001"); System.out.println("结账完成"); } 这时候会出现错误 出现错误之后我们当然希望数据库中的数据不发生变化,但是库存还是减少了1 3)、给事务方法加@Transactional注解 现在我们在checkout方法上加上注解,再来测试一番虽然也出现了错误但是数据库中的数据没有发生改变 ———————————————— 原文链接:https://blog.csdn.net/glass__sky/article/details/116102700
-
ArrayList概述ArrayList是Java中的一个重要组件,属于java.util包,是一种基于数组的列表(List)实现,能够以动态的方式存储对象。与传统数组不同的是,ArrayList允许在运行时动态调整自身的大小,这使得它在处理未知数量元素时非常有用。ArrayList的基本用法添加元素向ArrayList中添加元素是通过add()方法实现的。例如,可以将一个整数添加到ArrayList中:ArrayList<Integer> numbers = new ArrayList<>();numbers.add(12);访问元素可以通过get()方法按索引访问ArrayList中的元素:int firstElement = numbers.get(0);修改元素使用set()方法可以修改指定索引位置的元素:numbers.set(0, 13); // 将索引0的元素修改为13删除元素删除元素可以使用remove()方法,也可以使用clear()方法清空整个ArrayList:numbers.remove(0); // 删除索引为0的元素numbers.clear(); // 清空ArrayListArrayList的特点动态数组:ArrayList是基于数组的,但它允许在运行时动态增加或删除元素,而不需要重新分配数组。线程不安全:ArrayList不是线程安全的,这意味着在多线程环境中,需要采取额外的同步措施来保护ArrayList。随机访问:ArrayList支持快速的随机访问,即O(1)时间复杂度。插入和删除操作:虽然在数组末尾添加或删除元素很快(O(1)),但在数组中间进行插入或删除操作则较慢,时间复杂度为O(n)。扩容机制:当ArrayList达到容量限制时,会自动进行扩容,这个过程可能涉及复制原有数据到一个更大的数组中,这可能会带来性能开销。ArrayList的应用场景ArrayList适用于以下场景:当需要存储大量数据且数量未知时。当需要频繁进行插入和删除操作时。当需要随机访问列表中的元素时。ArrayList与其他数据结构的比较与Arrays比较:与固定大小的数组相比,ArrayList提供了动态调整大小的能力,更适合数据量不确定的情况。与LinkedList比较:LinkedList在插入和删除操作上通常更快,尤其是在列表中间操作时,但随机访问元素的速度较慢。相比之下,ArrayList在随机访问上较快,但在插入和删除操作上较慢。总结ArrayList是Java中一种常用的动态数组实现,它提供了丰富的方法来方便地进行列表操作。了解它的特点和使用场景对于有效地利用ArrayList非常重要。在实际开发中,应根据具体需求选择最合适的数据结构。
-
XLSX.SSF.format 是 Apache POI 库中用于处理 Excel 文件(特别是 .xlsx 格式)时,用于格式化单元格数据的一个功能。Apache POI 是一个流行的 Java 库,它提供了对 Microsoft Office 格式文件的读写能力,包括 Excel、Word 等。在 Excel 文件的处理中,XLSX.SSF.format 允许开发者根据预定义的格式字符串或自定义的格式字符串来设置单元格的显示格式。基本用法在 Apache POI 中,XSSFDataFormat 类是处理单元格数据格式的关键类。你可以通过 XSSFWorkbook 对象获取到一个 XSSFDataFormat 实例,然后使用它来获取或创建格式字符串。一旦你有了格式字符串,就可以将其应用到 XSSFCellStyle 对象上,进而将该样式应用到单元格上。// 假设 workbook 是已经创建的 XSSFWorkbook 对象 XSSFDataFormat format = workbook.createDataFormat(); short formatIndex = format.getFormat("#,##0.00"); // 获取或创建格式为“#,##0.00”的索引 // 接下来,你可以将这个格式索引设置到单元格样式上 XSSFCellStyle cellStyle = workbook.createCellStyle(); cellStyle.setDataFormat(formatIndex); // 最后,将这个样式应用到单元格上 XSSFCell cell = ...; // 假设这是已经创建的单元格 cell.setCellStyle(cellStyle);格式字符串格式字符串遵循 Excel 的数字格式代码规则,这些规则允许你定义如何显示数字、日期、时间、货币等。例如:#,##0.00:表示带有千位分隔符和两位小数的数字。yyyy-mm-dd:表示日期格式为年-月-日。0.00%:表示百分比,保留两位小数。注意事项当使用 XSSFDataFormat.getFormat(String formatString) 方法时,如果传入的 formatString 已经存在于工作簿的格式列表中,则该方法会返回该格式的索引;如果不存在,则会创建一个新的格式并返回其索引。单元格的显示格式和它的实际值是分开的。即使你改变了单元格的显示格式,单元格的实际值(存储在 XSSFCell 对象中的值)也不会改变。在处理大量数据时,应谨慎使用自定义格式,因为每个自定义格式都会增加工作簿的大小。结论XLSX.SSF.format(在 Apache POI 的上下文中更准确地说是 XSSFDataFormat 和相关的格式字符串)是处理 Excel 文件中单元格数据格式的强大工具。通过它,开发者可以灵活地定义单元格的显示方式,以满足不同的数据处理和展示需求。
-
包含功能为: 1、向Ftp服务器上传文件 2、从Ftp服务器下载特定文件 3、从Ftp某个文件夹里面下载所有文件 4、远程在FTP服务器指定位置创建文件夹 5、查看FTP服务器指定目录内所有文件名 解决了FTP上传/下载文件时,文件名有中文,有特殊字符时无法上传/下载的问题。 写在前面 可以直接运行的,包含上述全部功能的代码已经上传到github上了,下载文件,配置好自己的环境即可直接使用。代码中包含详细注释以及使用方式。 github链接:https://github.com/DylanYh2/FTPClient_libcurl.git libcurl库windows下编译配置 windows系统编译libcurl库,并在visual studio2019/2022使用(win10,win11通用) libcurl c++常用操作 libcurl c++常用操作 libcurl库实现FTP远程文件操作 头文件 #pragma once #include<iostream> #include<curl/curl.h> #include<string> #include<vector> using namespace std; class FtpManage { public: FtpManage(); FtpManage(const string user, const string password, const string id); ~FtpManage(); /* *@Upload: 向Ftp服务器上传文件 * @localFilePath:所上传的文件(精确到文件名),如"D:/ggbond/123.jpg" * @remoteDirectory:上传至Ftp的目录,如"/shjr/" * @remark:将123.jpg上传到Ftp服务器的"/shjr/"目录下, */ bool Upload(const char* localFilePath, const char* remoteDirectory); /* * @Download:从Ftp服务器下载特定文件 * @remoteFilePath:所下载的文件(精确到文件名)如"/shjr/333.jpg" * @localDirectory:保存至本地目录,如"D:/ggbond/" * @remark:将FTp服务器/shjr目录下的333.jpg下载到本地D:/ggbond目录 */ bool DownloadFile(const char* remoteFilePath, const char* localDirectory); /* * @DownloadAllFiles:从Ftp某个文件夹里面下载所有文件 * @remoteFilePath:所下载的文件夹名称,如"/shjr/" * @localDirectory:保存至本地目录,如"D:/ggbond/" * @remark:将FTp服务器/shjr/目录里面下载所有文件到本地D:/ggbond/目录 */ bool DownloadAllFiles(const char* remoteFilePath, const char* localDirectory); /* * @Createdir:创建文件夹 * @directoryname:文件夹名称,可包含上级目录名,要以“/”结尾以表示目录 * 比如:"/ggbond/" */ bool Createdir(const char* directoryname); /* * @GetFilesName:提供获取FTP文件夹内所有文件名的接口 * @filepath:文件夹名称,可包含上级目录名,要以“/”结尾以表示目录 * 比如:"/ggbond/" */ const std::vector<std::string>& GetFilesName(const string filepath); private: /* * @SetURL:初始化URL */ void SetURL(); /* * @UrlEncode:把含有特殊符号的文件名进行URL编码 * @value:文件名 * @"kunkunboy## 123"这种形式是无法传入curl的,需要转换一下 */ std::string UrlEncode(const std::string& value); /* * @GEtFileNameFromPath:从路径中提取文件名并返回 * @filePath:文件完整路径 * @"D:/ggbond/123.jpg"->123.jpg */ std::string GetFileNameFromPath(const std::string& filePath); /* * @GetfilenameFromftp:获得FTP服务器上某个文件夹内所有文件名,如果文件夹内既有文件又有文件夹,则只会显示文件 * @directoryname:文件夹名称,可包含上级目录名,要以“/”结尾以表示目录 */ bool GetfilenameFromftp(const string directoryname); private: std::string Ftp_ip; //Ftp服务器地址 std::string User, Password; //登录用户名及密码 std::string _URL; std::vector<std::string> fNs; //用于记录全部文件名 CURL* curl; }; 向Ftp服务器上传文件 核心代码为: /* *@Upload: 向Ftp服务器上传文件 * @localFilePath:所上传的文件(精确到文件名),如"D:/ggbond/123.jpg" * @remoteDirectory:上传至Ftp的目录,如"/shjr/" * @remark:将123.jpg上传到Ftp服务器的"/shjr/"目录下, */ bool Upload(const char* localFilePath, const char* remoteDirectory); { SetURL(); _URL = Ftp_ip + remoteDirectory+ GetFileNameFromPath(localFilePath); curl_easy_setopt(curl, CURLOPT_URL, _URL.c_str()); // 设置访问的FTP地址 if (curl) { FILE* fp = fopen(localFilePath, "rb"); curl_off_t fsize = 0; if (fp) { fseek(fp, 0, SEEK_END); fsize = ftell(fp); // 获取文件大小 fseek(fp, 0, SEEK_SET); curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); // 指定为上传文件模式 curl_easy_setopt(curl, CURLOPT_READDATA, fp); // 指定上传的文件是哪个 curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, fsize);// 指定上传的文件大小 CURLcode res = curl_easy_perform(curl); // 开始上传 if (res != CURLE_OK) { std::cout << "localFilePath: " << localFilePath << " upload failed!" << curl_easy_strerror(res) << std::endl; curl_easy_cleanup(curl); fclose(fp); return false; } else { std::cout << "localFilePath: " << localFilePath << " upload successfully!" << std::endl; } fclose(fp); } else { std::cout << "file: " << localFilePath << " open failed!" << std::endl; curl_easy_cleanup(curl); return false; } } curl_easy_cleanup(curl); return true; } 从Ftp服务器下载指定文件 核心代码为: /* * @Download:从Ftp服务器下载特定文件 * @remoteFilePath:所下载的文件(精确到文件名)如"/shjr/333.jpg" * @localDirectory:保存至本地目录,如"D:/ggbond/" * @remark:将FTp服务器/shjr目录下的333.jpg下载到本地D:/ggbond目录 */ bool FtpManage::DownloadFile(const char* remoteFilePath, const char* localDirectory) { SetURL(); _URL = Ftp_ip + UrlEncode(remoteFilePath); curl_easy_setopt(curl, CURLOPT_URL, _URL.c_str()); // 设置请求的URL if (curl) { FILE* fp = fopen((localDirectory + GetFileNameFromPath(remoteFilePath)).c_str(), "wb"); if (fp) { // 设置文件写入地址 curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); // 执行任务 CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { std::cout << "DownLoad file:" << remoteFilePath << " failed! " << curl_easy_strerror(res) << std::endl; fclose(fp); curl_easy_cleanup(curl); return false; } else { std::cout << "DownLoad file:" << remoteFilePath << " successfully!" << std::endl; fclose(fp); } } else { std::cout << "file open failed!" << std::endl; curl_easy_cleanup(curl); // 清除curl return false; } } curl_easy_cleanup(curl); return true; } 从Ftp某个文件夹里面下载所有文件 /* * @DownloadAllFiles:从Ftp某个文件夹里面下载所有文件 * @remoteFilePath:所下载的文件夹名称,如"/shjr/" * @localDirectory:保存至本地目录,如"D:/ggbond/" * @remark:将FTp服务器/shjr/目录里面下载所有文件到本地D:/ggbond/目录 */ bool FtpManage::DownloadAllFiles(const char* remoteFilePath, const char* localDirectory) { if (GetfilenameFromftp(remoteFilePath)) { for (const auto& fns : fNs) { std::string filename = remoteFilePath + fns; bool res = DownloadFile(filename.c_str(), localDirectory); if (res) continue; else { return false; } } } return true; } 远程在FTP上创建文件夹 /* * @Createdir:创建文件夹 * @directoryname:文件夹名称,可包含上级目录名,要以“/”结尾以表示目录 * 比如:"/ggbond/" */ bool FtpManage::Createdir(const char* directoryname) { if (directoryname == nullptr) { std::cout << "directoryname is NULL!" << std::endl; return false; } SetURL(); _URL = Ftp_ip + UrlEncode(directoryname); curl_easy_setopt(curl, CURLOPT_URL, _URL.c_str()); // 指定url curl_easy_setopt(curl, CURLOPT_FTP_CREATE_MISSING_DIRS, 1L); //如果目录不存在则创建一个 CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { std::cout << "Directory:" << directoryname << "create failed!" << curl_easy_strerror(res) << std::endl; curl_easy_cleanup(curl); // 清理curl return false; } else { std::cout << "Create directory:" << directoryname << " successfully!" << std::endl; } curl_easy_cleanup(curl); return true; } 获取FTP文件夹内所有文件名 /* * @GetFilesName:提供获取FTP文件夹内所有文件名的接口 * @filepath:文件夹名称,可包含上级目录名,要以“/”结尾以表示目录 * 比如:"/ggbond/" */ //接收消息回调函数 size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* output) { size_t totalSize = size * nmemb; output->append(static_cast<char*>(contents), totalSize); return totalSize; } // 获取ftp某个文件夹内文件名 bool FtpManage::GetfilenameFromftp(const std::string filePath) { SetURL(); std::string path = Ftp_ip + UrlEncode(filePath); std::string fileName; // 文件名列表保存位置 if (curl) { curl_easy_setopt(curl, CURLOPT_URL, path.c_str()); // 设置访问URL curl_easy_setopt(curl, CURLOPT_DIRLISTONLY, 1L); // 设置只返回文件 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &fileName); // 设置只获取文件名列表 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); // 设置get的回调函数 if (curl_easy_perform(curl) == CURLE_OK) //执行任务 { fNs.clear(); size_t startPos = 0, endPos = 0; while ((endPos = fileName.find('\n', startPos)) != std::string::npos ) { std::string name = fileName.substr(startPos, endPos - startPos-1); fNs.emplace_back(name); startPos = endPos + 1; } } } else { return false; } curl_easy_cleanup(curl); return true; } // 获取FTP文件夹内所有文件名的接口函数 const std::vector<std::string>& FtpManage::GetFilesName(const string filepath) { if (GetfilenameFromftp(filepath)) { return fNs; } else { return std::vector<std::string>(); } } 解决文件名中空格、特殊字符无法出现错误 // 对文件名进行URL编码,否则文件名中有空格,特殊符合(#^...)会出现URL错误 std::string FtpManage::UrlEncode(const std::string &value) { CURL* curl = curl_easy_init(); if (!curl) return ""; char* output = curl_easy_escape(curl, value.c_str(), value.length()); if (output) { std::string encoded(output); curl_free(output); curl_easy_cleanup(curl); return encoded; } curl_easy_cleanup(curl); return ""; } 其他相关函数 // 把带有路径的文件转换为文件名 D:/ggbond/123.jpg->123.jpg std::string FtpManage::GetFileNameFromPath(const std::string& filePath) { if (filePath == "") return filePath; //去除路径最后的空格 string retPath; size_t lastBlank = filePath.find_last_not_of(' '); if (lastBlank != std::string::npos) { retPath = filePath.substr(0, lastBlank+1); } else { return ""; } size_t lastSlashPos = retPath.find_last_of("/\\"); if (lastSlashPos != std::string::npos) { return retPath.substr(lastSlashPos + 1); } else { // 如果没有找到斜杠或反斜杠,整个路径就是文件名 return retPath; } } ———————————————— 原文链接:https://blog.csdn.net/yh_secret/article/details/140661525
-
FTP(File Transfer Protocol,文件传输协议)是一种用于在计算机网络上进行文件传输的标准协议。它定义了客户端和服务器之间的通信规则,使得用户可以通过网络上传和下载文件,以下分别是文件的下载以及上的过程。 1. 文件下载的过程 ① 建立连接 // 创建了一个名为ftpClient的FTP客户端对象 FTPClient ftpClient = new FTPClient(); // 指定文件下载的路径,通过ftp访问指定目录并下载文件 try (FileOutputStream out = new FileOutputStream("下载路径" + System.currentTimeMillis())){ // 连接ftp服务器(ftp默认的端口号为21)并验证用户名、密码 ftpClient.connect("ip地址",21); ftpClient.login("用户名", "密码"); } ② 切换至个人目录 boolean isChange = ftpClient.changeWorkingDirectory("个人目录名"); System.out.println("目录切换成功了吗?" + isChange); ③ 下载指定文件 // 设置文件的类型 ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); // 下载文件,并确定结果 boolean isRetrieve = ftpClient.retrieveFile("待下载的文件名", out); 2. 文件上传过程 ① 连接ftp服务器并验证用户名、密码 无论是上传文件或是下载文件,第一部都需要先建立连接,因此,第一步的实现方法是相同的,不过,由于下载文件时是将读取到的文件存放至本地文件,因此采用的是文件输出流,但在文件上传过程中,是将本地文件读取至程序中并上传,因此需要定义的是文件输入流,以便读取本地文件并上传。 ② 切换目录至上传文件处 // 通过changeWorkingDirectory()方法切换目录并判断是否切换成功 boolean isChange = ftpClient.changeWorkingDirectory("目标目录名"); // 获取当前目录中的所有文件 FTPFile[] ftpFileList = ftpClient.listFiles(); // 如若没有你想上传的目录也可以自行建立一个目录 // 创建个人目录 ftpClient.makeDirectory("创建的目录名"); // 切换至新建目录 ftpClient.changeWorkingDirectory("创建的目录"); ③ 上传文件并存放 ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); // 存文件至FTP boolean isStore = ftpClient.storeFile(System.currentTimeMillis(), 文件输入流); ———————————————— 原文链接:https://blog.csdn.net/qq_72172339/article/details/131645398
-
public void downloadFileStream(HttpServletResponse response, String path, String fileName) { OutputStream outputStream = null; FTPClient ftpClient = getFtpClient(); InputStream in = null; BufferedInputStream bis = null; try { int index = path.indexOf("/"); int index1 = path.indexOf("/", index + 2); //文件路径 去除掉ftp地址 String filePath = path.substring(index1) ftpClient.setFileType(FTP.BINARY_FILE_TYPE); // 设置文件ContentType类型,这样设置,会自动判断下载文件类型 response.setContentType("application/x-msdownload"); // 设置文件头:最后一个参数是设置下载的文件名并编码为UTF-8 response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")); // 此句代码适用Linux的FTP服务器 String newPath = new String(fileName.getBytes("GBK"), StandardCharsets.ISO_8859_1); ftpClient.enterLocalPassiveMode(); // 设置被动模式,开通一个端口来传输数据 boolean result = existFile(filePath, ftpClient); if (result) { ftpClient.changeWorkingDirectory(path); in = ftpClient.retrieveFileStream(newPath); bis = new BufferedInputStream(in); outputStream = response.getOutputStream(); byte[] buf = new byte[1024]; int len = 0; while ((len = bis.read(buf)) > 0) { outputStream.write(buf, 0, len); } outputStream.flush(); } } catch (IOException e) { log.error("下载文件出错!", (Object) e.getStackTrace()); } finally { releaseFtpClient(ftpClient); if (null != outputStream) { try { outputStream.close(); } catch (IOException e) { log.error("关闭输出流出错!", (Object) e.getStackTrace()); } } if (bis != null) { try { bis.close(); } catch (IOException e) { e.printStackTrace(); } } if (in != null) { try { in.close(); //需要注意,在使用ftpClient.retrieveFileStream,一定要加completePendingCommand() //调用这个接口后,一定要手动close掉返回的InputStream,然后再调用completePendingCommand方法,若不是按照这个顺序,则会导致后面对FTPClient的操作都失败 ftpClient.completePendingCommand(); } catch (IOException e) { e.printStackTrace(); } } } } ———————————————— 原文链接:https://blog.csdn.net/weixin_40388298/article/details/119907061
上滑加载中
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签