Spring --day1

  • ==Spring的IOC/DI==

  • 1.==IOC(Inversion of Control)控制反转==

  • 2.依赖注入==

(1)什么是控制反转呢?

  • 使用对象时,由主动new产生对象转换为由==外部==提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。

(1)什么是依赖注入呢?

  • 在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入

(1)什么IOC/DI思想?

  • IOC:控制反转,控制反转的是对象的创建权

  • DI:依赖注入,绑定对象与对象之间的依赖关系

(2)什么是IOC容器?

Spring创建了一个容器用来存放所创建的对象,这个容器就叫IOC容器

(3)什么是Bean?

容器中所存放的一个个对象就叫Bean或Bean对象

3.1 IOC入门案例

对于入门案例,我们得先分析思路然后再代码实现

3.1.1 入门案例思路分析

(1)Spring是使用容器来管理bean对象的,那么管什么?

  • 主要管理项目中所使用到的类对象,比如(Service和Dao)

(2)如何将被管理的对象告知IOC容器?

  • 使用配置文件

(3)被管理的对象交给IOC容器,要想从容器中获取对象,就先得思考如何获取到IOC容器?

  • Spring框架提供相应的接口

(4)IOC容器得到后,如何从容器中获取bean?

  • 调用Spring框架提供对应接口中的方法

(5)使用Spring导入哪些坐标?

  • 用别人的东西,就需要在pom.xml添加对应的依赖

3.1.2 入门案例代码实现

需求分析:将BookServiceImpl和BookDaoImpl交给Spring管理,并从容器中获取对应的bean对象进行方法调用。

1.创建Maven的java项目

2.pom.xml添加Spring的依赖jar包

3.创建BookService,BookServiceImpl,BookDao和BookDaoImpl四个类

4.resources下添加spring配置文件,并完成bean的配置

5.使用Spring提供的接口完成IOC容器的创建

6.从容器中获取对象进行方法调用

步骤1:创建Maven项目

 

步骤2:添加Spring的依赖jar包

pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

步骤3:添加案例中需要的类

创建BookService,BookServiceImpl,BookDao和BookDaoImpl四个类

public interface BookDao {
    public void save();
}
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
    }
}
public interface BookService {
    public void save();
}
public class BookServiceImpl implements BookService {
    private BookDao bookDao = new BookDaoImpl();
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}

步骤4:添加spring配置文件

resources下添加spring配置文件applicationContext.xml,并完成bean的配置

 

步骤5:在配置文件中完成bean的配置

<?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">
 
    <!--bean标签标示配置bean
        id属性标示给bean起名字
        class属性表示给bean定义类型
    -->
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"/>
​
</beans>

==注意事项:bean定义时id属性在同一个上下文中(配置文件)不能重复==

步骤6:获取IOC容器

使用Spring提供的接口完成IOC容器的创建,创建App类,编写main方法

public class App {
    public static void main(String[] args) {
        //获取IOC容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); 
    }
}

步骤7:从容器中获取对象进行方法调用

public class App {
    public static void main(String[] args) {
        //获取IOC容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); 
//        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
//        bookDao.save();
        BookService bookService = (BookService) ctx.getBean("bookService");
        bookService.save();
    }
}

步骤8:运行程序

Spring的IOC入门案例已经完成,但是在BookServiceImpl的类中依然存在BookDaoImpl对象的new操作,它们之间的耦合度还是比较高,这块该如何解决,就需要用到下面的DI:依赖注入

DI入门案例

步骤1: 去除代码中的new

在BookServiceImpl类中,删除业务层中使用new的方式创建的dao对象

public class BookServiceImpl implements BookService {
    //删除业务层中使用new的方式创建的dao对象
    private BookDao bookDao;
​
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}

步骤2:为属性提供setter方法

在BookServiceImpl类中,为BookDao提供setter方法

public class BookServiceImpl implements BookService {
    //删除业务层中使用new的方式创建的dao对象
    private BookDao bookDao;
​
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
    //提供对应的set方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}
​

步骤3:修改配置完成注入

在配置文件中添加依赖注入的配置

<?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">
    <!--bean标签标示配置bean
        id属性标示给bean起名字
        class属性表示给bean定义类型
    -->
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
​
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <!--配置server与dao的关系-->
        <!--property标签表示配置当前bean的属性
                name属性表示配置哪一个具体的属性
                ref属性表示参照哪一个bean
        -->
        <property name="bookDao" ref="bookDao"/>
    </bean>
​
</beans>

==注意:配置中的两个bookDao的含义是不一样的==

