Spring 马士兵


1.Spring能做什么?

Spring是一个轻量级的控制反转(IoC/di)和面向切面编程(AOP) 的对象容器框架+事务管理

Spring官网:http://spring.io/ 

Spring 是一个于 2003 年兴起的一个轻量级的 Java 开源开发框架

如今Spring已是Java项目的标配,它极大的简化了开发过程,降低了开发难度。

- 方便解耦,简化开发;

- AOP 编程的支持;

- 声明式事务的支持;

- 方便程序的测试;
- 方便集成各种优秀框架。

**IoC**
控制反转
Inversion of Control

**AOP** 
面向切面编程
Aspect Oriented Programming 

## Spring5新特性
- 支持JDK8
  - Spring 接口中的默认方法
  - 基于 Java8 反射增强的内部代码改进
  - 在框架代码中使用函数式编程 - lambda表达式 和 stream流

- 响应式编程支持Spring Web Reactive异步的、非阻塞的、事件驱动的服务
- 支持J2EE7
  - Servlet 3.1
  - JMS 2.0
  - JPA 2.1
  - JAX-RS 2.0

- Bean Validation 1.1
- Hibernate 5

- Jackson 2.6

- EhCache 2.10

- JUnit 5

- Tiles 3

- Kotlin

## IOC
**IoC是一个概念,是一种思想,其实现方式多种多样。当前比较流行的实现方式之一是DI。**

从IOC容器中获取bean实例
```java
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = (User)ctx.getBean("user");
        user.setName("uu");
        user.setAge(18);
        System.out.println(ToStringBuilder.reflectionToString(user));;
```
### Bean的定义与注册

Spring的配置文件是用于指导Spring工厂进行Bean的生产、依赖关系注入及Bean实例分发的“图纸”,它是一个或多个标准的XML文档

<bean id=**"food"** class=**"com.msb.Food"**></bean>

一个bean只能有一个id,但是可以有多个name作为别名

### 作用域
 

spring为bean提供了6种作用域,其中4种只有在web-aware的ApplicationContext种才有用。用户也可以创建自定义的作用域。

singleton 、prototype 、websocket(ws)、request、session、application
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210205155034475.png)

***\*singleton scope 单例作用域\****
单例
每一个类,在一个容器内只能产生一个实例

 ***\*Request scope\****

该作用域的bean,在每个HTTP request都会新建一个实例,当一个request结束后,该实例也会被丢弃。

 ***\*Session scope\****

某一个用户在一段时间内,会使用同一个session,session有超时时间,过了超时时间则session失效。不同用户使用不同的session。

 ***\*Application\**** ***\*scope\****

该作用域的bean,每一个application会创建一个

***\*prototype scope 原型作用域\****
多例
该bean每次被注入,或者使用getBean()方法获取时,都返回一个新的实例。
SpringBoot配置多例

```java
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
```

 ***\*MVC下Spring的单例\****

想在一个singleton内多次调用短存活时间的bean(propotype、request、session等),希望调用的是不同的实例,那么就需要使用AOP proxy技术

 ***\*线程安全问题\****
业务对象并没有做线程的并发限制,因此不会出现各个线程之间的等待问题,或是死锁问题

MVC中的实体bean不是单例的

单例类的成员变量
@Component @Service  @RestController 注释类中不能有状态数据(类的成员变量),只能有方法。多个请求会共用这些单例对象,线程不安全的。

在并发访问的时候这些成员变量将会是并发线程中的共享对象,也是影响线程安全的重要因素

**前端到后端的一次请求,后端会创建一个线程来处理,及一个请求会用一个线程处理**
```java
@Component
public class Test {

    public int i = 0;

    public int test(int t) throws InterruptedException {

        if (t == 1) {
            i = 1;
            TimeUnit.SECONDS.sleep(5);
            i =100;
        }

        if(t==2) {
            TimeUnit.SECONDS.sleep(10);
            i = i + 1;
        }
        System.out.println(i);
        return i;
    }
}

```

```java
    @Autowired
    private Test test;


    @GetMapping("/test/{id}")
    public int test(@PathVariable(name = "id") int id) throws InterruptedException {

        System.out.println("id1----: " + id);
        int i =   test.test(id);
        System.out.println("test:"+test);
        System.out.println("id2----: " + id);
        return i;
    }

```
localhost:8380/ekafka/v1/test/1
返回:100
打印
100
test:com.chinamobile.cmss.ekafka.service.impl.Test@40e41f88
id2----: 1

