spring/spring boot

92 阅读14分钟

1.Spring

1.1 简介

Spring 是于 2003 年兴起的一个轻量级的 Java 开发框架,它是为了解决企业应用开发的复杂性而创建的。Spring 的核心是控制反转(IoC)和面向切面编程(AOP)。 Spring 的主要作用就是为代码“解耦”,降低代码间的耦合度。

1.2 IOC

控制反转(IoC,Inversion of Control)是一个概念,指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值,依赖的管理。

依赖:classA 类中含有 classB 的实例,在 classA 中调用 classB 的方法完 成功能,即 classA 对 classB 有依赖

Spring 容器负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。Spring 容器管理着容器中 Bean 之间的依赖关系, Spring 使用“依赖注入”的方式来管理 Bean 之间的依赖关系。

1.2.1 基本配置

maven基本依赖

<dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-context</artifactId>

    <version>5.2.5.RELEASE</version>

</dependency>

创建 Spring 配置文件

在 src/main/resources/目录现创建一个 xml 文件,文件名可以随意,但 Spring 建议的名称为 applicationContext.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">

    <import resource="appli.xml"/>

    <bean id = "some1" class="com.impl.SomeImpl"/>

    <bean id = "some2" class="com.impl.SomeImpl">



    </bean>

</beans>

:用于定义一个实例对象。一个实例对应一个 bean 元素。

id:该属性是 Bean 实例的唯一标识,程序通过 id 属性访问 Bean,Bean与 Bean 间的依赖关系也是通过 id 属性关联的。

class:指定该 Bean 所属的类,注意这里只能是类,不能是接口。

定义测试类



public class Test {



    public static void main(String[] args) throws Exception {

    //配置文件的位置和名称

        String be = "beans.xml";

        //创建容器对象

        ApplicationContext atx = new ClassPathXmlApplicationContext(be);

        //通过ID获取对象

        SomeImpl ww = (SomeImpl) atx.getBean("some1");

        //执行对象的方法

        ww.doSome();

        U1 uu = (U1) atx.getBean("U11");

        uu.doe();



    }

}

ApplicationContext 容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。以后代码中若要使用到这些对象,只需从内存中直接获取即可。执行效率较高。但占用内存。

1.2.2 依赖注入

bean 实例在调用无参构造器创建对象后,就要对 bean 对象的属性进行初

始化。初始化是由容器自动完成的,称为注入。

根据注入方式的不同,常用的有两类:set 注入、构造注入。

set 注入(掌握)

set 注入也叫设值注入是指,通过 setter 方法传入被调用者的实例。这种注入方式简单、直观,因而在 Spring 的依赖注入中大量使用。

//lombook生成set方法

@Data

public class SomeImpl implements Some {

    String name;

    int a = 0;

    SomeImpl(){

        System.out.println("我是SomeImpl的构造方法");

    }

    public void doSome() {

        System.out.println(name+"的a为"+a);

    }

}
通过标签给属性赋值即可

<bean id = "some1" class="com.impl.SomeImpl">

    <property name="name" value="som1"/>

    <property name="a" value="1"/>

</bean>

<bean id = "some2" class="com.impl.SomeImpl">

    <property name="name" value="som2"/>

    <property name="a" value="2"/>

</bean>

引用类型

当指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。ref 的值必须为某 bean 的 id 值。

构造注入(理解)

构造注入是指,在构造调用者实例的同时,完成被调用者的实例化。即使用构造器设置依赖关系。

标签中用于指定参数的属性有:

name:指定参数名称。

index:指明该参数对应着构造器的第几个参数,从 0 开始。不过,该属性不要也行,但要注意,若参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。

为应用指定多个 Spring 配置文件

<import resource="appli.xml"/>

1.2.3基于注解的 DI

对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例。需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。

<context:component-scan base-package="com"/>

base-package 的值表是基本包,容器启动会扫描包及其子包中的注解,也会扫描到子包下级的子包。

定义 Bean 的注解@Component

//相当于以前的bean标签,U11为id

@Component("U11")

public class U1 {

    public void doe(){

        System.out.println("U1");

    }

}

不指定 value 属性,bean 的 id 是类名的首字母小写。

简单类型属性注入@Value

byType 自动注入@Autowired(掌握)

需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配 Bean 的方式。

byName 自动注入@Autowired 与@Qualifier

需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier的 value 属性用于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到set 方法上。

@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。

1.3 AOP 面向切面编程

AOP 为 Aspect Oriented Programming 面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

1.3.1 静态代理与动态代理