  • name="bookDao"中bookDao的作用是让Spring的IOC容器在获取到名称后,将首字母大写,前面加set找对应的setBookDao()方法进行对象注入

  • ref="bookDao"中bookDao的作用是让Spring能在IOC容器中找到id为bookDao的Bean对象给bookService进行注入

  • 综上所述,对应关系如下:

 

步骤4:运行程序

bean

1:配置别名

 

2:根据名称容器中获取bean对象

public class AppForName {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        //此处根据bean标签的id属性和name属性的任意一个值来获取bean对象
        BookService bookService = (BookService) ctx.getBean("service4");
        bookService.save();
    }
}

3.scope使用

 

  • singleton默认为单例

  • prototype为非单例

注意:

  • 哪些bean对象适合交给容器进行管理?

    • 表现层对象

    • 业务层对象

    • 数据层对象

    • 工具对象

  • 哪些bean对象不适合交给容器进行管理?

    • 封装实例的域对象,因为会引发线程安全问题,所以不适合。

总之bean配置掌握:

 

bean实例化

  • 实例化bean的三种方式,构造方法,静态工厂实例工厂

Spring的IOC实例化对象的三种方式分别是:

  • 构造方法(常用)

  • 静态工厂(了解)

  • 实例工厂(了解)

    • FactoryBean(实用)

构造方法实例化

在上述的环境下,我们来研究下Spring中的第一种bean的创建方式构造方法实例化:

步骤1:准备需要被创建的类

准备一个BookDao和BookDaoImpl类

​
public interface BookDao {
    public void save();
}
​
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
    }
​
}

步骤2:将类配置到Spring容器

<?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">
​
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
​
</beans>

步骤3:编写运行程序

public class AppForInstanceBook {
    public static void main(String[] args) {
        ApplicationContext ctx = new 
            ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();
​
    }
}

步骤4:类中提供构造函数测试

在BookDaoImpl类中添加一个无参构造函数,并打印一句话,方便观察结果。

public class BookDaoImpl implements BookDao {
    public BookDaoImpl() {
        System.out.println("book dao constructor is running ....");
    }
    public void save() {
        System.out.println("book dao save ...");
    }
​
}

运行程序,如果控制台有打印构造函数中的输出,说明Spring容器在创建对象的时候也走的是构造函数

步骤5:将构造函数改成private测试

public class BookDaoImpl implements BookDao {
    private BookDaoImpl() {
        System.out.println("book dao constructor is running ....");
    }
    public void save() {
        System.out.println("book dao save ...");
    }
​
}

运行程序,能执行成功,说明内部走的依然是构造函数,能访问到类中的私有构造方法,显而易见Spring底层用的是反射

步骤6:构造函数中添加一个参数测试

public class BookDaoImpl implements BookDao {
    private BookDaoImpl(int i) {
        System.out.println("book dao constructor is running ....");
    }
    public void save() {
        System.out.println("book dao save ...");
    }
​
}

运行程序,

程序会报错,说明Spring底层使用的是类的无参构造方法。

 

实例工厂与FactoryBean

接下来继续来研究Spring的第三种bean的创建方式实例工厂实例化:

4.2.3.1 环境准备

(1)准备一个UserDao和UserDaoImpl类

public interface UserDao {
    public void save();
}
​
public class UserDaoImpl implements UserDao {
​
    public void save() {
        System.out.println("user dao save ...");
    }
}

(2)创建一个工厂类OrderDaoFactory并提供一个普通方法,注意此处和静态工厂的工厂类不一样的地方是方法不是静态方法

public class UserDaoFactory {
    public UserDao getUserDao(){
        return new UserDaoImpl();
    }
}

(3)编写AppForInstanceUser运行类,在类中通过工厂获取对象

