• [技术干货] 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 
  • [问题求助] 接口返回为空
    GaussDB库有数据 但是调cms接口返回为空什么原因呢
  • [问题求助] 动态鉴权guid失效
    【问题来源】【必填】南网电网【问题简要】【必填】工具条签入后一段时间点击示忙按钮失败,经调用日志排查发现动态鉴权重新通过轮训接口获取新的guid期间,gateway示忙接口操作调用失败返回100-009【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】工具条签入后一段时间点击示忙按钮失败,经调用日志排查发现动态鉴权重新通过轮训接口获取新的guid期间,gateway示忙接口操作调用失败返回100-009【日志或错误截图】【可选】【附件】【可选】无
  • [问题求助] 视频坐席屏幕共享方式
    【问题来源】【必填】南网电网【问题简要】【必填】屏幕共享必须要与openeye建立websocket链接的前提下通过方法进行触发吗,能否直接通过openeye进行共享【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】屏幕共享必须要与openeye建立websocket链接的前提下通过方法进行触发吗,能否直接通过openeye进行共享【日志或错误截图】【可选】无【附件】【可选】无
  • [问题求助] 视频坐席图片推送
    【问题来源】【必填】南网电网【问题简要】【必填】通过openeye实现的视频坐席,能否实现将图片推送到来电用户的手机端进行展示【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】当用户咨询电费账单时,客服代表点击"动态图片推送"后,弹出场景列表,选择相关场景后,将电费账单信息以图片形式在用户手机端展示。【日志或错误截图】【可选】无【附件】【可选】无
  • 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 
总条数:2292 到第
上滑加载中