localhost:8380/ekafka/v1/test/2
返回:101  
打印
101
test:com.chinamobile.cmss.ekafka.service.impl.Test@40e41f88
id2----: 2
再来一次localhost:8380/ekafka/v1/test/2
返回:102
打印
102
test:com.chinamobile.cmss.ekafka.service.impl.Test@40e41f88
id2----: 2

再来一次localhost:8380/ekafka/v1/test/2
返回:103
打印
id1----: 2
103
test:com.chinamobile.cmss.ekafka.service.impl.Test@40e41f88
id2----: 2

注意所有的test对象都是同一个Test@40e41f88

改成多例模式
不管执行几次localhost:8380/ekafka/v1/test/1
返回:100
打印
100
test:com.chinamobile.cmss.ekafka.service.impl.Test@9002ded //每次的test都不一样
id2----: 1

不管执行几次localhost:8380/ekafka/v1/test/2
返回:1
打印
1
test:com.chinamobile.cmss.ekafka.service.impl.Test@2500323c //每次的test都不一样
id2----: 2

**不带属性变量使用默认单列是线程安全的:**

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210207095014859.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lhbmdzaGVuZ3dlaTIzMDYxMg==,size_16,color_FFFFFF,t_70)


接口:

```java
public interface UserDao {

    public User getUserByName(String name);
}

```
两个实现类:

```java
@Repository("daoMysql")
public class UserDaoMysqlImpl implements UserDao {

    @Autowired
    User user;
    
    public User getUserByName(String name) {
        System.out.println("用户查找中。。。");
        return user;
    }

}

```

```java
@Repository("daoSS")
public class UserDaoSqlServerImpl implements UserDao {

    public User getUserByName(String name) {
        // TODO Auto-generated method stub
        System.out.println("UserDaoSqlServerImpl");
        return null;
    }

}

```

调用:
    @Autowired
    @Qualifier("daoMysql")  //使用daoMysql的实现类
```java
@Service
public class MainService {

    @Autowired
    @Qualifier("daoMysql")
    UserDao dao;
    
    public User login(String loginName, String password) {

        System.out.println("loginName:" + loginName);
        System.out.println("Service 接到请求 ,开始处理");
        User user = dao.getUserByName(loginName);
        
        System.out.println(ToStringBuilder.reflectionToString(user));
        
        return user;
    }

}
```

**带属性变量的类必须使用多列来保证线程安全:**


```java
@Component  //交给spring IOC管理
@Scope("prototype") //使用多例
public class User {

    @Value("zhangfg")
    private String loginName;
    @Value("123123")
    private String password;
    @Autowired
    private Pet pet;
    
}

```

```java
@Component
@Scope("prototype")
public class Pet {

    @Value("可乐")
    private String name;
    
}

```

### 引用类型的成员

其中引用类型的成员变量即我们在controller中注入的service,在service中注入的dao,这里将其定义为成员变量主要是为了实例化进而调用里面的业务方法,**在这些类中一般不会有全局变量,因此只要我们的业务方法不含有独立的全局变量即使是被多线程共享,也是线程安全的。**
Controller service dao 层中的业务类是多线程共享的,但是每个线程在处理数据的时候具体处理的数据是在每个线程中各自有一份。

 ***\*controller层\****

l final类型 线程安全

l 成员变量 有状态数据有线程安全问题


# 2.Spring Framework


## 工程模式
工厂生产不同品牌的汽车

工厂类

```java
public class CarFactory {

    public CarFactory() {
        super();
        System.out.println("CarFactory");
    }
    
    
    public static Car getCar(String name) throws Exception{
        if (name.equals("audi")) {
            
            return new Audi();
        }else {
            throw new Exception("暂时没法生产这辆车");
        }
    }
}

```

```java
public interface Car {

    public String getName();
    public String getPrice();
}

```

奥迪车

```java
public class Audi implements Car {

    public String getName() {
        // TODO Auto-generated method stub
        return "我是奥迪A7";
    }

    public String getPrice() {
        // TODO Auto-generated method stub
        return "700000";
    }

}

```

宝马车

```java
public class Bmw implements Car {

    public String getName() {
        // TODO Auto-generated method stub
        return "BMW 7";
    }

    public String getPrice() {
        // TODO Auto-generated method stub
        return "700000w";
    }

}

```

通过工厂获取汽车

```xml
 <!--
 动态工厂 每次都是new出来的
        <bean id="carFactory" class="com.mashibing.spring.CarFactory">
        
            <property name="name"  value="audi"></property> 
        </bean>
        
        <bean id="car" factory-bean="carFactory" factory-method="getCar">
        需要set方法
            
        </bean> -->
        
        <bean id="car" class="com.mashibing.spring.CarFactory" factory-method="getCar" >
            <constructor-arg value="audi"></constructor-arg>
        </bean>
        
</beans>
```