public class AppForInstanceUser {
    public static void main(String[] args) {
        //创建实例工厂对象
        UserDaoFactory userDaoFactory = new UserDaoFactory();
        //通过实例工厂对象创建对象
        UserDao userDao = userDaoFactory.getUserDao();
        userDao.save();
}

(4)运行后,可以查看到结果

对于上面这种实例工厂的方式如何交给Spring管理呢?

4.2.3.2 实例工厂实例化

具体实现步骤为:

(1)在spring的配置文件中添加以下内容:

<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>

实例化工厂运行的顺序是:

  • 创建实例化工厂对象,对应的是第一行配置

  • 调用对象中的方法来创建bean,对应的是第二行配置

    • factory-bean:工厂的实例对象

    • factory-method:工厂对象中的具体创建对象的方法名,对应关系如下:

       

factory-mehod:具体工厂类中创建对象的方法名

(2)在AppForInstanceUser运行类,使用从IOC容器中获取bean的方法进行运行测试

public class AppForInstanceUser {
    public static void main(String[] args) {
        ApplicationContext ctx = new 
            ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) ctx.getBean("userDao");
        userDao.save();
    }
}

(3)运行后,可以查看到结果

实例工厂实例化的方式就已经介绍完了,配置的过程还是比较复杂,所以Spring为了简化这种配置方式就提供了一种叫FactoryBean的方式来简化开发。

4.2.3.3 FactoryBean的使用

具体的使用步骤为:

(1)创建一个UserDaoFactoryBean的类,实现FactoryBean接口,重写接口的方法

public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    //代替原始实例工厂中创建对象的方法
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }
    //返回所创建类的Class对象
    public Class<?> getObjectType() {
        return UserDao.class;
    }
}

(2)在Spring的配置文件中进行配置

<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>

(3)AppForInstanceUser运行类不用做任何修改,直接运行

这种方式在Spring去整合其他框架的时候会被用到,所以这种方式需要大家理解掌握。

查看源码会发现,FactoryBean接口其实会有三个方法,分别是:

T getObject() throws Exception;
​
Class<?> getObjectType();
​
default boolean isSingleton() {
        return true;
}

方法一:getObject(),被重写后,在方法中进行对象的创建并返回

方法二:getObjectType(),被重写后,主要返回的是被创建类的Class对象

方法三:没有被重写,因为它已经给了默认值,从方法名中可以看出其作用是设置对象是否为单例,默认true,从意思上来看,我们猜想默认应该是单例,如何来验证呢?

思路很简单,就是从容器中获取该对象的多个值,打印到控制台,查看是否为同一个对象。

public class AppForInstanceUser {
    public static void main(String[] args) {
        ApplicationContext ctx = new 
            ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao1 = (UserDao) ctx.getBean("userDao");
        UserDao userDao2 = (UserDao) ctx.getBean("userDao");
        System.out.println(userDao1);
        System.out.println(userDao2);
    }
}

打印结果,如下:

通过验证,会发现默认是单例,那如果想改成单例具体如何实现?

只需要将isSingleton()方法进行重写,修改返回为false,即可

//FactoryBean创建对象
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    //代替原始实例工厂中创建对象的方法
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }
​
    public Class<?> getObjectType() {
        return UserDao.class;
    }
​
    public boolean isSingleton() {
        return false;
    }
}

重新运行AppForInstanceUser,查看结果

bean生命周期小结

(1)关于Spring中对bean生命周期控制提供了两种方式:

  • 在配置文件中的bean标签中添加init-methoddestroy-method属性

  • 类实现InitializingBeanDisposableBean接口,这种方式了解下即可。

(2)对于bean的生命周期控制在bean的整个生命周期中所处的位置如下:

  • 初始化容器

    • 1.创建对象(内存分配)

    • 2.执行构造方法

    • 3.执行属性注入(set操作)

    • ==4.执行bean初始化方法==

  • 使用bean

    • 1.执行业务操作

  • 关闭/销毁容器

    • ==1.执行bean销毁方法==

(3)关闭容器的两种方式:

  • ConfigurableApplicationContext是ApplicationContext的子类

    • close()方法

    • registerShutdownHook()方法

前面我们已经完成了bean相关操作的讲解,接下来就进入第二个大的模块DI依赖注入,首先来介绍下Spring中有哪些注入方式?

