• [技术干货] Java必修课——Spring框架
    一、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 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学习
    使用 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的官网网站: 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概述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非常重要。在实际开发中,应根据具体需求选择最合适的数据结构。
  • [技术干货] Apache POI 处理 Excel 文件
    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 文件中单元格数据格式的强大工具。通过它,开发者可以灵活地定义单元格的显示方式,以满足不同的数据处理和展示需求。
  • [技术干货] 使用libcurl库实现FTP远程文件操作
    包含功能为: 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协议进行文件的上传和下载
    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 
  • [技术干货] java FtpClient文件下载方法
     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 
  • [技术干货] java:ftpclient实现文件上传下载,使用原文件名
     import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream;  import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPFile;  public class FtpFileTransferWithOriginalName {      public static void main(String[] args) {         String server = "your_server";         int port = 21;         String user = "your_user";         String pass = "your_password";          // 下载文件         downloadFile(server, port, user, pass, "/remote_path/file.txt", "/local_path");          // 上传文件         uploadFile(server, port, user, pass, "/local_path/file.txt", "/remote_path");     }      public static void downloadFile(String server, int port, String user, String pass, String remotePath, String localPath) {         FTPClient ftpClient = new FTPClient();         try {             ftpClient.connect(server, port);             ftpClient.login(user, pass);             ftpClient.enterLocalPassiveMode();              FTPFile[] files = ftpClient.listFiles(remotePath);             if (files.length > 0) {                 FTPFile file = files[0];                 String originalFileName = file.getName();  // 获取原文件名                 File localFile = new File(localPath + File.separator + originalFileName);  // 使用原文件名创建本地文件                 OutputStream outputStream = new FileOutputStream(localFile);                 ftpClient.retrieveFile(remotePath, outputStream);                 outputStream.close();             }              ftpClient.logout();         } catch (IOException e) {             e.printStackTrace();         } finally {             try {                 if (ftpClient.isConnected()) {                     ftpClient.disconnect();                 }             } catch (IOException e) {                 e.printStackTrace();             }         }     }      public static void uploadFile(String server, int port, String user, String pass, String localPath, String remotePath) {         FTPClient ftpClient = new FTPClient();         try {             ftpClient.connect(server, port);             ftpClient.login(user, pass);             ftpClient.enterLocalPassiveMode();              File localFile = new File(localPath);             String originalFileName = localFile.getName();  // 获取原文件名             // 检查服务器端是否存在同名文件,如果存在则添加后缀避免冲突             boolean fileExists = checkFileExists(ftpClient, remotePath + File.separator + originalFileName);             if (fileExists) {                 originalFileName = addSuffixToFileName(originalFileName);             }             ftpClient.storeFile(remotePath + File.separator + originalFileName, new FileInputStream(localFile));              ftpClient.logout();         } catch (IOException e) {             e.printStackTrace();         } finally {             try {                 if (ftpClient.isConnected()) {                     ftpClient.disconnect();                 }             } catch (IOException e) {                 e.printStackTrace();             }         }     }      // 检查服务器端指定路径是否存在文件     public static boolean checkFileExists(FTPClient ftpClient, String filePath) throws IOException {         FTPFile[] files = ftpClient.listFiles(filePath);         return files.length > 0;     }      // 给文件名添加后缀以避免冲突     public static String addSuffixToFileName(String fileName) {         return fileName + "_new";     } } ————————————————               原文链接:https://blog.csdn.net/hongyuxiongji/article/details/140848594 
  • [技术干货] Java从FTP下载文件到本地前端+后端
    一、前端 1.首先创建下载文件按钮 <template slot-scope="scope">   <div class="Style_center">      <el-button @click="downloadFile(scope.row)" type="text" >下载文件</el-button>   </div> </template> 2.其次编写手动触发事件 downloadFile(row){    this.axios.post("/Zlhzjbxxb/downloadFile", {wjdz:row.wjdz, wjmc:row.wjmc},{       headers: {   //解决后端接收时多了一个 =        'Content-Type': 'application/json'       }    }).then(res => {        this.$message.success("成功下载至桌面");       }) }         wjdz和wjmc是要传给后端的值。后端获取到文件的FTP路径的时候,路径末尾会莫名的多一个等于(=)的符号,反正我的是出现了,把headers加上就完美解决了  二、后端         结尾处贴附后端全码          1.用Map来接收前端传过来的数据  public void downloadFile(@RequestBody Map<String,String> map) throws UnsupportedEncodingException {           String wjdz = map.get("wjdz");         String wjmc = map.get("wjmc"); }         2.获取的文件路径,比如C:\Users\Administrator\Desktop,中间的 \ 符号会被转义,所以需要转义回来,其它符号的转义可以百度一下哦。  //解决文件名中间的符号被转义(如“/”被转义成%2F) wjdz = wjdz.replaceAll("%(?![0-9a-fA-F]{2})", "%2F"); String wjdzStr = URLDecoder.decode(wjdz, "UTF-8");         3.在上传FTP文件的时候,为了防止重复的文件名在FTP服务器里出现,于是给每一个文件都设置了不同的文件编号,格式为xxx/2021-05-09/**************(*是uuid创建拼接在后面的),在FTP服务器里面下载文件的话需要找到具体的目录,然后再去下载具体的文件。接下来开始获取FTP文件的名称以及目录名   //获取第一个“/”的位置 int index = wjdzStr.indexOf("/");   //获取第二个 “/” 的位置 index = wjdzStr.indexOf("/", index + 1);   //获取第二个 “/” 的位置后面的字符串 String result = wjdzStr.substring(index + 1);   //获取第二个 “/” 的位置之前的字符串 String menuDz = wjdzStr.substring(0,index + 1);         通过以上方式来获取文件的目录和文件名称,result就是需要下载文件的名称,menuDz就是文件所在的FTP服务器里面的目录          4.接下来就是设置文件的保存地址了  FileOutputStream out = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\" + wjmc )         这里我是给设置到了桌面,后面的wjmc是该文件的中文名称,总不能给用户展示文件的路径吧......          5.开始连接FTP服务器并且登录  FTPClient ftpClient = new FTPClient(); ftpClient.connect(hostName, port);//连接 ftpClient.login(userName, password);//登陆         这里的hostname是自己的IP地址,port是端口号(默认21),userName是用户名,password就是密码了,如果在yml文件里面封装的有,便可以直接调用,没有就自己手动填上去也是一样的          6.登上去之后便开始下载文件了,首先找到文件目录,然后再去下载  ftpClient.changeWorkingDirectory(menuDz);//切换目录   //下载文件( 获取FTP服务器指定目录的文件) //参数result,服务器指定文件 //参数out,本地输出流(负责下载后写入) ftpClient.setFileType(ftpClient.BINARY_FILE_TYPE); ftpClient.retrieveFile(result,out);          ftpClient.setFileType这个就是用来配置文件传输形式的,默认ASCII,我们一般都是配置用二进制形式传送          7.最后最后就是退出登录和关闭连接了  ftpClient.logout();// 退出登录 ftpClient.disconnect();// 断开连接         8.接下来就是后端的全码了  @PostMapping(value = "/downloadFile") @ResponseBody public void downloadFile(@RequestBody Map<String,String> map) throws UnsupportedEncodingException {           String wjdz = map.get("wjdz");         String wjmc = map.get("wjmc");           //解决文件名中间的符号被转义(如“/”被转义成%2F)         wjdz = wjdz.replaceAll("%(?![0-9a-fA-F]{2})", "%2F");         String wjdzStr = URLDecoder.decode(wjdz, "UTF-8");           FTPClient ftpClient = new FTPClient();         int index = wjdzStr.indexOf("/"); //获取第一个“/”的位置         index = wjdzStr.indexOf("/", index + 1);//获取第二个 “/” 的位置         String result = wjdzStr.substring(index + 1);//获取第二个 “/” 的位置后面的字符串         String menuDz = wjdzStr.substring(0,index + 1);//获取第二个 “/” 的位置之前的字符串         try (FileOutputStream out = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\" + wjmc )) {               FtpUtil ftpUtil = new FtpUtil();             ftpClient.connect(ftpUtil.hostName, ftpUtil.port);//连接             ftpClient.login(ftpUtil.userName, ftpUtil.password);//登陆             ftpClient.changeWorkingDirectory(menuDz);//切换目录               //下载文件( 获取FTP服务器指定目录的文件)             //参数result,服务器指定文件             //参数out,本地输出流(负责下载后写入)             ftpClient.setFileType(ftpClient.BINARY_FILE_TYPE);             ftpClient.retrieveFile(result,out);           } catch (SocketException e) {             e.printStackTrace();         } catch (FileNotFoundException e) {             e.printStackTrace();         } catch (IOException e) {             e.printStackTrace();         } finally {             try {                 ftpClient.logout();// 退出登录                 ftpClient.disconnect();// 断开连接             } catch (IOException e) {                 e.printStackTrace();             }         }     }          记录这些方便自己的同时也想能帮助一下和我遇到同样困惑的人,有什么不足或者不对的地方还望大佬们指出 ————————————————                    原文链接:https://blog.csdn.net/glacierW/article/details/130581947 
  • [技术干货] JAVA实现FTP文件传输
    一、FTP介绍 FTP是File Transfer Protocol(文件传输协议)的英文简称,即文件协议。用于Internet上的控制文件的双向传输。同时,它是一个应用程序(Application)。基于不同的操作系统有不同的FTP应用程序,而所有的应用程序都遵守同一种协议以传输文件。在FTP的使用中,用户经常遇到两个概念:下载(Download)和上次(Upload)。下载文件就是从远程主机拷贝文件至自己的计算机上;删除文件就是将文件从自己的计算机中拷贝至远程主机上。用Internet语言来说,用户可通过客户机程序向远程主机上传、下载文件(摘自百度百科)。  二、准备工作 1、在服务器安装FTP服务 FTP服务需要下FileZilla_Server服务安装程序,大家可以在网上搜索,也可以通过我的资源下载 点击下载 2、编写Java连接FTP源码 大家也可以通过CSDN资源下载我的源码 点击下载 三、实现步骤 1、编写FTP-Config.properties配置属性文件 FTP_IP=localhost FTP_PORT=21 FTP_USER=wangjunwei FTP_PASSWORD=wangjunwei FTP_PATH=/FTP FTP_DRIVELETTER=E: 2、编写FTPUtil读取FTP配置属性文件 /**      * FTP服务地址      */     private static String FTP_IP;     /**      * FTP端口号      */     private static Integer FTP_PORT;     /**      * FTP用户名      */     private static String FTP_USER;     /**      * FTP密码      */     private static String FTP_PASSWORD;     /**      * FTP根路径      */     private static String FTP_PATH;     /**      * 映射盘符      */     private static String FTP_DRIVELETTER;     static{         try {             Properties properties = new Properties();             InputStream inputStream = FtpUtil.class.getResourceAsStream("/FTP-Config.properties");             properties.load(inputStream);             FTP_IP = properties.getProperty("FTP_IP");             FTP_PORT = Integer.valueOf(properties.getProperty("FTP_PORT"));             FTP_USER = properties.getProperty("FTP_USER");             FTP_PASSWORD = properties.getProperty("FTP_PASSWORD");             FTP_PATH = properties.getProperty("FTP_PATH");             FTP_DRIVELETTER = properties.getProperty("FTP_DRIVELETTER");         } catch (IOException e) {             e.printStackTrace();         }     }  3、连接(登录)FTP服务端获取授权 private static FTPClient ftpClient;       public static FTPClient connectionFTPServer() {         ftpClient = new FTPClient();         try {             ftpClient.connect(FtpUtil.getFTP_IP(), FtpUtil.getFTP_PORT());             ftpClient.login(FtpUtil.getFTP_USER(), FtpUtil.getFTP_PASSWORD());             if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {                 logger.info("==============未连接到FTP,用户名或密码错误=================");                 ftpClient.disconnect();             } else {                 logger.info("==============连接到FTP成功=================");             }         } catch (SocketException e) {             e.printStackTrace();             logger.info("==============FTP的IP地址错误==============");         } catch (IOException e) {             e.printStackTrace();             logger.info("==============FTP的端口错误==============");         }         return ftpClient;     }  4、编写文件上传、下载相关方法 /**      * 切换到父目录      *       * @return 切换结果 true:成功, false:失败      */     private static boolean changeToParentDir() {         if (!ftpClient.isConnected()) {             return false;         }         try {             return ftpClient.changeToParentDirectory();         } catch (IOException e) {             e.printStackTrace();             return false;         }     }       /**      * 改变当前目录到指定目录      *       * @param dir      *            目的目录      * @return 切换结果 true:成功,false:失败      */     private static boolean cd(String dir) {         if (!ftpClient.isConnected()) {             return false;         }         try {             return ftpClient.changeWorkingDirectory(dir);         } catch (IOException e) {             e.printStackTrace();             return false;         }     }       /**      * 获取目录下所有的文件名称      *       * @param filePath      *            指定的目录      * @return 文件列表,或者null      */     private static FTPFile[] getFileList(String filePath) {         if (!ftpClient.isConnected()) {             return null;         }         try {             return ftpClient.listFiles(filePath);         } catch (IOException e) {             e.printStackTrace();             return null;         }     }       /**      * 切换工作目录      *       * @param ftpPath      *            目的目录      * @return 切换结果      */     public static boolean changeDir(String ftpPath) {         if (!ftpClient.isConnected()) {             return false;         }         try {             // 将路径中的斜杠统一             char[] chars = ftpPath.toCharArray();             StringBuffer sbStr = new StringBuffer(256);             for (int i = 0; i < chars.length; i++) {                 if ('\\' == chars[i]) {                     sbStr.append('/');                 } else {                     sbStr.append(chars[i]);                 }             }             ftpPath = sbStr.toString();             if (ftpPath.indexOf('/') == -1) {                 // 只有一层目录                 ftpClient.changeWorkingDirectory(new String(ftpPath.getBytes(), "iso-8859-1"));             } else {                 // 多层目录循环创建                 String[] paths = ftpPath.split("/");                 for (int i = 0; i < paths.length; i++) {                     ftpClient.changeWorkingDirectory(new String(paths[i].getBytes(), "iso-8859-1"));                 }             }             return true;         } catch (Exception e) {             e.printStackTrace();             return false;         }     }       /**      * 循环创建目录,并且创建完目录后,设置工作目录为当前创建的目录下      *       * @param ftpPath      *            需要创建的目录      * @return      */     public static boolean mkDir(String ftpPath) {         if (!ftpClient.isConnected()) {             return false;         }         try {             // 将路径中的斜杠统一             char[] chars = ftpPath.toCharArray();             StringBuffer sbStr = new StringBuffer(256);             for (int i = 0; i < chars.length; i++) {                 if ('\\' == chars[i]) {                     sbStr.append('/');                 } else {                     sbStr.append(chars[i]);                 }             }             ftpPath = sbStr.toString();             if (ftpPath.indexOf('/') == -1) {                 // 只有一层目录                 ftpClient.makeDirectory(new String(ftpPath.getBytes(), "iso-8859-1"));                 ftpClient.changeWorkingDirectory(new String(ftpPath.getBytes(), "iso-8859-1"));             } else {                 // 多层目录循环创建                 String[] paths = ftpPath.split("/");                 for (int i = 0; i < paths.length; i++) {                     ftpClient.makeDirectory(new String(paths[i].getBytes(), "iso-8859-1"));                     ftpClient.changeWorkingDirectory(new String(paths[i].getBytes(), "iso-8859-1"));                 }             }             return true;         } catch (Exception e) {             e.printStackTrace();             return false;         }     }       /**      * 上传单个文件      *       * @param inputStream      *            单个文件流对象      * @param newFileName      *            新文件名      * @param folder      *            自定义保存的文件夹      * @return 上传结果      */     public static boolean uploadSingleAttachment(File file, String newFileName, String folder) {         FtpUtil.connectionFTPServer();         if (!ftpClient.isConnected()) {             logger.info("==============FTP服务已断开==============");             return false;         }         boolean result = false;         if (ftpClient != null) {             FileInputStream fileInputStream = null;             try {                 fileInputStream = new FileInputStream(file);                 if(StringUtils.isNotEmpty(folder)){                     FtpUtil.mkDir(folder);                 }                 ftpClient.setBufferSize(100000);                 ftpClient.setControlEncoding("utf-8");                 ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);                 result = ftpClient.storeFile(new String(newFileName.getBytes(), "iso-8859-1"), fileInputStream);             } catch (Exception e) {                 FtpUtil.close();                 e.printStackTrace();                 return false;             } finally {                 try {                     if (ftpClient.isConnected()) {                         FtpUtil.close();                     }                     fileInputStream.close();                 } catch (IOException e) {                     e.printStackTrace();                 }             }         }         return result;     }     /**      * 上传多个文件      *       * @param inputStreams      *            多文件流对象数组      * @param newFileNames      *            多文件名数组(与流对象数组一一对应)      * @param folder      *            自定义保存的文件夹      * @return 上传结果      */     public static String[] uploadBatchAttachment(File[] files , String[] newFileNames, String folder) {         String[] filesSaveUrls = new String[files.length];         FtpUtil.connectionFTPServer();         if (!ftpClient.isConnected()) {             logger.info("==============FTP服务已断开==============");             return null;         }         if (ftpClient != null) {             FileInputStream fileInputStream = null;             try {                 if(StringUtils.isNotEmpty(folder)){                     FtpUtil.mkDir(folder);                 }                 for (int i = 0; i < files.length; i++) {                     filesSaveUrls[i] = FtpUtil.FTP_PATH;                     fileInputStream = new FileInputStream(files[i]);                     if(StringUtils.isNotEmpty(folder)){                         filesSaveUrls[i] += "/"+folder;                     }                     ftpClient.setBufferSize(100000);                     ftpClient.setControlEncoding("utf-8");                     ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);                     ftpClient.storeFile(new String(newFileNames[i].getBytes(), "iso-8859-1"), fileInputStream);                     filesSaveUrls[i] += "/"+newFileNames[i];                 }             } catch (Exception e) {                 FtpUtil.close();                 e.printStackTrace();                 return null;             } finally {                 try {                     if (ftpClient.isConnected()) {                         FtpUtil.close();                     }                     fileInputStream.close();                 } catch (IOException e) {                     e.printStackTrace();                 }             }         }         return filesSaveUrls;     }     /**      * 上传单个文件      *       * @param inputStreams      *            多文件流对象数组      * @param newFileNames      *            多文件名数组(与流对象数组一一对应)      * @param folder      *            自定义保存的文件夹      * @return 上传结果      */     public static String uploadSingleAttachment(MultipartFile multipartFile, String newFileName, String folder) {         String filesSaveUrl = null;         FtpUtil.connectionFTPServer();         if (!ftpClient.isConnected()) {             logger.info("==============FTP服务已断开==============");             return null;         }         if (ftpClient != null) {             try {                 if(StringUtils.isNotEmpty(folder)){                     FtpUtil.mkDir(folder);                 }                 filesSaveUrl = FtpUtil.FTP_PATH;                 if(StringUtils.isNotEmpty(folder)){                     filesSaveUrl += "/"+folder;                 }                 ftpClient.setBufferSize(100000);                 ftpClient.setControlEncoding("utf-8");                 ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);                 ftpClient.storeFile(new String(newFileName.getBytes(), "iso-8859-1"), multipartFile.getInputStream());                 filesSaveUrl += "/"+newFileName;             } catch (Exception e) {                 FtpUtil.close();                 e.printStackTrace();                 return null;             } finally {                 if (ftpClient.isConnected()) {                     FtpUtil.close();                 }             }         }         return filesSaveUrl;     }     /**      * 上传多个文件      *       * @param inputStreams      *            多文件流对象数组      * @param newFileNames      *            多文件名数组(与流对象数组一一对应)      * @param folder      *            自定义保存的文件夹      * @return 上传结果      */     public static String[] uploadBatchAttachment(MultipartFile[] multipartFiles , String[] newFileNames, String folder) {         String[] filesSaveUrls = new String[multipartFiles.length];         FtpUtil.connectionFTPServer();         if (!ftpClient.isConnected()) {             logger.info("==============FTP服务已断开==============");             return null;         }         if (ftpClient != null) {             InputStream inputStream = null;             try {                 if(StringUtils.isNotEmpty(folder)){                     FtpUtil.mkDir(folder);                 }                 for (int i = 0; i < multipartFiles.length; i++) {                     filesSaveUrls[i] = FtpUtil.FTP_PATH;                     inputStream = multipartFiles[i].getInputStream();                     if(StringUtils.isNotEmpty(folder)){                         filesSaveUrls[i] += "/"+folder;                     }                     ftpClient.setBufferSize(100000);                     ftpClient.setControlEncoding("utf-8");                     ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);                     ftpClient.storeFile(new String(newFileNames[i].getBytes(), "iso-8859-1"), inputStream);                     filesSaveUrls[i] += "/"+newFileNames[i];                 }             } catch (Exception e) {                 FtpUtil.close();                 e.printStackTrace();                 return null;             } finally {                 try {                     if (ftpClient.isConnected()) {                         FtpUtil.close();                     }                     inputStream.close();                 } catch (IOException e) {                     e.printStackTrace();                 }             }         }         return filesSaveUrls;     }       /**      * 根据文件url下载文件      *       * @param path 被下载文件url      * @return 文件流对象      * @throws IOException      */     public static final InputStream downLoadFile(String path,String filaName){         FtpUtil.connectionFTPServer();         if (!ftpClient.isConnected()) {             logger.info("==============FTP服务已断开==============");             return null;         }          ftpClient.enterLocalPassiveMode();          FtpUtil.changeDir(path);          try {             return ftpClient.retrieveFileStream(filaName);         } catch (IOException e) {             logger.info("==============获取文件异常==============");             e.printStackTrace();             return null;         }finally{             if (ftpClient.isConnected()) {                 FtpUtil.close();             }         }     }           /**      * 返回FTP目录下的文件列表      *       * @param pathName      * @return      */     public static String[] getFileNameList(String pathName) {         if (!ftpClient.isConnected()) {             return null;         }         try {             return ftpClient.listNames(pathName);         } catch (IOException e) {             e.printStackTrace();             return null;         }     }       /**      * 删除FTP上的文件      *       * @param ftpDirAndFileName      *            路径开头不能加/,比如应该是test/filename1      * @return      */     public static boolean deleteFile(String ftpDirAndFileName) {         if (!ftpClient.isConnected()) {             FtpUtil.connectionFTPServer();         }         try {             return ftpClient.deleteFile(ftpDirAndFileName);         } catch (IOException e) {             e.printStackTrace();             return false;         }     }       /**      * 删除FTP目录      *       * @param ftpDirectory      * @return      */     public static boolean deleteDirectory(String ftpDirectory) {         if (!ftpClient.isConnected()) {             return false;         }         try {             return ftpClient.removeDirectory(ftpDirectory);         } catch (IOException e) {             e.printStackTrace();             return false;         }     }       /**      * 关闭链接      */     public static void close() {         try {             if (ftpClient != null && ftpClient.isConnected()) {                 ftpClient.disconnect();             }         } catch (Exception e) {             e.printStackTrace();         }     } ————————————————              原文链接:https://blog.csdn.net/brianang/article/details/78727835 
  • [技术干货] Java实现文件上传到ftp服务器
    一、ftp简介 文件传输协议(File Transfer Protocol,FTP)是用于在网络上进行文件传输的一套标准协议,它工作在 OSI 模型的第七层, TCP 模型的第四层, 即应用层, 使用 TCP 传输而不是 UDP, 客户在和服务器建立连接前要经过一个“三次握手”的过程, 保证客户与服务器之间的连接是可靠的, 而且是面向连接, 为数据传输提供可靠保证。  FTP允许用户以文件操作的方式(如文件的增、删、改、查、传送等)与另一主机相互通信。然而, 用户并不真正登录到自己想要存取的计算机上面而成为完全用户, 可用FTP程序访问远程资源, 实现用户往返传输文件、目录管理以及访问电子邮件等等, 即使双方计算机可能配有不同的操作系统和文件存储方式。  1.1工作流程 在传输文件时,FTP 客户端程序先与服务器建立连接,然后向服务器发送命令。服务器收到命令后给予响应,并执行命令。FTP 协议与操作系统无关,任何操作系统上的程序只要符合 FTP 协议,就可以相互传输数据  1.2工作模式 FTP支持两种模式:Standard (PORT方式,主动方式),Passive (PASV,被动方式) 1.3传输方式 FTP的传输有两种方式:ASCII、二进制 二、windows开启ftp服务 在自己的电脑上可以开启一个ftp服务用于测试。 1:打开控制面板—>程序—>启动或关闭windows功能 找到互联网信息服务勾选其中的ftp服务器,web管理,万维网服务。 系统就会安装IIS服务管理器了,安装过程可能需要等待几分钟。 2:回到电脑桌面,右击“计算机”,点击 管理,进入计算机管理界面。在这里,我们就可以看到刚刚添加的IIS服务,接下来的操作就像我们在VPS主机上添加网站差不多的操作步骤了。选中IIS服务——>网站——>右键添加FTP站点 3:设置ftp基本信息。路径为远程连接的工作路径 选取本机的IP地址,SSL如果不涉密可以选无。 这里权限先这样设置之后再修改,点击完成即可。 保存后在此后即可看到添加的ftp服务。可以这样开启多个。 注意: 要想通过java程序连接FTP在这里我新建了一个FTP的用户,新建一个用户ftptest,密码:123456,如下所示 这里注意一定要把√取消掉确定完成新建,之后修改FTP服务器设置,添加这个特定用户。 点击创建的ftp名称—>ftp身份验证—>修改身份认证,禁止匿名身份 返回最开始的页面,添加刚才创建的用户,如果创建用户对勾选着可能添加不成功。 输入刚才新建的用户名和密码,完成 如何测试刚才新建的用户是否有效嘞有好多种方法常用的是CMD命令行和下面介绍的这种 打开我的电脑在最上面输入 ftp://+FTP之前设置的IP地址 我的是ftp://192.168.10.11/回车 输入用户和密码即可登录 注意:如果输入后弹出FTP文件夹错误,需要查看一下防火墙是否放行FTP服务器 解决方式: 1、打开防火墙,点击允许应用或功能通过Windows Defender防火墙 2、找到FTP服务器并勾选,然后点击确定 三、java连接ftp org.apache.commons.net提供了对FTP的开发,引入以下依赖 <dependency>     <groupId>commons-net</groupId>     <artifactId>commons-net</artifactId>     <version>3.9.0</version> </dependency> 3.1ftp工具类 连接ftp服务  @Slf4j public class FtpClient {      /**     * FTP服务地址     */    private static String FTP_IP;    /**     * FTP端口号     */    private static Integer FTP_PORT;    /**     * FTP用户名     */    private static String FTP_USER;    /**     * FTP密码     */    private static String FTP_PASSWORD;    /**     * FTP根路径     */    private static String FTP_PATH;    /**     * 映射盘符     */    private static String FTP_DRIVELETTER;    private static FTPClient ftpClient;      static {       try {          // 根据该类的class文件获取到yaml文件          Yaml yaml = new Yaml();          URL resource = FtpClient.class.getClassLoader().getResource("application-dev.yml");          assert resource != null;          // 把yaml文件加载到对象中          Object obj = yaml.load(new FileInputStream(resource.getFile()));          // 解析对象中的属性并赋值          JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(obj));          JSONObject ftp = jsonObject.getObject("fanhai", JSONObject.class).getObject("ftp", JSONObject.class);          FTP_IP = String.valueOf(ftp.get("ip"));          FTP_PORT = Integer.valueOf(String.valueOf(ftp.get("port")));          FTP_USER = String.valueOf(ftp.get("username"));          FTP_PASSWORD = String.valueOf(ftp.get("password"));       } catch (Exception e) {          e.printStackTrace();       }    }      public static FTPClient getFtpConnection() {       ftpClient = new FTPClient();       try {          //连接          ftpClient.connect(FtpClient.FTP_IP, FtpClient.FTP_PORT);          //设置编码          ftpClient.setControlEncoding("UTF-8");          //设置传输文件类型          ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);          //登录          ftpClient.login(FtpClient.FTP_USER, FtpClient.FTP_PASSWORD);          if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {             log.info("==============未连接到FTP,用户名或密码错误=================");             //拒绝连接             ftpClient.disconnect();          } else {             log.info("==============连接到FTP成功=================");          }       } catch (SocketException e) {          e.printStackTrace();          log.info("==============FTP的IP地址错误==============");       } catch (IOException e) {          e.printStackTrace();          log.info("==============FTP的端口错误==============");       }       return ftpClient;    }      public static void closeConnect() {       log.warn("关闭ftp服务器");       try {          if (ftpClient != null) {             ftpClient.logout();             ftpClient.disconnect();          }       } catch (Exception e) {          e.printStackTrace();       }    } }  ftp工具类,进行文件的上传、下载操作 ————————————————               原文链接:https://blog.csdn.net/weixin_44486583/article/details/130319858 
  • [技术干货] java实现把文件上传至ftp服务器
    用java实现ftp文件上传。我使用的是commons-net-1.4.1.zip。其中包含了众多的java网络编程的工具包。 1 把commons-net-1.4.1.jar包加载到项目工程中去。 2 看如下代码:  import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPReply;   public class FileTool {       /**      * Description: 向FTP服务器上传文件      * @Version      1.0      * @param url FTP服务器hostname      * @param port  FTP服务器端口      * @param username FTP登录账号      * @param password  FTP登录密码      * @param path  FTP服务器保存目录      * @param filename  上传到FTP服务器上的文件名      * @param input   输入流      * @return 成功返回true,否则返回false *      */     public static boolean uploadFile(String url,// FTP服务器hostname             int port,// FTP服务器端口             String username, // FTP登录账号             String password, // FTP登录密码             String path, // FTP服务器保存目录             String filename, // 上传到FTP服务器上的文件名             InputStream input // 输入流     ){         boolean success = false;         FTPClient ftp = new FTPClient();         ftp.setControlEncoding("GBK");         try {             int reply;             ftp.connect(url, port);// 连接FTP服务器             // 如果采用默认端口,可以使用ftp.connect(url)的方式直接连接FTP服务器             ftp.login(username, password);// 登录             reply = ftp.getReplyCode();             if (!FTPReply.isPositiveCompletion(reply)) {                 ftp.disconnect();                 return success;             }             ftp.setFileType(FTPClient.BINARY_FILE_TYPE);             ftp.makeDirectory(path);             ftp.changeWorkingDirectory(path);             ftp.storeFile(filename, input);             input.close();             ftp.logout();             success = true;         } catch (IOException e) {             e.printStackTrace();         } finally {             if (ftp.isConnected()) {                 try {                     ftp.disconnect();                 } catch (IOException ioe) {                 }             }         }         return success;     }       /**      * 将本地文件上传到FTP服务器上 *      */     public static void upLoadFromProduction(String url,// FTP服务器hostname             int port,// FTP服务器端口             String username, // FTP登录账号             String password, // FTP登录密码             String path, // FTP服务器保存目录             String filename, // 上传到FTP服务器上的文件名             String orginfilename // 输入流文件名        ) {         try {             FileInputStream in = new FileInputStream(new File(orginfilename));             boolean flag = uploadFile(url, port, username, password, path,filename, in);             System.out.println(flag);         } catch (Exception e) {             e.printStackTrace();         }     }      //测试     public static void main(String[] args) {                  upLoadFromProduction("192.168.13.32", 21, "hanshibo", "han", "韩士波测试", "hanshibo.doc", "E:/temp/H2数据库使用.doc");     } } 3 直接运行。即可把指定的文件上传到ftp服务器.有需要jar包的可以到我的资源中去下载。 结束! ————————————————         原文链接:https://blog.csdn.net/atomcrazy/article/details/8943194 
  • [技术干货] java ftp文件上传下载删除
    Java FTP 文件上传、下载与删除在现代软件开发中,文件传输是一项常见的需求。FTP(File Transfer Protocol)是一个用于在网络上进行文件传输的标准协议。在Java中,我们可以利用Apache Commons Net库来实现FTP的文件上传、下载和删除操作。本文将逐步讲解如何使用Java进行FTP操作,并提供相应的代码示例。准备工作在开始之前,我们需要引入Apache Commons Net库。在你的Java项目中添加以下依赖(如果使用Maven):登录后复制<dependency><groupId>commons-net</groupId><artifactId>commons-net</artifactId><version>3.8.0</version></dependency>FTP客户端基本操作1. 连接FTP服务器首先,我们需要连接到FTP服务器。以下代码示范了如何建立连接并登陆到FTP服务器:登录后复制import org.apache.commons.net.ftp.FTP;import org.apache.commons.net.ftp.FTPClient;import java.io.IOException;public class FTPExample {private FTPClient ftpClient;public void connect(String server, int port, String user, String password) throws IOException {ftpClient = new FTPClient();ftpClient.connect(server, port);ftpClient.login(user, password);ftpClient.enterLocalPassiveMode(); // 被动模式ftpClient.setFileType(FTP.BINARY_FILE_TYPE); // 设置二进制文件类型}}2. 文件上传文件上传是FTP操作中最常见的需求之一。我们可以使用storeFile方法将文件上传到服务器。以下是上传文件的代码示例:登录后复制import java.io.FileInputStream;import java.io.IOException;public void uploadFile(String localFilePath, String remoteFilePath) throws IOException {try (FileInputStream inputStream = new FileInputStream(localFilePath)) {boolean done = ftpClient.storeFile(remoteFilePath, inputStream);if (done) {System.out.println("File uploaded successfully.");} else {System.out.println("File upload failed.");}}}3. 文件下载与文件上传类似,下载文件可以使用retrieveFile方法。以下是下载文件的代码示例:登录后复制import java.io.FileOutputStream;public void downloadFile(String remoteFilePath, String localFilePath) throws IOException {try (FileOutputStream outputStream = new FileOutputStream(localFilePath)) {boolean done = ftpClient.retrieveFile(remoteFilePath, outputStream);if (done) {System.out.println("File downloaded successfully.");} else {System.out.println("File download failed.");}}}4. 文件删除删除文件可以通过deleteFile方法来实现。下面的示例代码演示了如何删除FTP服务器上的文件:登录后复制public void deleteFile(String remoteFilePath) throws IOException {boolean done = ftpClient.deleteFile(remoteFilePath);if (done) {System.out.println("File deleted successfully.");} else {System.out.println("File deletion failed.");}}完整示例以下是一个完整的Java FTP客户端示例,组合了上述所有操作:登录后复制public class FTPClientExample {private FTPClient ftpClient;public FTPClientExample() {ftpClient = new FTPClient();}public void connect(String server, int port, String user, String password) throws IOException {ftpClient.connect(server, port);ftpClient.login(user, password);ftpClient.enterLocalPassiveMode();ftpClient.setFileType(FTP.BINARY_FILE_TYPE);}public void uploadFile(String localFilePath, String remoteFilePath) throws IOException {try (FileInputStream inputStream = new FileInputStream(localFilePath)) {boolean done = ftpClient.storeFile(remoteFilePath, inputStream);if (done) {System.out.println("File uploaded successfully.");}}}public void downloadFile(String remoteFilePath, String localFilePath) throws IOException {try (FileOutputStream outputStream = new FileOutputStream(localFilePath)) {boolean done = ftpClient.retrieveFile(remoteFilePath, outputStream);if (done) {System.out.println("File downloaded successfully.");}}}public void deleteFile(String remoteFilePath) throws IOException {boolean done = ftpClient.deleteFile(remoteFilePath);if (done) {System.out.println("File deleted successfully.");}}public void disconnect() throws IOException {if (ftpClient.isConnected()) {ftpClient.logout();ftpClient.disconnect();}}}状态图示例我们可以使用状态图来描述FTP操作的状态变化。以下是一个简单的状态图示例,描述了文件上传、下载和删除的过程:DisconnectedConnectingConnectedUploadingUploadCompleteDownloadingDownloadCompleteDeletingDeleteCompleteDisconnecting数据统计在实际应用中,我们可能需要监控不同操作的状态,下面是用饼状图示例展示的一些简单统计数据。例如,统计在某一时间段内的文件操作:FTP 文件操作统计上传下载删除结尾通过本系列的示例代码,您可以在Java中轻松实现FTP文件的上传、下载和删除操作。在构建需要文件传输功能的应用时,掌握FTP的基本操作是非常重要的。这不仅可以帮助提高应用的处理效率,也能提升用户体验。原文链接:https://blog.51cto.com/u_16175522/11615875
总条数:2272 到第
上滑加载中