1. 基本概念

如今,java的技术体系已经涉及到方方面面,囊括web前端/后端/移动/大数据等方面。

  • Java SE是标准版,包含标准的JVM和标准库。
  • Java EE是企业版,它在Java SE的基础上加上了大量的API和库,以便方便开发Web应用、数据库、消息服务等,包括Spring框架、数据库开发、分布式架构等。
  • JRE:Java Runtime Environment,运行Java字节码的虚拟机
  • JDK:Java Development Kit,JDK除了包含JRE,还提供了编译器、调试器等开发工具
  • JAR 文件的全称 Java Archive File(Java 档案文件),通常 JAR 文件是一种压缩格式,和 ZIP 格式兼容。我们类文件打包成一个 JAR 包,把这个 JAR 包提供给别人使用,有点类似C语言的静态库。java生态的工具类都是以 jar 包的形式出现的,例如 Spring,SpringMVC、MyBatis、数据库驱动等等。
  • JavaSE程序可以打包成Jar包,而JavaWeb程序可以打包成war包。然后把war发布到Tomcat的webapps目录下,Tomcat会在启动时自动解压war包。
  • Maven 是一个项目管理工具,依赖管理,项目编译、测试、打包、部署、上传等。类似go module, rust cargo,是现代语言必备的工具。

2. java语法

2.1 基本数据类型

  • 整数类型:byte,short,int,long
  • 浮点数类型:float,double
  • 字符类型:char
  • 布尔类型:boolean
public class Test {
    static boolean bool;
    static byte by;
    static char ch;
    static double d;
    static float f;
    static int i;
    static long l;
    static short sh;
    static String str;
 
    public static void main(String[] args) {
        System.out.println("Bool :" + bool);
        System.out.println("Byte :" + by);
        System.out.println("Character:" + ch);
        System.out.println("Double :" + d);
        System.out.println("Float :" + f);
        System.out.println("Integer :" + i);
        System.out.println("Long :" + l);
        System.out.println("Short :" + sh);
        System.out.println("String :" + str);
    }
}

2.2 引用类型

String s = "hello"; // 字符串
final double PI = 3.14; // PI是一个常量
var sb = new StringBuilder(); // 如果想省略变量类型,可以使用var关键字
int[] ns = new int[5]; // 数组
String[] names = {"ABC", "XYZ", "zoo"}; // 字符数组
int[][] ns = {
    { 1, 2, 3, 4 },
    { 5, 6, 7, 8 },
    { 9, 10, 11, 12 }
}; // 二维数组

数组的遍历

// 标准for循环
public class Main {
    public static void main(String[] args) {
        int[] ns = { 1, 4, 9, 16, 25 };
        for (int i=0; i<ns.length; i++) {
            int n = ns[i];
            System.out.println(n);
        }
    }
}

// for each
public class Main {
    public static void main(String[] args) {
        int[] ns = { 1, 4, 9, 16, 25 };
        for (int n : ns) {
            System.out.println(n);
        }
    }
}

2.3 修饰符

修饰符用来定义类、方法或者变量,通常放在语句的最前端。

访问控制修饰符
Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。

  • default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
  • private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
  • public : 对所有类可见。使用对象:类、接口、变量、方法
  • protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。

非访问修饰符
为了实现一些其他的功能,Java 也提供了许多非访问修饰符。

  • static 修饰符,用来修饰类方法和类变量。
  • final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
  • abstract 修饰符,用来创建抽象类和抽象方法。
  • synchronized 和 volatile 修饰符,主要用于线程的编程。

2.4 流程控制

和c/c++一脉相承,不多说。

2.5 异常处理

try {
    String s = processFile(“C:\\test.txt”);
    // ok:
} catch (FileNotFoundException e) {
    // file not found:
} catch (SecurityException e) {
    // no read permission:
} catch (IOException e) {
    // io error:
} catch (Exception e) {
    // other error:
}

3. 面向对象

3.1 构造方法

public class Main {
    public static void main(String[] args) {
        Person p = new Person("Xiao Ming", 15);
        System.out.println(p.getName());
        System.out.println(p.getAge());
    }
}

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }
}

3.2 继承

class 父类 {
}

// 继承有个特点,就是子类无法访问父类的private字段或者private方法
// protected关键字可以把字段和方法的访问权限控制在继承树内部,一个protected字段和方法可以被其子类,以及子类的子类所访问 
class 子类 extends 父类 {
}