我们先来思考

  • 向一个类中传递数据的方式有几种?

    • 普通方法(set方法)

    • 构造方法

  • 依赖注入描述了在容器中建立bean与bean之间的依赖关系的过程,如果bean运行需要的是数字或字符串呢?

    • 引用类型

    • 简单类型(基本数据类型与String)

Spring就是基于上面这些知识点,为我们提供了两种注入方式,分别是:

  • setter注入

    • 简单类型

    • ==引用类型==

  • 构造器注入

    • 简单类型

    • 引用类型

5.1 setter注入

  1. 对于setter方式注入引用类型的方式之前已经学习过,快速回顾下:

  • 在bean中定义引用类型属性,并提供可访问的==set==方法

public class BookServiceImpl implements BookService {
    private BookDao bookDao;
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}
  • 配置中使用==property==标签==ref==属性注入引用类型对象

<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
    <property name="bookDao" ref="bookDao"/>
</bean>
​
<bean id="bookDao" class="com.itheima.dao.imipl.BookDaoImpl"/>

5.1.1 环境准备

为了更好的学习下面内容,我们依旧准备一个新环境:

  • 创建一个Maven项目

  • pom.xml添加依赖

  • resources下添加spring的配置文件

这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:

 

(1)项目中添加BookDao、BookDaoImpl、UserDao、UserDaoImpl、BookService和BookServiceImpl类

public interface BookDao {
    public void save();
}
​
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
    }
}
public interface UserDao {
    public void save();
}
public class UserDaoImpl implements UserDao {
    public void save() {
        System.out.println("user dao save ...");
    }
}
​
public interface BookService {
    public void save();
}
​
public class BookServiceImpl implements BookService{
    private BookDao bookDao;
​
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
​
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}

(2)resources下提供spring的配置文件

<?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">
​
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
    </bean>
</beans>

(3)编写AppForDISet运行类,加载Spring的IOC容器,并从中获取对应的bean对象

public class AppForDISet {
    public static void main( String[] args ) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookService bookService = (BookService) ctx.getBean("bookService");
        bookService.save();
    }
}

接下来,在上面这个环境中来完成setter注入的学习:

5.1.2 注入引用数据类型

需求:在bookServiceImpl对象中注入userDao

1.在BookServiceImpl中声明userDao属性

2.为userDao属性提供setter方法

3.在配置文件中使用property标签注入

步骤1:声明属性并提供setter方法

在BookServiceImpl中声明userDao属性,并提供setter方法

public class BookServiceImpl implements BookService{
    private BookDao bookDao;
    private UserDao userDao;
    
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
​
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
        userDao.save();
    }
}

步骤2:配置文件中进行注入配置

在applicationContext.xml配置文件中使用property标签注入

<?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">
​
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>

步骤3:运行程序

运行AppForDISet类,查看结果,说明userDao已经成功注入。

5.1.3 注入简单数据类型

需求:给BookDaoImpl注入一些简单数据类型的数据

参考引用数据类型的注入,我们可以推出具体的步骤为:

1.在BookDaoImpl类中声明对应的简单数据类型的属性

2.为这些属性提供对应的setter方法

3.在applicationContext.xml中配置

思考:

引用类型使用的是<property name="" ref=""/>,简单数据类型还是使用ref么?

ref是指向Spring的IOC容器中的另一个bean对象的,对于简单数据类型,没有对应的bean对象,该如何配置?

步骤1:声明属性并提供setter方法

在BookDaoImpl类中声明对应的简单数据类型的属性,并提供对应的setter方法

public class BookDaoImpl implements BookDao {
​
    private String databaseName;
    private int connectionNum;
​
    public void setConnectionNum(int connectionNum) {
        this.connectionNum = connectionNum;
    }
​
    public void setDatabaseName(String databaseName) {
        this.databaseName = databaseName;
    }
​
    public void save() {
        System.out.println("book dao save ..."+databaseName+","+connectionNum);
    }
}

步骤2:配置文件中进行注入配置

在applicationContext.xml配置文件中使用property标签注入

<?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">
​
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
        <property name="databaseName" value="mysql"/>
        <property name="connectionNum" value="10"/>
    </bean>
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>

说明:

