天天看點

Spring三天入門(一):IOC、注入以及注解

說在前面

github位址

https://github.com/Michilay/Spring

spring系列視訊如下

Spring三天入門(一):IOC、注入以及注解

Spring三天入門(二):代理模式,動态代理,spring實作AOP,切入

Spring三天入門(三):spring整合mybatis,aop事務切入

spring簡介

傳統的ssh指的是struct2+spring+hibernate

現在通常使用ssm指的是spring+spring mvc+mybatis

spring相當于是個大雜燴,整合了現有的技術架構

  1. spring是一個開源的免費容器
  2. spring是一個輕量級的非入侵的架構,引入了spring不會對原來的項目有任何影響
  3. 控制反轉IOC和面向切面程式設計AOP
  4. 支援事務的處理,聲明式事務因為AOP。對架構整合的支援

springIOC思想

idea建立一個maven項目,導入項目依賴

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.9</version>
        </dependency>
    <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
		<dependency>
    		<groupId>org.junit.jupiter</groupId>
    		<artifactId>junit-jupiter-api</artifactId>
    		<version>5.7.0</version>
    		<scope>test</scope>
		</dependency>

           

導入spriing-webmvc一個包會有多個依賴項,junit用來測試

dao層寫使用者和使用者實作

package com.michilay.dao;

public interface UserDao {
    void getUser();
}

           
package com.michilay.dao;

public class UserDaoImpl implements UserDao{
    @Override
    public void getUser() {
        System.out.println("預設擷取使用者的資料");
    }
}

           

service寫業務類和業務實作類

package com.michilay.service;

public interface UserService {
    void getUser();
}

           
package com.michilay.service;

import com.michilay.dao.UserDao;
import com.michilay.dao.UserDaoImpl;
import com.michilay.dao.UserDaoMySqlImpl;

public class UserServiceImpl implements UserService{

    private UserDao userDao = new UserDaoImpl();

    @Override
    public void getUser() {
        userDao.getUser();
    }
}

           

建立測試看看能不能走通

import com.michilay.dao.UserDaoImpl;
import com.michilay.service.UserServiceImpl;
import org.junit.jupiter.api.Test;

public class MyTest {
    @Test
    public void test(){
        //使用者實際上調用業務層,dao層不需要接觸
        UserServiceImpl userService = new UserServiceImpl();
        userService.getUser();
    }
}

           

預設擷取使用者的資料

程序已結束,退出代碼為 0

這樣是完全沒問題的

但是當我們需要有新的持久層需求的時候,比如這時候有mysql的持久層,新加了一個UserDaoMySqlImpl.class

package com.michilay.dao;

public class UserDaoMySqlImpl implements UserDao{
    @Override
    public void getUser() {
        System.out.println("mysql擷取使用者資料");
    }
}

           

如果要和業務層連結的話,就得在業務層修改mysql的執行個體化

package com.michilay.service;

import com.michilay.dao.UserDao;
import com.michilay.dao.UserDaoImpl;
import com.michilay.dao.UserDaoMySqlImpl;

public class UserServiceImpl implements UserService{

    private UserDao userDao = new UserDaoMySqlImpl();

    @Override
    public void getUser() {
        userDao.getUser();
    }
}
           

這樣就很麻煩,使用者的需求可能會影響我們原來的代碼,如果代碼量大的話,修改一次的成本就會十分昂貴,需要在代碼裡面修改,大大降低了維護性,這就完全違反了IOC,那麼我們要怎麼去修改呢

使用一個Set接口實作

private UserDao userDao;
    //利用set進行動态實作值的注入
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
           

使用了set注入,程式不再具有主動性,而是成為了被動的接受對象

@Test
    public void test(){
        //使用者實際上調用業務層,dao層不需要接觸
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDao(new UserDaoImpl());
        //userService.setUserDao(new UserDaoMySqlImpl());
        userService.getUser();
    }
           

想擷取什麼樣子的dao就可以擷取什麼樣的,這種思想從本質上解決的IOC的問題,可以更加專注于業務層,降低耦合性。

IOC的本質

