天天看点

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中相当常见