3.3 接口(英文:Interface)

// 在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

/* 文件名 : Animal.java */
interface Animal {
   public void eat();
   public void travel();
}

/* 文件名 : MammalInt.java */
public class MammalInt implements Animal{
 
   public void eat(){
      System.out.println("Mammal eats");
   }
 
   public void travel(){
      System.out.println("Mammal travels");
   } 
 
   public int noOfLegs(){
      return 0;
   }
 
   public static void main(String args[]){
      MammalInt m = new MammalInt();
      m.eat();
      m.travel();
   }
}

3.4 包(package)

// package把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用
package net.java.util;
public class Something{
   ...
}

// 为了能够使用某一个包的成员,我们需要在 Java 程序中明确导入该包。使用 "import" 语句可完成此功能。
import net.java.util;

3.5 反射

// 反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法
public class Apple {

    private int price;

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public static void main(String[] args) throws Exception{
        //正常的调用
        Apple apple = new Apple();
        apple.setPrice(5);
        System.out.println("Apple Price:" + apple.getPrice());
        //使用反射调用
        Class clz = Class.forName("com.chenshuyi.api.Apple");
        Method setPriceMethod = clz.getMethod("setPrice", int.class);
        Constructor appleConstructor = clz.getConstructor();
        Object appleObj = appleConstructor.newInstance();
        setPriceMethod.invoke(appleObj, 14);
        Method getPriceMethod = clz.getMethod("getPrice");
        System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));
    }
}

3.6 泛型

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
java的很多集合(比如List,Map等)都是基于泛型搭建的。

public class GenericMethodTest
{
   // 泛型方法 printArray                         
   public static < E > void printArray( E[] inputArray )
   {
      // 输出数组元素            
         for ( E element : inputArray ){        
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }
 
    public static void main( String args[] )
    {
        // 创建不同类型数组: Integer, Double 和 Character
        Integer[] intArray = { 1, 2, 3, 4, 5 };
        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
        Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
 
        System.out.println( "整型数组元素为:" );
        printArray( intArray  ); // 传递一个整型数组
 
        System.out.println( "\n双精度型数组元素为:" );
        printArray( doubleArray ); // 传递一个双精度型数组
 
        System.out.println( "\n字符型数组元素为:" );
        printArray( charArray ); // 传递一个字符型数组
    } 
}
// 和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。

public class Box<T> {
   
  private T t;
 
  public void add(T t) {
    this.t = t;
  }
 
  public T get() {
    return t;
  }
 
  public static void main(String[] args) {
    Box<Integer> integerBox = new Box<Integer>();
    Box<String> stringBox = new Box<String>();
 
    integerBox.add(new Integer(10));
    stringBox.add(new String("菜鸟教程"));
 
    System.out.printf("整型值为 :%d\n\n", integerBox.get());
    System.out.printf("字符串为 :%s\n", stringBox.get());
  }
}

4. java web开发历史发展

4.1 servlet

编写HTTP服务器其实是非常简单的,只需要先编写基于多线程的TCP服务,然后在一个TCP连接中读取HTTP请求,发送HTTP响应即可。
但是,要编写一个完善的HTTP服务器,以HTTP/1.1为例,需要考虑的包括:

  • 识别正确和错误的HTTP请求;
  • 识别正确和错误的HTTP头;
  • 复用TCP连接;
  • 复用线程;
  • IO异常处理;

这些基础工作需要耗费大量的时间,并且经过长期测试才能稳定运行。如果我们只需要输出一个简单的HTML页面,就不得不编写上千行底层代码,那就根本无法做到高效而可靠地开发。

因此,在JavaEE平台上,处理TCP连接,解析HTTP协议这些底层工作统统扔给现成的Web服务器去做,我们只需要把自己的应用程序跑在Web服务器上。为了实现这一目的,JavaEE提供了Servlet API,我们使用Servlet API编写自己的Servlet来处理HTTP请求,Web服务器实现Servlet API接口,实现底层功能。

下面是一个最简单的Servlet:

// WebServlet注解表示这是一个Servlet,并映射到地址/:
@WebServlet(urlPatterns = "/")
public class HelloServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        // 设置响应类型:
        resp.setContentType("text/html");
        // 获取输出流:
        PrintWriter pw = resp.getWriter();
        // 写入响应:
        pw.write("<h1>Hello, world!</h1>");
        // 最后不要忘记flush强制输出:
        pw.flush();
    }
}