value:后面跟的是简单数据类型,对于参数类型,Spring在注入的时候会自动转换,但是不能写成

<property name="connectionNum" value="abc"/>

这样的话,spring在将abc转换成int类型的时候就会报错。

步骤3:运行程序

运行AppForDISet类,查看结果,说明userDao已经成功注入。

注意:两个property注入标签的顺序可以任意。

对于setter注入方式的基本使用就已经介绍完了,

  • 对于引用数据类型使用的是<property name="" ref=""/>

  • 对于简单数据类型使用的是<property name="" value=""/>

5.2 构造器注入

5.2.1 环境准备

构造器注入也就是构造方法注入,学习之前,还是先准备下环境:

  • 创建一个Maven项目

  • pom.xml添加依赖

  • resources下添加spring的配置文件

这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:

(1)项目中添加BookDao、BookDaoImpl、UserDao、UserDaoImpl、BookService和BookServiceImpl类

public interface BookDao {
    public void save();
}
​
public class BookDaoImpl implements BookDao {
    
    private String databaseName;
    private int connectionNum;
    
    public void save() {
        System.out.println("book dao save ...");
    }
}
public interface UserDao {
    public void save();
}
public class UserDaoImpl implements UserDao {
    public void save() {
        System.out.println("user dao save ...");
    }
}
​
public interface BookService {
    public void save();
}
​
public class BookServiceImpl implements BookService{
    private BookDao bookDao;
​
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
​
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}

(2)resources下提供spring的配置文件

<?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">
​
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
    </bean>
</beans>

(3)编写AppForDIConstructor运行类,加载Spring的IOC容器,并从中获取对应的bean对象

public class AppForDIConstructor {
    public static void main( String[] args ) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookService bookService = (BookService) ctx.getBean("bookService");
        bookService.save();
    }
}

5.2.2 构造器注入引用数据类型

接下来,在上面这个环境中来完成构造器注入的学习:

需求:将BookServiceImpl类中的bookDao修改成使用构造器的方式注入。

1.将bookDao的setter方法删除掉

2.添加带有bookDao参数的构造方法

3.在applicationContext.xml中配置

步骤1:删除setter方法并提供构造方法

在BookServiceImpl类中将bookDao的setter方法删除掉,并添加带有bookDao参数的构造方法

public class BookServiceImpl implements BookService{
    private BookDao bookDao;
​
    public BookServiceImpl(BookDao bookDao) {
        this.bookDao = bookDao;
    }
​
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}

步骤2:配置文件中进行配置构造方式注入

在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">
​
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <constructor-arg name="bookDao" ref="bookDao"/>
    </bean>
</beans>

说明:

标签<constructor-arg>中

  • name属性对应的值为构造函数中方法形参的参数名,必须要保持一致。

  • ref属性指向的是spring的IOC容器中其他bean对象。

步骤3:运行程序

运行AppForDIConstructor类,查看结果,说明bookDao已经成功注入。

5.2.3 构造器注入多个引用数据类型

需求:在BookServiceImpl使用构造函数注入多个引用数据类型,比如userDao

1.声明userDao属性

2.生成一个带有bookDao和userDao参数的构造函数

3.在applicationContext.xml中配置注入

步骤1:提供多个属性的构造函数

在BookServiceImpl声明userDao并提供多个参数的构造函数

public class BookServiceImpl implements BookService{
    private BookDao bookDao;
    private UserDao userDao;
​
    public BookServiceImpl(BookDao bookDao,UserDao userDao) {
        this.bookDao = bookDao;
        this.userDao = userDao;
    }
​
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
        userDao.save();
    }
}

步骤2:配置文件中配置多参数注入

在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">
​
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <constructor-arg name="bookDao" ref="bookDao"/>
        <constructor-arg name="userDao" ref="userDao"/>
    </bean>
</beans>

说明:这两个<contructor-arg>的配置顺序可以任意

步骤3:运行程序

运行AppForDIConstructor类,查看结果,说明userDao已经成功注入。

5.2.4 构造器注入多个简单数据类型

需求:在BookDaoImpl中,使用构造函数注入databaseName和connectionNum两个参数。

参考引用数据类型的注入,我们可以推出具体的步骤为:

1.提供一个包含这两个参数的构造方法