静态代理:由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

动态代理:动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。

1.3.2不使用 AOP 的开发方式

先定义好接口与一个实现类,该实现类中除了要实现接口中的方法外,还要再写两个非业务方法。非业务方法也称为交叉业务逻辑:

 doTransaction():用于事务处理

 doLog():用于日志处理

然后,再使接口方法调用它们。接口方法也称为主业务逻辑。

接口:

可以有另一种解决方案:将这些交叉业务逻辑代码放到专门的工具类或处理类中,主业务逻辑调用。

1.3.3 AOP开发

面向切面编程,将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。

交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。

若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使主业务逻辑变的混杂不清。

1.减少重复;

2.专注业务;

注意:面向切面编程只是面向对象编程的一种补充。

AOP 编程术语

(1) 切面(Aspect)

切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面是通知(Advice)。实际就是对主业务逻辑的一种增强。

(2) 连接点(JoinPoint)

连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。

(3) 切入点(Pointcut)

切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。

(4) 目标对象(Target)

目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。

(5) 通知(Advice)

通知表示切面的执行时间,Advice 也叫增强。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。切入点定义切入的位置,通知定义切入的时间。

1.3.4 AspectJ 对 AOP 的实现

对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,而且还支持注解式开发。所以,Spring 又将AspectJ 的对于 AOP 的实现也引入到了自己的框架中。

AspectJ 中常用的通知有五种类型:

(1)前置通知

(2)后置通知

(3)环绕通知

(4)异常通知

(5)最终通知

AspectJ 的切入点表达式

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-

pattern(param-pattern)

throws-pattern?)

modifiers-pattern] 访问权限类型

ret-type-pattern 返回值类型

declaring-type-pattern 包名类名

name-pattern(param-pattern) 方法名(参数类型和参数个数)

throws-pattern 抛出异常类型

?表示可选的部分

execution(访问权限 方法返回值 方法声明(参数) 异常类型)

execution(public * *(..))

指定切入点为:任意公共方法。

execution(* set*(..))

指定切入点为:任何一个以“set”开始的方法。

execution(* com.xyz.service.*.*(..))

指定切入点为:定义在 service 包里的任意类的任意方法。

execution(* com.xyz.service..*.*(..))

指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现

在类名中时,后面必须跟“*”,表示包、子包下的所有类。

execution(* *..service.*.*(..))

指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点

execution(* *.service.*.*(..))

指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点

execution(* *.ISomeService.*(..))

指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点

execution(* *..ISomeService.*(..))

指定所有包下的 ISomeSerivce 接口中所有方法为切入点

execution(* com.xyz.service.IAccountService.*(..))

指定切入点为:IAccountService 接口中的任意方法。

execution(* com.xyz.service.IAccountService+.*(..))

指定切入点为:IAccountService 若为接口,则为接口中的任意方法及其所有

实现类中的任意方法;若为类,则为该类及其子类中的任意方法。

execution(* joke(String,int)))

指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是

String,第二个参数是 int。如果方法中的参数类型是 java.lang 包下的类,可

以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)。

execution(* joke(String,*)))

指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数

可以是任意类型,如 joke(String s1,String s2)和 joke(String s1,double d2)

都是,但 joke(String s1,double d2,String s3)不是。

execution(* joke(String,..)))

指定切入点为:所有的 joke()方法,该方法第一个参数为 String,后面可以有

任意个参数且参数类型不限,如 joke(String s1)、joke(String s1,String s2)和

joke(String s1,double d2,String s3)都是。

execution(* joke(Object))

指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类

型。joke(Object ob)是,但,joke(String s)与 joke(User u)均不是。

execution(* joke(Object+)))

指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型

或该类的子类。不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是。

前置通知(1) 实现步骤

Step2:定义切面类

类中定义了若干普通方法,将作为不同的通知方法,用来增强功能。

Step3:声明目标对象切面类对象

Step4:注册 AspectJ 的自动代理

Step5:测试类中使用目标对象的 id

JoinPoint 参数

在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个JoinPoint 类型参数。通过该参数,可获取切入点表达式、方法签名、目标对象等。不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数(要是第一个参数)。

后置通知

//returning 要和方法的参数同名,即为该方法的返回值

@AfterReturning(value = "execution(public String com.iml.ServiceImpl.doOther(String))",

                returning = "obj")

public void myAfter(Object obj){

    System.out.println("切面的功能在目标功能之后执行,返回的对象为" + obj);



}





String be = "app.xml";

ApplicationContext atx = new ClassPathXmlApplicationContext(be);