一个Servlet总是继承自HttpServlet,然后覆写doGet()或doPost()方法。注意到doGet()方法传入了HttpServletRequest和HttpServletResponse两个对象,分别代表HTTP请求和响应。我们使用Servlet API时,并不直接与底层TCP交互,也不需要解析HTTP协议,因为HttpServletRequest和HttpServletResponse就已经封装好了请求和响应。以发送响应为例,我们只需要设置正确的响应类型,然后获取PrintWriter,写入响应即可。

程序写好,运行Maven命令mvn clean package,在target目录下得到一个hello.war文件,这个文件就是我们编译打包后的Web应用程序。

普通的Java程序是通过启动JVM,然后执行main()方法开始运行。但是Web应用程序有所不同,我们无法直接运行war文件,必须先启动Web服务器,再由Web服务器加载我们编写的HelloServlet,这样就可以让HelloServlet处理浏览器发送的请求。常用的web服务器有:Tomcat,Jetty等。

一个完整的Web应用程序的开发流程如下:

  1. 编写Servlet;
  2. 打包为war文件;
  3. 复制到Tomcat的webapps目录下;
  4. 启动Tomcat。

实际上,类似Tomcat这样的服务器也是Java编写的,启动Tomcat服务器实际上是启动Java虚拟机,执行Tomcat的main()方法,然后由Tomcat负责加载我们的.war文件,并创建一个HelloServlet实例,最后以多线程的模式来处理HTTP请求。如果Tomcat服务器收到的请求路径是/(假定部署文件为ROOT.war),就转发到HelloServlet并传入HttpServletRequest和HttpServletResponse两个对象。

因为我们编写的Servlet并不是直接运行,而是由Web服务器加载后创建实例运行,所以,类似Tomcat这样的Web服务器也称为Servlet容器。

在Servlet容器中运行的Servlet具有如下特点:

  • 无法在代码中直接通过new创建Servlet实例,必须由Servlet容器自动创建Servlet实例;
  • Servlet容器只会给每个Servlet类创建唯一实例;
  • Servlet容器会使用多线程执行doGet()或doPost()方法。

一个Servlet类在服务器中只有一个实例,但对于每个HTTP请求,Web服务器会使用多线程执行请求。因此,一个Servlet的doGet()、doPost()等处理请求的方法是多线程并发执行的。如果Servlet中定义了字段,要注意多线程并发访问的问题:

public class HelloServlet extends HttpServlet {
    private Map<String, String> map = new ConcurrentHashMap<>();

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 注意读写map字段是多线程并发的:
        this.map.put(key, value);
    }
}

4.2 JSP开发

Servlet就是一个能处理HTTP请求,发送HTTP响应的小程序,而发送响应无非就是获取PrintWriter,然后输出HTML。

只不过,用PrintWriter输出HTML比较痛苦,因为不但要正确编写HTML,还需要插入各种变量。如果想在Servlet中输出一个类似新浪首页的HTML,写对HTML基本上不太可能。

我们可以使用JSP:

  • JSP是一种在HTML中嵌入动态输出的文件,它和Servlet正好相反,Servlet是在Java代码中嵌入输出HTML;
  • JSP可以引入并使用JSP Tag,但由于其语法复杂,不推荐使用;
  • JSP本身目前已经很少使用,我们只需要了解其基本用法即可。

4.3 MVC开发

  • Servlet适合编写Java代码,实现各种复杂的业务逻辑,但不适合输出复杂的HTML;
  • JSP适合编写HTML,并在其中插入动态内容,但不适合编写复杂的Java代码。

能否将两者结合起来,发挥各自的优点,避免各自的缺点?
其中一个答案是,可以使用MVC模式:

  • 使用MVC模式的好处是,Controller专注于业务处理,它的处理结果就是Model。
  • Model可以是一个JavaBean,也可以是一个包含多个对象的Map
  • Controller只负责把Model传递给View,View只负责把Model给“渲染”出来
  • 这样,三者职责明确,且开发更简单,因为开发Controller时无需关注页面,开发View时无需关心如何创建Model。

比较有名的mvc开发框架就是Spring MVC, 但是mvc模式在现代的web开发中也很少使用了,我们了解一下即可。

4.4 Rest接口