DI(依賴注入)是實作ioc的一種方法,依賴對象的方式反轉了。是哦那個Bean的xml來配置,将配置檔案放在Bean内,可以将定義資訊和實作分離,之後可以使用注解,達到零配置

但是這樣仍然有點麻煩,如果是内部的對象好說,但是外部的其他類型的非java對象該怎樣導入進來呢?

spring基本配置

建立一個新的子產品

建立pojo.hello的class,聲明一個str對象,自動生成get,set和tostring方法

package com.michilay.pojo;

public class Hello {
    private String str;

    @Override
    public String toString() {
        return "Hello{" +
                "str='" + str + '\'' +
                '}';
    }

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }
}

           

這裡spring提供beans來建立對象,在配置檔案中建立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">

<!--    使用spring來建立對象,在spring這些都成為Bean-->
    <bean id="hello" class="com.michilay.pojo.Hello">
        <property name="str" value="Spring"/>
    </bean>

</beans>
           

測試類如下

@Test
    public void test(){
        //擷取spring的上下文對象
        ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        //對象都交給spring管理,使用對象直接取出
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.toString());
    }
           

依賴倒轉原則,使用set來注入,有主動的程式的建立對象變成了被動的接受對象

那麼回到剛才的項目,如果是多個sql需求我們應該怎麼改動呢?

在配置檔案裡下如下内容

<bean id="mysqlImpl" class="com.michilay.dao.UserDaoMySqlImpl"/>
        <bean id="impl" class="com.michilay.dao.UserDaoImpl"/>

        <bean id="userServiceImpl" class="com.michilay.service.UserServiceImpl">
        <!--        ref:引用spring容器中建立好的對象-->
        <property name="userDao" ref="mysqlImpl"/>
        </bean>
           
如果是對象屬性,就是用ref,如果是普通屬性,就用value

執行個體化的對象,直接交給spring以每一個bean的形式建立,将id,set注入給業務層實作類,測試類如下

@Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        UserServiceImpl  userServiceImpl= (UserServiceImpl) context.getBean("userServiceImpl");
        userServiceImpl.getUser();
    }
           

IOC就是由spring來建立,管理,裝配!

ioc建立對象的方式

  1. 預設使用無參構造建立對象
private String name;    
	public User(){
        System.out.println("user的無參構造");
    }
           
<bean id="user" class="com.michilay.pojo.User">
        <property name="name" value="michilay"/>
    </bean>
           
  1. 如果沒有無參構造函數的話
    1. 第一種方式,下标指派
private String name;     
	public User(String name){
        this.name = name;
    }
           
<!--    下表指派-->
    <bean id="user" class="com.michilay.pojo.User">
        <constructor-arg index="0" value="michilay"/>
<!--        <property name="name" value="michilay"/>-->
    </bean>
           
  1. 第二種方式,通過類型建立
<!--    不建議使用-->
    <bean id="user" class="com.michilay.pojo.User">
        <constructor-arg type="java.lang.String" value="michilay"/>
    </bean>
           
  1. 第三種方式,直接通過參數名來設定
<!--    直接通過參數名來設定-->
    <bean id="user" class="com.michilay.pojo.User">
        <constructor-arg name="name" value="michilay"/>
    </bean>
           

注意:在context擷取上下文對象之後,Beans裡面的對象就已經都被建立了,而且這個對象隻有一個位址,不管它被指向其他對象多少次

spring配置深入

spring别名

使用getBean拿到的對象是一個

bean屬性

一個對象就是一個Bean,

id = 對象名名,相當于spring根據這個名字獲得配置對象,id有唯一性

class = new 的對象,對象所在的全路徑,bean對象的全限定名:報名+屬性

name 對象名字,别名,而且name可以同時取多個

property 相當于給對象設定值

import

import适用于團隊開發,可以将多個配置檔案,導入為一個總的配置檔案

<import resource="Beans1.xml"/>
    <import resource="Beans2.xml"/>
    <import resource="Beans3.xml"/>
           

各種beans裡面的對象name相同,就會選取一個,如果後來的name相同,就用後面bean對象裡的屬性值覆寫前者

注入

構造器注入

構造器注入在ioc建立對象的方式中已有提到

set注入[重要]