2.在applicationContext.xml中进行注入配置

步骤1:添加多个简单属性并提供构造方法

修改BookDaoImpl类,添加构造方法

public class BookDaoImpl implements BookDao {
    private String databaseName;
    private int connectionNum;
​
    public BookDaoImpl(String databaseName, int connectionNum) {
        this.databaseName = databaseName;
        this.connectionNum = connectionNum;
    }
​
    public void save() {
        System.out.println("book dao save ..."+databaseName+","+connectionNum);
    }
}

步骤2:配置完成多个属性构造器注入

在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">
​
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
        <constructor-arg name="databaseName" value="mysql"/>
        <constructor-arg name="connectionNum" value="666"/>
    </bean>
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <constructor-arg name="bookDao" ref="bookDao"/>
        <constructor-arg name="userDao" ref="userDao"/>
    </bean>
</beans>

说明:这两个<contructor-arg>的配置顺序可以任意

步骤3:运行程序

运行AppForDIConstructor类,查看结果

 

上面已经完成了构造函数注入的基本使用,但是会存在一些问题:

  • 当构造函数中方法的参数名发生变化后,配置文件中的name属性也需要跟着变

  • 这两块存在紧耦合,具体该如何解决?

在解决这个问题之前,需要提前说明的是,这个参数名发生变化的情况并不多,所以上面的还是比较主流的配置方式,下面介绍的,大家都以了解为主。

方式一:删除name属性,添加type属性,按照类型注入

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
    <constructor-arg type="int" value="10"/>
    <constructor-arg type="java.lang.String" value="mysql"/>
</bean>
  • 这种方式可以解决构造函数形参名发生变化带来的耦合问题

  • 但是如果构造方法参数中有类型相同的参数,这种方式就不太好实现了

方式二:删除type属性,添加index属性,按照索引下标注入,下标从0开始

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
    <constructor-arg index="1" value="100"/>
    <constructor-arg index="0" value="mysql"/>
</bean>
  • 这种方式可以解决参数类型重复问题

  • 但是如果构造方法参数顺序发生变化后,这种方式又带来了耦合问题

介绍完两种参数的注入方式,具体我们该如何选择呢?

  1. 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现

    • 强制依赖指对象在创建的过程中必须要注入指定的参数

  2. 可选依赖使用setter注入进行,灵活性强

    • 可选依赖指对象在创建过程中注入的参数可有可无

  3. Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨

  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入

  5. 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入

  6. ==自己开发的模块推荐使用setter注入==

依赖注入的方式已经介绍完,要讲解的是Spring的依赖注入的实现方式:

  • setter注入

    • 简单数据类型

      <bean ...>
          <property name="" value=""/>
      </bean>
    • 引用数据类型

      <bean ...>
          <property name="" ref=""/>
      </bean>
  • 构造器注入

    • 简单数据类型

      <bean ...>
          <constructor-arg name="" index="" type="" value=""/>
      </bean>
    • 引用数据类型

      <bean ...>
          <constructor-arg name="" index="" type="" ref=""/>
      </bean>
  • 依赖注入的方式选择上

    • 建议使用setter注入

集合注入

5.4.2 注入数组类型数据

<property name="array">
    <array>
        <value>100</value>
        <value>200</value>
        <value>300</value>
    </array>
</property>

5.4.3 注入List类型数据

<property name="list">
    <list>
        <value>itcast</value>
        <value>itheima</value>
        <value>boxuegu</value>
        <value>chuanzhihui</value>
    </list>
</property>

5.4.4 注入Set类型数据

<property name="set">
    <set>
        <value>itcast</value>
        <value>itheima</value>
        <value>boxuegu</value>
        <value>boxuegu</value>
    </set>
</property>

5.4.5 注入Map类型数据

<property name="map">
    <map>
        <entry key="country" value="china"/>
        <entry key="province" value="henan"/>
        <entry key="city" value="kaifeng"/>
    </map>
</property>

5.4.6 注入Properties类型数据

<property name="properties">
    <props>
        <prop key="country">china</prop>
        <prop key="province">henan</prop>
        <prop key="city">kaifeng</prop>
    </props>
</property>