现在java web开发都是前后端分离,前端只需要关注页面呈现,后端只需要实现rest接口。

使用Spring框架可以很方便地开发rest接口,它提供了一个@RestController注解,每个方法自动变成API接口方法

@RestController
@RequestMapping("/api")
public class ApiController {
    @Autowired
    UserService userService;

    @GetMapping("/users")
    public List<User> users() {
        return userService.getUsers();
    }

    @GetMapping("/users/{id}")
    public User user(@PathVariable("id") long id) {
        return userService.getUserById(id);
    }

    @PostMapping("/signin")
    public Map<String, Object> signin(@RequestBody SignInRequest signinRequest) {
        try {
            User user = userService.signin(signinRequest.email, signinRequest.password);
            return Map.of("user", user);
        } catch (Exception e) {
            return Map.of("error", "SIGNIN_FAILED", "message", e.getMessage());
        }
    }

    public static class SignInRequest {
        public String email;
        public String password;
    }
}

5. Spring 框架系列

Spring框架系列,是java服务端开发,当之无愧的霸主了,使用Spring框架开发,可以让开发者专注于业务开发,极大提高了开发效率。

而且,随着Spring越来越受欢迎,在Spring Framework基础上,又诞生了Spring Boot、Spring Cloud、Spring Data、Spring Security等一系列基于Spring Framework的项目。

5.1 SpingFramework

Spring框架最核心的概念就是容器。

什么是容器?容器是一种为某种特定组件的运行提供必要支持的一个软件环境。例如,Tomcat就是一个Servlet容器,它可以为Servlet的运行提供运行环境。

通常来说,使用容器运行组件,除了提供一个组件运行环境之外,容器还提供了许多底层服务。例如,Servlet容器底层实现了TCP连接,解析HTTP协议等非常复杂的服务,如果没有容器来提供这些服务,我们就无法编写像Servlet这样代码简单,功能强大的组件。早期的JavaEE服务器提供的EJB容器最重要的功能就是通过声明式事务服务,使得EJB组件的开发人员不必自己编写冗长的事务处理代码,所以极大地简化了事务处理。

Spring的核心就是提供了一个IoC容器,它可以管理所有轻量级的JavaBean组件,提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等。

什么是IoC?IoC全称Inversion of Control,直译为控制反转。

如果一个系统有大量的组件,其生命周期和相互之间的依赖关系如果由组件自身来维护,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合,继而给测试和维护带来了极大的困难。

因此,核心问题是:

  • 谁负责创建组件?
  • 谁负责根据依赖关系组装组件?
  • 销毁时,如何按依赖顺序正确销毁?

解决这一问题的核心方案就是IoC。

  • 传统的应用程序中,控制权在程序本身,程序的控制流程完全由开发者控制
  • 在IoC模式下,控制权发生了反转,即从应用程序转移到了IoC容器,所有组件不再由应用程序自己创建和配置,而是由IoC容器负责,这样,应用程序只需要直接使用已经创建好并且配置好的组件。

IoC解决了一个最主要的问题:将组件的创建+配置与组件的使用相分离,并且,由IoC容器负责管理组件的生命周期。因为IoC容器要负责实例化所有的组件,因此,有必要告诉容器如何创建组件,以及各组件的依赖关系。

在Spring的IoC容器中,我们把所有组件统称为JavaBean,即配置一个组件就是配置一个Bean。
使用Spring的IoC容器,实际上就是通过类似XML这样的配置文件,把我们自己的Bean的依赖关系描述出来,然后让容器来创建并装配Bean。Spring容器会自动为每个Bean创建一个单例(Singleton),即容器初始化时创建Bean,容器关闭前销毁Bean。一旦容器初始化完毕,我们就直接从容器中获取Bean使用它们,例如:

<beans>
    <bean id="dataSource" class="HikariDataSource" />
    <bean id="bookService" class="BookService">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <bean id="userService" class="UserService">
        <property name="dataSource" ref="dataSource" />
    </bean>
</beans>

使用XML配置的优点是所有的Bean都能一目了然地列出来,并通过配置注入能直观地看到每个Bean的依赖。它的缺点是写起来非常繁琐,每增加一个组件,就必须把新的Bean配置到XML中。

我们也可以使用Annotation配置,可以完全不需要XML,让Spring自动扫描Bean并组装它们,比如:

@Component
public class MailService {
    ...
}