依賴注入,也就是set注入,依賴注入分為依賴和注入,所謂依賴,就是bean建立的對象依賴于容器;注入,就是bean對象中的所有屬性,由容器來注入

  1. 測試對象student
    public class Student {
        private String name;
        private Address address;
        private String[] books;
        private List<String> hobbys;
        private Map<String,String> card;
        private Set<String> games;
        private String wife;
        private Properties info;
        //還有set,get方法和tostring
               
  2. 複雜對象
    public class Address {
        private String address;
        //還有set,get方法和tostring
               
  3. 各種類型對象的注入方式
    <bean id="address" class="com.michilay.pojo.Address">
            <property name="address" value="湖北"></property>
        </bean>
        <bean id="student" class="com.michilay.pojo.Student">
    <!--       普通值注入,value-->
            <property name="name" value="michilay"/>
    <!--        Bean注入,value-->
            <property name="address" ref="address"/>
    <!--        數組注入-->
            <property name="books">
                <array>
                    <value>紅樓夢</value>
                    <value>西遊記</value>
                    <value>三國演義</value>
                    <value>水浒傳</value>
                </array>
            </property>
    <!--        List集合注入-->
             <property name="hobbys">
                 <list>
                     <value>聽歌</value>
                     <value>學習</value>
                     <value>吃飯</value>
                 </list>
             </property>
    <!--        map-->
            <property name="card">
                <map>
                    <entry key="身份證" value="123214123"/>
                    <entry key="銀行卡" value="92324322341"/>
                </map>
            </property>
    <!--        set-->
            <property name="games">
                <set>
                    <value>LOL</value>
                    <value>MC</value>
                    <value>WAR3</value>
                </set>
            </property>
    <!--        null-->
            <property name="wife">
                <null/>
            </property>
    <!--        properties-->
            <property name="info">
                <props>
                    <prop key="id">200123</prop>
                    <prop key="sex">男</prop>
                    <prop key="username">張三</prop>
                </props>
            </property>
        </bean>
               

拓展方式注入

User實體類

public class User {
    private String name;
    private int age;
    //加上get,set和tostring以及無參構造和有參構造
           
  1. p命名空間

    對簡單的對象進行注入

    在beans标簽裡添加下面一行

  2. c命名空間

    對構造器進行注入

    添加标簽

bean的作用域

  1. 單例模式

    預設使用的就是單例模式,可以添加scope="singleton"屬性

    @Test
        public void test1(){
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            User user1 = context.getBean("user2", User.class);
            User user = context.getBean("user2", User.class);
            System.out.println(user == user1);
        }
               
    true
  2. 原型模式

    每次從容器中get的時候,都會産生一個新的對象,scope=“prototype”

    @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user1 = context.getBean("user2", User.class);
        User user = context.getBean("user2", User.class);
        System.out.println(user1.hashCode());
        System.out.println(user.hashCode());
        System.out.println(user == user1);
    }
               

    846254484

    592983282

    false

  3. 其餘的request、session、application存在于web開發中

Bean自動裝配

自動裝配是Spring滿足bean依賴的一種方式

spring會在上下文中尋找,并自動給bean裝配屬性!

在spring中有三種裝配的方式

  1. 在xml中顯示的配置
  2. 在java中顯示配置
  3. 隐式的自動裝配

people實體類

public class People {
    private Cat cat;
    private Dog dog;
    private String name;
    //set和get以及tostring方法
           

byName自動裝配

會自動在容器上下文中查找和自己對象set方法後面的值對應的bean的id,要保證所有bean的id唯一

<bean id="cat" class="com.michilay.pojo.Cat"/>
    <bean id="dog" class="com.michilay.pojo.Dog"/>

    <bean id="people" class="com.michilay.pojo.People" autowire="byName">
        <property name="name" value="michilay"/>
    </bean>
           

byType自動裝配

會自動在容器上下文中查找和自己對象屬性類型對應的bean,必須保證class全局唯一

<bean id="cat" class="com.michilay.pojo.Cat"/>
    <bean id="do11g" class="com.michilay.pojo.Dog"/>

    <bean id="people" class="com.michilay.pojo.People" autowire="byType">
        <property name="name" value="michilay"/>
    </bean>
           

注解

使用注解自動裝配

在spring4之後,使用注解開發必須要導入aop的包

導入context限制和配置注解的支援

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	        https://www.springframework.org/schema/beans/spring-beans.xsd
	        http://www.springframework.org/schema/context
	        https://www.springframework.org/schema/context/spring-context.xsd">
		
		<!--開啟注解的支援    -->
        <context:annotation-config/>
</beans>
           

@Autowired

直接在屬性上使用即可,也可以在set方法上使用

注解是通過反射的原理實作,可以不需要set方法,但是需要将改屬性在IOC容器中存在

當注入在IOC容器中的類型隻有一個的時候,按照byType進行比對,預設也是這樣;

當注入在IOC容器中存在多個同一類型的對象時,按照byName進行比對。

如果它的required屬性為false,說明這個對象可以為null,否則不允許為空

@Nullable

字段标記了這個注解,改注解就可以為null

@Qualifier(value=“xxx”)

如果@Autowired裝配的對象比較複雜,就可以使用@Qualifier設定和它id相同的值對應,指定一個唯一的bean

<bean id="cat111" class="com.michilay.pojo.Cat"/>
<bean id="dog" class="com.michilay.pojo.Dog"/>
<bean id="people" class="com.michilay.pojo.People" autowire="byType"/>
           
public class People {
    @Autowired
    @Qualifier(value = "cat111")
    private Cat cat;
    @Autowired
    private Dog dog;
    private String name;
           

@Resource

這個一個java的注解,但是也能起到Autowired的作用,它會先和名字比對,再和類型比對,它也可以取value值

注解開發

在使用下面注解之前,需要在配置檔案裡添加包掃描器

<context:component-scan base-package="com.michilay"/>
<context:annotation-config/>
           

@Component

元件,放在類的上面,說明這個類被spring給管理了,相當于這一行代碼

@Value

隻有類的托管還不夠,類的屬性就由Value注解,它相當于這一行代碼,如果這個屬性有set方法,把Value注解放在set方法效果相同**(優先級高于類屬性)**

@Component
public class User {
    public String name = "Michilay";
    @Value("123")
    public void setName(String name) {
        this.name = name;
    }
}
           

@Repository

@Service

@Controller

這三個注解和component是一樣效果,隻不過它們分别用在web開發中不同的位置

dao層用repository注解,service層用service注解,controller層用controller注解

@Scope

作用域注解,和bean的作用域這一小節效果相同,注解後面加上(“singleton”)改屬性

注解總結

注解相對于xml友善,當類和屬性不多的時候

注解不是自己的類使用不了,維護更加負責

開發java最常用的組合就是

xml用來管理bean,注解隻負責完成屬性的注入

使用注解代替xml配置檔案

完全不使用spring的xml配置檔案,把所有的内容全部交給java來做

javaConfig是Spring的一個子項目,在Spring4之後,它成為了一個核心功能

這裡建立個config包寫一個config類

package com.michilay.config;

import com.michilay.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@ComponentScan("com.michilay.pojo")
@Import(myConfig2.class)
public class myConfig {
    @Bean
    public User getUser(){
        return new User();//就是傳回要注入到bean的對象
    }
}
           

@Configuration

代表這個類是一個注解類,相當于beans标簽

@ComponentScan

是一個包掃描器标簽

@Import

相當于import标簽,當有多個配置檔案的時候導入

@Bean

相當于Bean标簽,方法名就是bean中的id屬性,傳回值就是bean中的class屬性

pojo包中的User實體類

package com.michilay.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


//說明這個類被spring接管了,注冊到了IOC中
//@Component
public class User {
    private String name;

    public String getName() {
        return name;
    }
    @Value("michilay")
    public void setName(String name) {
        this.name = name;
    }
}
           

測試類

import com.michilay.config.myConfig;
import com.michilay.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MyTest {
    @Test
    public void test(){
        //如果完全使用配置類,就隻能用這個上下文來擷取容器,通過配置類的class對象加載
        ApplicationContext context = new AnnotationConfigApplicationContext(myConfig.class);
        User getUser = context.getBean("getUser", User.class);
        System.out.println(getUser.getName());
    }
}
           

這種沒有xml的配置方式,在springboot中相當常見