说明:

  • property标签表示setter方式注入,构造方式注入constructor-arg标签内部也可以写<array><list><set><map><props>标签

  • List的底层也是通过数组实现的,所以<list><array>标签是可以混用

  • 集合中要添加引用类型,只需要把<value>标签改成<ref>标签,这种方式用的比较少

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以通过在Spring Security中配置一个Token认证过滤器来实现基于WebSocket的Token认证。具体步骤如下: 1. 创建一个TokenAuthenticationFilter类,继承自OncePerRequestFilter并实现doFilterInternal方法。该类负责检查请求中是否包含有效的Token,并进行相应的认证处理。 ```java public class TokenAuthenticationFilter extends OncePerRequestFilter { private final TokenProvider tokenProvider; public TokenAuthenticationFilter(TokenProvider tokenProvider) { this.tokenProvider = tokenProvider; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = getTokenFromRequest(request); if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) { Authentication authentication = tokenProvider.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); } filterChain.doFilter(request, response); } private String getTokenFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } } ``` 2. 创建一个TokenProvider类,用于生成Token和验证Token的有效性,并根据Token获取用户信息。 ```java @Component public class TokenProvider { private static final String SECRET_KEY = "my-secret-key"; private static final long EXPIRATION_TIME = 86400000; // 1 day public String generateToken(Authentication authentication) { UserPrincipal principal = (UserPrincipal) authentication.getPrincipal(); Date expirationDate = new Date(System.currentTimeMillis() + EXPIRATION_TIME); return Jwts.builder() .setSubject(Long.toString(principal.getId())) .setIssuedAt(new Date()) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); } public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token); return true; } catch (Exception e) { return false; } } public Authentication getAuthentication(String token) { Claims claims = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody(); Long userId = Long.parseLong(claims.getSubject()); UserPrincipal principal = new UserPrincipal(userId); return new UsernamePasswordAuthenticationToken(principal, "", principal.getAuthorities()); } } ``` 3. 在配置类中注册TokenAuthenticationFilter和TokenProvider,并将TokenAuthenticationFilter添加到Spring Security的过滤器链中。 ```java @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Autowired private TokenProvider tokenProvider; @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws").setAllowedOriginPatterns("*").withSockJS(); } @Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors(new ChannelInterceptorAdapter() { @Override public Message<?> preSend(Message<?> message, MessageChannel channel) { StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); if (StompCommand.CONNECT.equals(accessor.getCommand())) { String token = accessor.getFirstNativeHeader("Authorization"); if (StringUtils.hasText(token) && token.startsWith("Bearer ")) { token = token.substring(7); TokenAuthenticationFilter filter = new TokenAuthenticationFilter(tokenProvider); SecurityContextHolder.getContext().setAuthentication(filter.getAuthentication(token)); } } return message; } }); } @Override public void configureClientOutboundChannel(ChannelRegistration registration) { } @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { } @Override public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) { } @Override public boolean configureMessageConverters(List<MessageConverter> messageConverters) { return true; } @Override public void configureWebSocketTransport(WebSocketTransportRegistration registry) { } @Bean public TokenAuthenticationFilter tokenAuthenticationFilter() throws Exception { TokenAuthenticationFilter filter = new TokenAuthenticationFilter(tokenProvider); filter.setAuthenticationManager(authenticationManager()); return filter; } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().authorizeRequests() .antMatchers("/api/auth/**").permitAll() .anyRequest().authenticated(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()) .passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override @Bean public UserDetailsService userDetailsService() { return new UserDetailsServiceImpl(); } } ``` 在上述代码中,我们通过重写configureClientInboundChannel方法,在连接到WebSocket时获取请求中的Token,并使用TokenAuthenticationFilter进行认证。注意,我们需要将TokenAuthenticationFilter添加到Spring Security的过滤器链中,以便它能够在WebSocket连接期间对请求进行拦截。 最后,我们需要在客户端的连接请求中添加Authorization头部,以便在服务端进行Token认证。例如: ```javascript stompClient.connect({}, function(frame) { console.log('Connected: ' + frame); stompClient.subscribe('/topic/greetings', function(greeting) { showGreeting(JSON.parse(greeting.body).content); }); }, function(error) { console.log('Error: ' + error); }, {"Authorization": "Bearer " + token}); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值