Service mySer = (Service) atx.getBean("ser1");

mySer.doSome("d");

mySer.doOther("doO");

环绕通知@Around

ProceedingJoinPoint 参数

在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。

@Override

public String doFirst(String str) {

    System.out.println("执行doFirst方法");

    return str;

}



@Around("execution(public String com.iml.ServiceImpl.doFirst(String))")

public Object myAround(ProceedingJoinPoint pdj) throws Throwable {

    Object obj;

    System.out.println("方法之前执行");

    obj = pdj.proceed();

    obj = obj + "被环绕通知处理了";

    System.out.println("方法之后执行");

    return obj;

}





String str = mySer.doFirst("first");

System.out.println(str);

@Pointcut 定义切入点

当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。

其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。

2.Spring Boot

Spring Boot基本上是Spring框架的扩展,它消除了设置Spring应用程序所需的XML配置,为更快,更高效的开发生态系统铺平了道路。

与Spring相比,思想上基本没有区别,类似于全部使用注解注入。

2.1 基本配置

maven依赖

spring依赖

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter</artifactId>

    <version>2.6.7</version>

</dependency>



aop依赖

<dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-aspects</artifactId>

    <version>5.3.1</version>

</dependency>

SpringBoot项目都有一个固定的类作为入口

//这个注解是固定的

@SpringBootApplication

public class FoolRpcClientFactory {

   public static void main(String... args){

   //这里这个run和spring获得的容器类似,直接getBean获取实例

      ConfigurableApplicationContext run = SpringApplication.run(FoolRpcClientFactory.class,args);

      Service ser = run.getBean(Service.class);

      ser.doSome("ww");

   }

}

2.1.1注入

springBoot采用和spring相似的注解注入,不要再配置xml文件

配置类注入:

@Configuration()//表示这是一个配置类。会把里面@Bean的方法放到容器里

public class ConfigClass {

    @Bean

    @Scope("prototype")//加这个表示每次容器获取都是新实例,否则为同一个实例

    public Do myDo(){

        return new Do();

    }

}

注解直接放到容器:

@Component//将这个类放到容器

public class ServiceImpl implements Service {

    @Autowired//按类型自动从容器里找相应实例赋值

    public Do d1;

    @Autowired

    public Do d2;

    @Override

    public void doSome(String str) {

        System.out.println("doSome方法");

        d1.dow();

        System.out.println("d1.hashcode:"+ d1.hashCode() +

                            "   d2.hashcode" + d2.hashCode());

    }



}

2.1.2aop

aop区别不大,不需要在xml里注册声明,直接注解即可



@Component

@Aspect

public class MyAspect {

    @Before(value = "execution(public void com.lll.iml.ServiceImpl.doSome(String))")

    public void myBefore(){

        System.out.println("前置");

    }

}

2.2.yml

YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)。

非常适合用来做以数据为中心的配置文件

基本语法

  • key: value;kv之间有空格
  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • '#'表示注释
  • 字符串无需加引号,如果要加,单引号’’、双引号""表示字符串内容会被 转义、不转义

'\n'字符输出,“\n”换行输出

数据类型

  • 字面量:单个的、不可再分的值。date、boolean、string、number、null
k: v

对象:键值对的集合。map、hash、set、object

#行内写法:  



k: {k1:v1,k2:v2,k3:v3}



#或



k: 

  k1: v1

  k2: v2

  k3: v3

数组:一组按次序排列的值。array、list、queue

#行内写法:  



k: [v1,v2,v3]



#或者



k:

 - v1

 - v2

 - v3

实例

@Data

public class Person {

    private String userName;

    private Boolean boss;

    private Date birth;

    private Integer age;

    private Pet pet;

    private String[] interests;

    private List<String> animal;

    private Map<String, Object> score;

    private Set<Double> salarys;

    private Map<String, List<Pet>> allPets;

}



@Data

public class Pet {

    private String name;

    private Double weight;

}
person:

  userName: zhangsan

  boss: false

  birth: 2019/12/12 20:12:33

  age: 18

  pet: 

    name: tomcat

    weight: 23.4

  interests: [篮球,游泳]

  animal: 

    - jerry

    - mario

  score:

    english: 

      first: 30

      second: 40

      third: 50

    math: [131,140,148]

    chinese: {first: 128,second: 136}

  salarys: [3999,4999.98,5999.99]

  allPets:

    sick:

      - {name: tom}

      - {name: jerry,weight: 47}

    health: [{name: mario,weight: 47}]