```java
public class TestGetBean {

    /**
     * Spring 对对象的生产来说 一共就两种
     * 
     * 1. 单例 singleton -> ws request session application -> 生命周期绑定 2. new出来的
     * prototype
     * 
     * @param args
     */
    
    public static void main(String[] args) {
//
//        try {
//            Car car = CarFactory.getCar("audi");  //代码中写死获取那种汽车,强耦合
//            System.out.println(car.getName());
//        } catch (Exception e) {
//            // TODO Auto-generated catch block
//            e.printStackTrace();
//        }
//        

        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); //获取xml配置文件中的内容
        Car car = ctx.getBean("car",Car.class); // 通过xml动态配置的方式获取汽车,xml中配置的获取什么品牌的汽车
        System.out.println(car.getName());

    }
}

```
**动态工厂**

```java
public class CarFactory {

    public Car getCar(String name) throws Exception{
        
        if (name.endsWith("bmw")) {
            return new Bmw();
        }else {
                throw new Exception("car not fond");
        }
    }
}
```

**静态工厂**
获取类的方法是用static修饰

工厂类
```java
public class CarFactoryStatic {

    public static Car getCar(String name) throws Exception{
        
        if (name.endsWith("bmw")) {
            return new Bmw();
        }else {
                throw new Exception("car not fond");
        }
    }
}
```
## 注解
**autowire自动注入:**
可选两种类型
byName
byName方式自动注入:要求注入的bean的id必须和被注入的bean对象的属性名一致
byType
byType方式自动注入:要求注入的bean的对象类型与被注入的bean对象类型一致,并且在配置文件中的Bean相同类型必须唯一
如果存在多个,会抛异常:
No qualifying bean of type 'com.msb.Pet' available: expected single matching bean but found 2: pet,pet2

全局自动注入
在首行Beans标签下添加default-autowire属性。

**@Component**
在需要注册到容器的类上添加@Component标签,标识这个类由Spring容器接管
约定大于配置
在一个类上添加@Component默认会使用首字母小写的类名作为ID注册到Spring容器。
如果需要手动指定Bean Id可以使用@Component("p")
同属@Component的额外三个注解
@Controller @Service @Repository
这三个注意在MVC开发中会经常用到,除了注解名字和Component不一样之外,其余功能都一样。
Spring额外提供这三个注解的目的主要是为了区分MVC中每个类的区别。
**@Scope**
使用注解注册Bean 默认的作用域还是singleton,可以使用@Scope("prototype")改变对象作用域
**@Value**
在使用注解给对象注入值的时候,不再需要Get/Set方法
```java
@Value("小明")
    private String name;
```

## Aop概念与代理

**面向切面编程 代码增强** 
AOP(Aspect Oriented Programming)面向切面编程。
面向切面,是与OOP(Object Oriented Programming)面向对象编程并列的编程思想。
Spring支持两种方法,那么我们在使用spring进行动态代理时究竟使用的哪一种方法呢?spring优先支持实现接口的方式,如果没有接口则使用cglib方式.
**代理** 
通过代理可以隐藏目标类的具体实现;在不修改目标类代码的情况下能够对其功能进行增强。
- 委托类和代理类有相同的接口或者共同的父类
- 代理类为委托类负责处理消息,并将消息转发给委托类
- 委托类和代理类对象通常存在关联关系
- 一个代理类对象与一个委托类对象关联
- 代理类本身并不是真正的实现者!而是通过调用委托类的方法来实现功能!
静态代理
使用硬编码的方式增强原有方法

- 优点:可以做到不对目标对象进行修改的前提下,对目标对象进行功能的扩展和拦截。
- 缺点:因为代理对象,需要实现与目标对象一样的接口,会导致代理类十分繁多,不易维护,同时一旦接口增加方法,则目标对象和代理类都需要维护。


# 3.SpringBoot入门


SpringBoot主要解决的是在微服务的架构下简化配置(有快速配置)、前后端分离、快速开发
优点:
- 提供了快速启动入门
- 开箱即用、提供默认配置
- 内嵌容器化web项目
- 没有冗余代码生成和xml配置要求

常见问题

Pom.xml

Springboot项目必须要继承的parnet

 

Pom.xml第一行报错

 

进入本地库

for /r %i in (*.lastUpdated) do del %i   

然后update

缺少或包错误

 

删掉 重新update

找不到主类

 

所有错误都解决后

Jar方式运行 首先得有这个jar包

先clean package 生成jar文件,然后再run main方法

 

找不到jdk

 

 

把jre的路径换成jdk的

 

 

 

猜你喜欢

转载自blog.csdn.net/yangshengwei230612/article/details/113740104