@Component
public class UserService {
    @Autowired
    MailService mailService;

    ...
}
  • 这个@Component注解就相当于定义了一个Bean
  • 使用@Autowired就相当于把指定类型的Bean注入到指定的字段中。和XML配置相比,@Autowired大幅简化了注入,因为它不但可以写在set()方法上,还可以直接写在字段上,甚至可以写在构造方法中。

使用Annotation配合自动扫描能大幅简化Spring的配置,我们只需要保证:

  • 每个Bean被标注为@Component并正确使用@Autowired注入;
  • 配置类被标注为@Configuration和@ComponentScan;
  • 所有Bean均在指定包以及子包内。

5.2 Spring Boot

Spring Boot是一个基于Spring的套件,它帮我们预组装了Spring的一系列组件,以便以尽可能少的代码和配置来开发基于Spring的Java应用程序。

简而言之,我们基于Spring Boot的预置结构继续开发,更加省时省力。

比如:

@SpringBootApplication
public class Application {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

启动Spring Boot应用程序只需要一行代码加上一个注解@SpringBootApplication,该注解实际上又包含了:
@SpringBootConfiguration
@Configuration
@EnableAutoConfiguration
@AutoConfigurationPackage
@ComponentScan
这样一个注解就相当于启动了自动配置和自动扫描。

事实上,Spring Boot大量使用XxxAutoConfiguration来使得许多组件被自动化配置并创建,而这些创建过程又大量使用了Spring的Conditional功能。从而让我们基于很少的配置和代码快速搭建出一个完整的应用程序。

SpringBoot核心的关键特性:

  • 起步依赖(Starter Dependency)
    创建项目时,会默认添加基础依赖,简化我们自己查找依赖的过程。
  • 自动配置(Auto Configuration)
    创建项目时,springboot工程添加的默认依赖中提供了很多默认的配置,简化了我们对资源的配置过程。
  • 健康检查(Actator)
    监控-springboot工程运行时,我们可以打开actuator特性,基于此特性监控spring中的bean,连接池,jvm内存等
  • 嵌入式服务(Tomcat)
    springboot工程支持内嵌的web服务,可以将tomcat这样的服务直接嵌套到web依赖中,简化部署过程。

总之,Spring Boot 框架就是要基于快速构建理念,基于约定大于配置方式,实现技术的开箱即用,以提高开发效率。

5.3 Spring Cloud

Spring Cloud 就是微服务系统架构的一站式解决方案,在平时我们构建微服务的过程中需要做如 服务发现注册 、配置中心 、消息总线 、负载均衡 、断路器 、数据监控 等操作,而 Spring Cloud 为我们提供了一套简易的编程模型,使我们能在 Spring Boot 的基础上轻松地实现微服务项目的构建。

以下是SpringCloud的一些核心组件介绍:

5.3.1 核心组件Eureke(服务发现)

是微服务架构中的注册中心,由Eureka Client和Eureka Server两部分组成,前者负责将各个服务的信息注册到Eureka Server中。后者为注册中心,里面有一个注册表,保存了各个服务所在的机器和端口号;

各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从Eureka Server拉去注册表,从而知道其他服务在哪里;

5.3.2 核心组件Feign(动态代理)

使用了动态代理的机制,用注解定义一个FeignClient接口,向指定的服务建立连接、发起请求、获取响应,解析响应等等。

5.3.3 核心组件Ribbon(客户端负载均衡)

Ribbon的作用是负载均衡,当我们要请求的服务部署在多台机器上时,Feign就不知道该请求哪台机器。此时具有负载均衡作用的Ribbon就会帮你在每次请求时选择一台机器,均匀的把请求分发到各台机器上。

此外Ribbon是和Feign以及Eureka紧密协作的,具体如下:

  • 首先Ribbon会从Eureka Client里获取到对应的服务注册表,知道所有的服务都部署在了哪些机器上,在监听哪些端口号。
  • 然后Ribbon就使用默认的Round Robin轮询算法,从中选择一台机器;
  • 最后Feign就会针对这台机器,构造并发起请求。

5.3.4 核心组件Hystrix(断路器)

Hystrix是隔离、熔断以及降级的一个框架。
发起请求是通过Hystrix的线程池来走的,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题;

5.3.5 核心组件Zuul(服务网关)

如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务;

java serlet 服务端开发 java服务器端开发_spring