spring-5学习笔记
  37TR5pANejWp 2024年04月03日 49 0

Spring5-2023/08/23(稍后更新6)

01 初识Spring

1.1 简介

Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。

Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架

历史:

  • 2002,首次推出了Spring框架的雏形:interface21框架!
  • Spring框架即以interface21为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日正式发布其1.0版本
  • Spring理念:使现有技术更加容易使用,本身是一个大杂烩,整合了现有的许多技术框架
  • 原来的技术路线:SSH:Struct2+Spring+Hibernate
  • 现在的技术路线:SSM:SpringMVC+Spring+Mybatis

官网:https://docs.spring.io/spring-framework/reference/overview.html

GitHub:https://github.com/spring-projects/spring-framework

<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.22.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.22.RELEASE</version>
</dependency>

1.2 优点

  • Spring是一个开源的免费的框架(容器)

  • Spring是一个轻量级的、非入侵式的框架

  • 控制反转(IOC)、面向切面编程(AOP)

  • 支持事务的处理,对框架整合的支持!

总结一句话:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架!

1.3 Spring组成

  1. 核心容器(Spring Core)

      核心容器提供Spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。BeanFactory使用控制反转(IoC)模式将应用的配置和依赖性规范与实际的应用程序代码分开。

  2. 应用上下文(Spring Context)

      Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,如JNDI、EJB、电子邮件、国际化、校验和调度功能。

  3. Spring面向切面编程(Spring AOP)

      通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring框架中。所以,可以很容易地使 Spring框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。

  4. JDBC和DAO模块(Spring DAO)

      JDBC、DAO的抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理,和不同数据库供应商所抛出的错误信息。异常层次结构简化了错误处理,并且极大的降低了需要编写的代码数量,比如打开和关闭链接。

  5. 对象实体映射(Spring ORM)

      Spring框架插入了若干个ORM框架,从而提供了ORM对象的关系工具,其中包括了Hibernate、JDO和 IBatis SQL Map等,所有这些都遵从Spring的通用事物和DAO异常层次结构。

  6. Web模块(Spring Web)

      Web上下文模块建立在应用程序上下文模块之上,为基于web的应用程序提供了上下文。所以Spring框架支持与Struts集成,web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

  7. MVC模块(Spring Web MVC)

      MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。MVC容纳了大量视图技术,其中包括JSP、POI等,模型来有JavaBean来构成,存放于m当中,而视图是一个街口,负责实现模型,控制器表示逻辑代码,由c的事情。Spring框架的功能可以用在任何J2EE服务器当中,大多数功能也适用于不受管理的环境。Spring的核心要点就是支持不绑定到特定J2EE服务的可重用业务和数据的访问的对象,毫无疑问这样的对象可以在不同的J2EE环境,独立应用程序和测试环境之间重用。

1.4 扩展

在Spring的官网有这个介绍:现代化的Java开发,就是基于Spring的开发

  • Spring Boot
    • 一个快速开发的脚手架。
    • 基于Spring Boot可以快速的开发单个微服务。
    • 约定大于配置!
  • Spring Cloud
    • SpringCloud是基于SpringBoot实现的。

现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及SpringMVC!

弊端:发展了太久之后,违背了原来的理念!配置十分繁琐,人称“配置地狱!”

02 IOC理论推导

2.1 初见IOC

1、UserDao接口

package com.wu.dao;

public interface UserDao {

    void getUser();

}

2、UserDaoImpl实现类(现在我们假设,有三个类似结构但功能不同的实现类均实现了UserDao接口)

package com.wu.dao;

public class UserDaoImpl implements UserDao{

    @Override
    public void getUser() {
        System.out.println("默认获取用户数据");
    }

}
package com.wu.dao;

public class UserDaoMysqlImpl implements UserDao{

    public void getUser(){
        System.out.println("获取Mysql默认数据");
    }
    
}
package com.wu.dao;

public class UserDaoOracleImpl implements UserDao{
    @Override
    public void getUser() {
        System.out.println("Oracle获取数据!");
    }
    
}

3、UserService业务接口

package com.wu.Service;

public interface UserService {

    void getUser();
    
}

4、UserServiceImpl业务实现类(思考一下,如果每次我们想让对象实例化时,如果按照以前的这种写法,如果每次需要更改时,都需要修改对应的语句,这是十分麻烦的)

package com.wu.Service;

import com.wu.dao.UserDao;
import com.wu.dao.UserDaoImpl;
import com.wu.dao.UserDaoMysqlImpl;
import com.wu.dao.UserDaoOracleImpl;

public class UserServiceImpl implements UserService{

    private UserDao userDao = new UserDaoOracleImpl();
    /*private UserDao userDao = new UserDaoMysqlImpl();*/
    /*private UserDao userDao = new UserDaoImpl();*/

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

​ 这个问题的本质在于,现在程序的运行逻辑控制权在程序员手上。由于内部较高的耦合度导致当用户需求发生变化时,需要大面积的修改一些对象实例的语句。这是很不好的。Spring提出的解决思路就是IOC(Inversion Of Control),即将程序运行的逻辑控制权从程序员手上解放出来,同时降低内部的耦合度。

​ 来看下面这个ServiceImpl的写法,重点关注对象实例化部分的代码。可以看到在这里原来的一句话被拆成2句,并且新增了1个方法。利用set方法实现对对象的动态注入,使得程序内部耦合度降低,并在一定程度上减少了程序员对程序运行的代码直接控制。

package com.wu.Service;

import com.wu.dao.UserDao;
import com.wu.dao.UserDaoImpl;
import com.wu.dao.UserDaoMysqlImpl;
import com.wu.dao.UserDaoOracleImpl;

public class UserServiceImpl implements UserService{

    /*private UserDao userDao = new UserDaoOracleImpl();*/
    /*private UserDao userDao = new UserDaoMysqlImpl();*/
    /*private UserDao userDao = new UserDaoImpl();*/
    private UserDao userDao;
    
    public void setUserDao(UserDao userDao){this.userDao=userDao;}
    
    @Override
    public void getUser() {
        userDao.getUser();
    }
}
  • 之前,程序是主动创建对象!控制权在程序员手上!
  • 使用了set注入后,程序不再具有主动性,而是变成了被动的接受对象!

这就是IOC的原型!

2.2 IOC本质

​ 控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,但控制反转后对象的创建被转移给第三方。个人所谓控制反转就是:获得依赖对象的方法反转了。

​ 控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。

03 Hello Spring

3.1 模块搭建

1、新建实体类

package com.wu.pojo;

public class Hello {

    private String str;

    public String getStr() {
        return str;
    }

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

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

2、新建配置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
    类型 变量名 = new 类型();
    Hello hello = new Hello();

    id = 变量名
    class = new的对象
    property 相当于给对象中的属性设置一个值!
    -->
    <bean id="hello" class="com.wu.pojo.Hello">
        <property name="str" value="Spring"/>
    </bean>

</beans>

3、编写测试方法

import com.wu.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {

    public static void main(String[] args) {
        /*获取Spring的上下文对象*/
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        /*我们的对象现在都在Spring中管理,我们要使用直接去取出来就可以*/
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.toString());
    }
}
  • Hello对象是谁创建的?

    hello对象是由Spring创建的

  • Hello对象的属性是怎么设置的?

​ hello的属性由xml文件进行

这个过程就是控制反转:

控制:传统应用程序的对象是由程序本身控制创建的,使用Sring后,对象是由Spring来创建的。

反转:程序本身不创建对象,而是变成被动的接收对象。

依赖注入:就是利用set方法来进行注入。

IOC是一种编程思想,由主动编写对象改为被动的接收。

总结一句话:在Spring框架中,对象由Spring来创建,管理和装配。如果想要实现不同的操作,只需在xml配置文件中进行修改。

04 IOC创建对象的方式

1、如果没有特殊说明,bean使用无参构造创建对象

来看以下的代码,首先创建一个pojo类:

package com.wu.pojo;

public class User {
    
    private String name;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void show(){
        System.out.println("name="+name);
    }
    public User(){
        System.out.println("User的无参构造");
    }
}

可以看到我们引入了一个无参构造(当然也可以去掉)。接着再配置一个beans,并在test中编写一个测试类,运行结果如下

<?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="user" class="com.wu.pojo.User">
        <property name="name" value="某某"/>
    </bean>
</beans>
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User)context.getBean("user");
        user.show();
    }
}

那么,如果我们引入的不是无参构造而是有参构造,会发生什么?结果如下:

可以看到,在没有进行特殊操作时,class类在创建对象时调用的是对象的无参构造类。但如果一定要用到有参构造器,那该如何操作呢?

2、假设我们要使用有参构造创建对象,方法有以下三种

  • 利用下标进行赋值

       <bean id="user" class="com.wu.pojo.User">
            <constructor-arg index="0" value="某某"/>
        </bean>
    
    public User(String name){
        System.out.println(name+"的无参构造");
        this.name=name;
    }
    
  • 通过类型创建(不推荐使用)

        <bean id="user3" class="com.wu.pojo.User">
            <constructor-arg type="java.lang.String" value="wutong"/>
        </bean>
    

    这种方式的弊端在于,如果实体类中有2个类型一样的变量,如姓名和住址都是String,这个时候就容易出错。

  • 利用参数名赋值创建(推荐)

        <!--
           第三种,利用变量名赋值来进行创建(推荐)
        -->
        <bean id="user3" class="com.wu.pojo.User">
            <constructor-arg name="name" value="wutong"/>
        </bean>
    

    注:这里我提出一个问题,那就是这个user对象究竟是什么时候创建的。狂神认为对象在:

  User user = (User)context.getBean("user1");

​ 已经创建了。但我试着做了如下操作发现了一些问题,如果我们试着不修改原有的bean,而是增加一个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 id="user" class="com.wu.pojo.User">
        <property name="name" value="某某"/>
    </bean>

    <bean id="user1" class="com.wu.pojo.User">
        <constructor-arg index="0" value="某某"/>
    </bean>
</beans>
package com.wu.pojo;
/*对象去掉无参构造器*/
public class User {

    private String name;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void show(){
        System.out.println("name="+name);
    }
/*    public User(){
        System.out.println("User的无参构造");
    }*/
    public User(String name){
        System.out.println(name+"的无参构造");
        this.name=name;
    }
}

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User)context.getBean("user1");
        user.show();
    }
}

​ 同样的错误,和前面探讨解决有参构造时的错误一样,spring报错没有默认(无参)构造体。如果是第二句报错就很没逻辑,新建一个User对象是不会报这种错误的,然后拿到bean时我甚至都不是拿的那个无参bean而是有参bean,从逻辑上来讲有参bean的创建是没有问题的,此时回到bean.xml文件中就会发现IDEA提示在那个无参的bean中class有问题。

​ 因此我认为,bean.xml中的对象应当是在执行完:

 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

​ 之后就已经创建了。而所谓的getBean方法正如其表面含义,是拿到bean.xml文件中一大堆bean对象中的其中一个而已。只要bean.xml中有一个bean的写法有毛病,那整个全部对象的创建就都会有问题。

​ 总结:作为一名新手,如果试图规避掉这样的错误,那最好的办法就是在对象Pojo类中同时新建有参无参两种构造体。但这种做法无疑将会掩盖掉可能存在的代码业务上的一些问题。如何取舍,需视具体情况而定。

05 Spring配置

5.1、别名

<!--别名,如果添加了别名,也可以使用别名获取到这个对象-->
<alias name="user" alias="newuser"/>

5.2、Bean配置

<!--
id:bean的唯一标识符,也就是相当于我们学习过的变量名
class:bean所对应的全限定名(包名+类名)
name:别名,而且name可以同时取多个别名
scope:作用域
···
-->
    <bean id="user2" class="com.wu.pojo.User" name="userB user3,u2;u3">
        <property name="name" value="李某某"/>
    </bean>

5.3、import

这个import,一般用于团队开发使用,它可以将多个配置文件,导入合并成一个。

假设项目中有多人进行开发每个人都负责不同类的开发,不同的类需要注册在不同的bean中,因此可以利用import在一个xml中将所有人的配置文件都导入合并到一个总的。

  • applicationContext.xml

        <import resource="beans.xml"/>
        <import resource="beans2.xml"/>
        <import resource="beans3.xml"/>
    

    需要使用的时候直接用总的就行。

06 依赖注入

6.1 构造器注入

前面讲过了

6.2 Set方式注入(重点)

  • 依赖注入:Set注入!
    • 依赖:bean对象的创建依赖于容器
    • 注入:bean对象中的所有属性,由容器来注入!

【环境搭建】

  1. 复杂类型

    package com.wu.pojo;
    
    public class Address {
    
        private String address;
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    }
    
    
  2. 真实测试对象

    package com.wu.pojo;
    
    import java.util.*;
    
    public class Student {
    
        private String name;
        private Address address;
        private String[] books;
        private List<String> hobby;
        private Map<String,String> marks;
        private Set<String> games;
        private Properties info;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Address getAddress() {
            return address;
        }
    
        public void setAddress(Address address) {
            this.address = address;
        }
    
        public String[] getBooks() {
            return books;
        }
    
        public void setBooks(String[] books) {
            this.books = books;
        }
    
        public List<String> getHobby() {
            return hobby;
        }
    
        public void setHobby(List<String> hobby) {
            this.hobby = hobby;
        }
    
        public Map<String, String> getMarks() {
            return marks;
        }
    
        public void setMarks(Map<String, String> marks) {
            this.marks = marks;
        }
    
        public Set<String> getGames() {
            return games;
        }
    
        public void setGames(Set<String> games) {
            this.games = games;
        }
    
        public Properties getInfo() {
            return info;
        }
    
        public void setInfo(Properties info) {
            this.info = info;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", address=" + address +
                    ", books=" + Arrays.toString(books) +
                    ", hobby=" + hobby +
                    ", marks=" + marks +
                    ", games=" + games +
                    ", info=" + info +
                    '}';
        }
    }
    
    
  3. 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 id="student" class="com.wu.pojo.Student">
               <!--第一种,普通值注入,value-->
               <property name="name" value="wt"/>
           </bean>
    </beans>
    
  4. 测试类

    public class test1 {
    
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
            Student student = (Student) context.getBean("student");
            System.out.println(student.getName());
        }
    
    }
    

【八种注入方式】(重点)

<?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="address" class="com.wu.pojo.Address">
             <property name="address" value="江西南昌"/>
       </bean>
       <bean id="student" class="com.wu.pojo.Student">
           <!--第一种,普通值注入,value-->
           <property name="name" value="wt"/>
           <!--第二种,bean注入,使用ref引用-->
           <property name="address" ref="address"/>
           <!--第三种,数组注入,使用array标签-->
           <property name="books">
               <array>
                   <value>《三国演义》</value>
                   <value>《西游记》</value>
                   <value>《红楼梦》</value>
                   <value>《水浒传》</value>
               </array>
           </property>
           <!--第四种,list注入,使用list标签-->
           <property name="hobby">
               <list>
                   <value>打游戏</value>
                   <value>看电影</value>
                   <value>钓鱼</value>
               </list>
           </property>
           <!--第五种,map注入,使用map标签-->
           <property name="marks">
               <map>
                   <entry key="math" value="100"/>
                   <entry key="english" value="90"/>
                   <entry key="chinese" value="85"/>
               </map>
           </property>
           <!--第六种,set注入,使用set标签-->
           <property name="games">
               <set>
                   <value>星际争霸</value>
                   <value>战舰世界</value>
               </set>
           </property>
           <!--第七种,properties注入,使用props标签-->
           <property name="info">
               <props>
                  <prop key="学号">2211936</prop>
                  <prop key="性别">男</prop>
                  <prop key="school">东北大学</prop>
               </props>
           </property>
           <!--第八种,null注入,使用null标签-->
           <property name="wife">
               <null/>
           </property>
       </bean>
</beans>

6.3 拓展方式注入

除开上面所述的注入方法外,还有两种注入方式:P命名空间注入和C命名空间注入

6.3.1 P命名注入

P命名注入使用,借助实体类中的set方法实现。

xml配置:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--使用p命名空间需要引入上面的xmlns:p-->
    <!--p:property-->
    <bean id="user" class="com.wu.pojo.User"
          p:name="wt" p:age="24" />

</beans>

测试方法:(这里使用一种新的方法从容器中取出对象,在getBean的同时规定反射对象的类型)(C命名空间测试方法类似这个)

    @Test
    public void test2(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        /*User user = (User)context.getBean("user");*/
        User user = context.getBean("user", User.class);
        System.out.println(user.toString());
    }

6.3.2 C命名注入

C命名注入使用,借助实体类中的有参构造器实现。(回到上面的问题,要想不报错,有参无参构造器都要建)

xml配置:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--使用c命名空间同样需要先引入"xmlns:c"-->
    <!--c:construct-args-->
    <!--c命名空间使用有参构造器进行注入-->
    <!--p命名空间使用set方法进行注入-->
    <bean id="user2" class="com.wu.pojo.User" c:name="wt" c:age="24"/>

</beans>

测试方法:

    @Test
    public void test2(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        /*User user = (User)context.getBean("user2");*/
        User user = context.getBean("user2", User.class);
        System.out.println(user.toString());
    }

注:P命名和C命名不能直接使用,需要导入xml约束

xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"

6.4 Bean的作用域

  1. 单例模式(Spring默认机制)

        <!--使用scope约束新生成的对象,其中singleton是默认设置-->
        <!--这样取出来的对象都是同一个-->
        <bean id="user2" class="com.wu.pojo.User" c:name="wt" c:age="25" scope="singleton"/>
    
     @Test
        public void test1(){
            ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
            User user2 = context.getBean("user2", User.class);
            User user = context.getBean("user2",User.class);
            System.out.println(user2==user);
            System.out.println(user.hashCode());
            System.out.println(user2.hashCode());
        }
    

    可以看到在单例模式下取出来的对象都是同一个。

  2. 原型模式:每次从容器中get的时候都会产生一个新对象!

    <bean id="user3" class="com.wu.pojo.User" scope="prototype">
            <property name="name" value="wt"/>
            <property name="age" value="25"/>
    </bean>
    
     @Test
        public void test2(){
            ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
            User user2 = context.getBean("user3", User.class);
            User user = context.getBean("user3",User.class);
            System.out.println(user2==user);
            System.out.println(user.hashCode());
            System.out.println(user2.hashCode());
        }
    

  1. 其余的request、session、application,这些个只能在web开发中使用到!

07 Bean的自动装配

  • 自动装配是Spring满足bean依赖的一种方式!
  • Spring会在上下文中自动寻找,并自动给bean装配属性!

在Spring中有三种装配的方式

  1. 在xml中显示的配置
  2. 在java中显示的配置
  3. 隐式的自动装配bean【重要】

7.1 测试环境

环境搭建:一个人有两个宠物(建立三个实体类:Cat,Dog,People)

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

7.2 ByName自动装配

    <!--
     byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的beanid
    -->
    <bean id="people1" class="com.wu.pojo.People" autowire="byName">
        <property name="name" value="wut"/>
    </bean>

7.3 ByType自动装配

    <!--
     byType:会自动在容器上下文中查找,和自己对象set方法后面的对象相同类型的bean
    -->
    <bean id="people2" class="com.wu.pojo.People" autowire="byType">
        <property name="name" value="wut"/>
    </bean>

注:需要注意的是使用byType实现自动装配时,可以省去bean中的id,但要求同类型的对象只能由一个,适合于复用较多的重复对象。

使用byName实现自动装配时,由于针对的是beanid,因此不存在同类型对象的数量限制,但要求beanid必须存在。

7.4 使用注解实现装配(重点)

要使用注解须知:

1、导入约束,context

2、 配置注解的支持:Core Technologies (spring.io)

<?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方式上使用,但需要确保相关命名一致。

使用Autowired就可以不用编写Set方法,前提是这个自动装配的属性在IOC(Spring)容器要存在,且符合名字byName!

科普:

@Nullable 字段标记了这个注解,说明这个字段可以为null
//如果显式的定义了Autowired的required属性为false,说明这个对象可以为null,否则不允许为空
@Autowired(required = false) 

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

测试代码:

public class People {

    @Autowired
    private Cat cat;

    @Autowired
    private Dog dog;

    @Autowired(required = false)
    private String name;
    

@Qualifier(value="xxx")

如果@Autowired自动装配的环境比较复杂,自动装配无法通过一个注解【@Autowired】完成的时候,我们可以使用@Qualifier(value="xxx")去配置@Autowired的使用,指定一个唯一的bean对象注入!

    @Autowired
    @Qualifier(value = "cat222")
    private Cat cat;

    @Autowired
    @Qualifier(value = "dog111")
    private Dog dog;

    @Autowired(required = false)
    private String name;

@Resource(name="xxx")

  @Resource
    private Cat cat;

    @Resource
    private Dog dog;

小结:

@Resource和@Autowired的区别:

  • 都是用来自动装配的,都可以放在属性字段上
  • @Autowired通过byname的方式实现
  • @Resource默认通过byname的方式实现,如果找不到名字,则通过bytype来实现!如果两个都找不到的情况下,就会报错(无唯一对象)【常用】
  • 执行顺序不同:
    • @Autowired找byname
    • @Resource先找byname,如果找不到再找bytype

08 使用注解开发

在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>

8.1 bean

8.2 属性如何注入

@Component
public class User {

    /*public String name = "吴同";*/

    /*相当于
    <property name="name" value="吴同"/>
    * */
    @Value("张三")
    private String name;

    public void setName(String name){
        this.name = name;
    }

    public String getname(){
        return this.name;
    }
}

8.3 衍生的注解

@Component 有几个衍生注解,在web开发中,会按照mvc三层架构分层!

  • dao 【@Repository】
  • service 【@Service】
  • controller 【@Controller】

这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配bean

8.4 自动装配配置

@Autowired :自动装配通过类型,名字
  如果Autowired不能唯一自动装配上属性,则需要通过@Qualifier(value="xxx")
@Nullable    字段标记了这个注解,说明这个字段可以为null
@Resource : 自动装配通过名字,类型。

8.5 作用域

@Scope("prototype")

比如:singleton 单例模式等

8.6 小结

xml 与 注解:

  • xml更加万能,适合与任何场合!维护简单方便
  • 注解 不是自己的类不能使用,维护相对复杂

xml与注解最佳实践:

  • xml用来管理bean
  • 注解只负责完成属性的注入
  • 在使用过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持
    <!--指定需要扫描的包,这个包下的注解就会生效-->
    <context:component-scan base-package="com.wu"/>
    <context:annotation-config/>

09 使用Java的方式配置Spring

现在要完全不适用Spring的xml配置了,全权交给Java来做

JavaConfig是Spring的一个子项目,在Spring4之后,它成了一个核心功能!

1、pojo类

//这里这个注解的意思就是说明这个类被spring接管了,注册到了容器中
@Component
public class User {

    private String name;

    public String getName() {
        return name;
    }

    //注入值
    @Value("张三")
    public void setName(String name) {
        this.name = name;
    }

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

2、新建config

//这个也会被spring容器托管,注册到容器中,因为他本来就是一个@Component
// @Configuration代表这是一个配置类,类似于之前看的beans.xml
@Configuration
@ComponentScan("com.wu.pojo")
@Import(WuConfig2.class)
public class WuConfig {

    //注册一个Bean,就相当于我们之前写的一个bean标签
    //这个方法的名字,就相当于bean标签中的id属性
    //这个方法的返回值,就相当于bean标签中的class属性
   @Bean
    public User getUser(){
         return new User();//就是返回要注入到bean的对象!
   }

}

3、测试文件

public class test {

    public static void main(String[] args) {
        //若完全使用了配置类方式去做,我们就只能通过AnnotationConfig 上下文来获取容器,通过配置类的class对象加载
        ApplicationContext context = new AnnotationConfigApplicationContext(WuConfig.class);
        User getuser = (User)context.getBean("user");
        System.out.println(getuser.getName());
    }

}

这种纯Java的配置方式,在springBoot中随处可见!

10 代理模式

为什么要学习代理模式?因为这就是springAOP的底层实现!【SpringAOP和SpringMVC】

代理模式的分类:

  • 静态代理
  • 动态代理

10.1 静态代理

角色分析:

  • 抽象角色:一般会使用接口或者抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
  • 客户:访问代理对象的人!

代码步骤:

  1. 接口

    /*
    * 租房的接口,抽象
    * */
    public interface Rent {
    
        public void rent();
    
    }
    
  2. 真实角色

    /*
    * 房东 真实角色
    * */
    public class Host implements Rent{
    
        @Override
        public void rent() {
            System.out.println("房东要出租房子!");
        }
    }
    
  3. 代理角色

    public class Proxy implements Rent{
    
        private Host host;
    
        public Proxy() {
        }
    
        public Proxy(Host host) {
            this.host = host;
        }
    
        @Override
        public void rent() {
            seeHouse();
            host.rent();
            signContract();
            fare();
        }
    
        /*
        * 看房
        * */
        public void seeHouse(){
            System.out.println("中介带你看房");
        }
    
        /*签合同*/
        public void signContract(){
            System.out.println("签租赁合同");
        }
    
        /*收中介费*/
        public void fare(){
            System.out.println("中介收取手续费");
        }
    
    
    }
    
  4. 客户端访问代理角色

        public static void main(String[] args) {
            /*房东要租房子*/
            Host host = new Host();
            /*代理,中介帮房东出租房子,但是同时代理角色会有一些自身的附属操作*/
            Proxy proxy = new Proxy(host);
            /*不用面对房东,直接找中介就行*/
            proxy.rent();
    
        }
    

代理模式的好处:

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
  • 公共业务就交给代理角色!实现了业务的分工。同时降低了代码的耦合性。
  • 公共业务发生扩展的时候,方便集中管理!

缺点:

  • 一个真实角色就会产生一个代理角色;代码量会翻倍~开发效率会降低。

10.2 加深静态代理的理解

代码:

  • 虚拟对象:

    public interface UserService {
    
        public void add();
    
        public void delete();
    
        public void update();
    
        public void query();
    }
    
  • 真实对象:

    public class UserServiceImpl implements UserService{
    
        @Override
        public void add() {
            System.out.println("新建了一个用户");
        }
    
        @Override
        public void delete() {
            System.out.println("删除了一个用户");
        }
    
        @Override
        public void update() {
            System.out.println("编辑了一个用户");
        }
    
        @Override
        public void query() {
            System.out.println("查询了一个用户");
        }
    }
    

    注意,如果此时有要求需要在进行上述操作时增加日志,该如何运作?

    第一,可以考虑在原真实服务类中逐一添加日志方法,但这不好!如果此类操作较多将导致代码工作量急剧增加。有人考虑可以将日志方法单独抽出来。但如果系统内部功能耦合比较严重的话,可能在意想不到的地方突然调用这个方法。简而言之就是不建议破坏系统的原有业务代码。

    第二,此时可以考虑使用代理思路,即原系统业务代码不变更,但新增一个代理类,在代理类中新建日志方法。

  • 代理对象:

    public class UserServiceProxy implements UserService{
    
        private UserService userService;
    
        public void setUserService(UserService userService) {
            this.userService = userService;
        }
    
        @Override
        public void add() {
           operationLog("add");
           userService.add();
        }
    
        @Override
        public void delete() {
            operationLog("delete");
            userService.delete();
        }
    
        @Override
        public void update() {
            operationLog("update");
            userService.update();
        }
    
        @Override
        public void query() {
            operationLog("query");
            userService.query();
        }
    
        //日志方法
        private void operationLog(String msg){
            System.out.println("[Debug]使用了"+msg+"方法");
        }
    }
    

    ​ 为了简化代码量,我们可以使得代理类的公开方法与原来的方法保持一致。这样在测试类进行修改时,只需要增加一行方法注入,将原来的方法调用改为代理类方法就可以实现新的方法,在不影响原业务逻辑的情况下加上自己的业务需求。

10.3 动态代理

动态代理的底层都是反射

  • 动态代理和静态代理角色一样
  • 动态代理的类是动态生成的,不是我们直接写好的!
  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
    • 基于接口 --- JDK动态代理【本次学习使用】【需要注意!】
    • 基于类:cglib
    • java字节码:javassist

需要了解两个类:Proxy,代理 InvocationHandler:调用处理程序

动态代理的好处:

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
  • 公共业务就交给代理角色!实现了业务的分工。同时降低了代码的耦合性。
  • 公共业务发生扩展的时候,方便集中管理!
  • 一个动态代理类代理的是一个接口,一般就是一类业务
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口

11 AOP

11.1 什么是AOP

AOP(Aspect Oriented Programming)意味:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP(面向对象)的延续,是软件开发中的一个热点,也是spring框架中的一个重要内容,是函数式编程的一种衍生范型,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。

11.2 AOP在spring中的作用

提供声明式事务:允许用户自定义切面

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等...
  • 切面(Aspect):横切关注点被模块化的特殊对象。即它是一个类。
  • 通知(Advise):切面必须要完成的工作。即它是类中的一个方法。
  • 目标(Target):被通知对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。
  • 切入点(PointCut):切面通知执行的"地点"的定义。
  • 连接点(JointPoint):与切入点匹配的执行点

SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Adcive:

通知类型 连接点 实现接口
前置通知 方法前 org.springframework.aop.MethodBeforeAdvice
后置通知 方法后 org.springframework.aop.AfterReturningAdvice
环绕通知 方法前后 org.aopalliance.
异常抛出通知 方法抛出异常 org.springframework.aop.ThrowsAdvice
引介通知 类中新增方法属性 org.springframework.aop.IntroductionInterceptor

即Aop在不改变原业务代码的情况下,去增加新的功能

11.3 使用spring实现AOP

【重点】使用AOP织入,需要导入一个依赖包

<dependency>
     <groupId>org.aspectj</groupId>
     <artifactId>aspectjweaver</artifactId>
     <version>1.9.19</version>
</dependency>

方式一:使用Spring的API接口调用

首先仍然新建1个service服务,包括1个接口类和1个实现类

package com.wu.service;

public interface UserService {

    public void add();

    public void delete();

    public void update();

    public void select();

}

package com.wu.service;

public class UserServiceImpl implements UserService{

    @Override
    public void add() {
        System.out.println("增加了一个用户");
    }

    @Override
    public void delete() {
        System.out.println("删除了一个用户");
    }

    @Override
    public void update() {
        System.out.println("修改了一个用户");
    }

    @Override
    public void select() {
        System.out.println("查询了一个用户");
    }
}

其次新建两个日志类:BeforeLog和AfterLog,并分别实现MethodBeforeAdvice和AfterReturningAdvice接口

package com.wu.log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class BeforeLog implements MethodBeforeAdvice {

    //method:要执行的目标对象的方法
    //objects:参数(看源码可知这个一般叫args)
    //o:目标对象(同上,这里一般是target)
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行了");
    }
}

package com.wu.log;

import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class AfterLog implements AfterReturningAdvice {


    //returnValue:返回值

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了"+method.getName()+"方法,返回结果为"+returnValue);
    }
}

最后配置xml文件,实现bean注册以及aop注册

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--注册bean-->
    <!--此处需注意,注册bean应当使用实体对象,而在前面的调用中,应当使用spring代理实体对象的接口类-->
    <bean id="userService" class="com.wu.service.UserServiceImpl"/>
    <bean id="beforeLog" class="com.wu.log.BeforeLog"/>
    <bean id="afterLog" class="com.wu.log.AfterLog"/>

    <!--方法一:使用原生Spring API接口-->
    <!--配置aop-->
    <aop:config>
        <!--切入点:expression:表达式,execution(要执行的位置!* * * * * ) 星星按顺序对应:修饰词(public)、返回值、类名、方法名、参数-->
        <aop:pointcut id="pointcut" expression="execution(* com.wu.service.UserServiceImpl.*(..))"/>

        <!--执行环绕增加-->
        <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>



</beans>

测试方法需要注意的是,在xml文件中我们注册的是实体对象,但在使用时需要给spring该实体类的代理接口,正如第10章在讲代理模式的时候说的,动态代理基于接口的方法!

import com.wu.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Mytest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //此处需注意
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }

}

方法二:使用自定义方式实现AOP【主要是切面定义】

此时新建的方法不需要实现MethodBeforeAdvice和AfterReturningAdvice等接口,只需要在配置文件中用spring的aop配置就行。

本质还是代理中的InvocationHandler和Proxy

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--注册bean-->
    <bean id="diy" class="com.wu.diy.Log"/>
    <bean id="userServiceImpl" class="com.wu.service.UserServiceImpl"/>

    <!--自定义切面-->
    <aop:config>
        <!--自定义切入点,ref是需要引入切面的类-->
        <aop:aspect ref="diy">
            <!--切入点-->
            <aop:pointcut id="point" expression="execution(* com.wu.service.UserServiceImpl.*(..))"/>
            <!--通知,如何插入方法中-->
            <aop:before method="before" pointcut-ref="point"/>
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>

</beans>

方式三:使用注解实现

package com.wu.diy;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class AnnotationPointCut {

    @Before("execution(* com.wu.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("=====方法执行前=====");
    }

    @After("execution(* com.wu.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("=====方法执行后=====");
    }
    
    //在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
    @Around("execution(* com.wu.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("*****环绕前*****");
        Signature signature = jp.getSignature();
        System.out.println(signature);
        Object proceed = jp.proceed();//执行方法
        System.out.println("*****环绕后*****");
        System.out.println(proceed);
    }

}

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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userServiceImpl" class="com.wu.service.UserServiceImpl"/>
    <bean id="annotationPointCut" class="com.wu.diy.AnnotationPointCut"/>

    <!--开启注解支持  JDK默认 false//cglib true-->
    <aop:aspectj-autoproxy proxy-target-class="false"/>

</beans>

运行结果,观察几条语句执行执行先后顺序!

12 整合Mybatis

步骤:

  1. 导入相关jar包
    • junit(测试)
    • mybatis
    • mysql数据库
    • spring相关的
    • aop织入包
    • mybatis-spring【new】
  2. 编写配置文件
  3. 测试

12.1 回忆mybatis

  1. 编写实体类
  2. 编写核心配置文件
  3. 编写接口
  4. 编写mapper.xml
  5. 测试

12.2 Mabits-Spring

1.编写数据源配置

2.sqlSessionFactory

3.sqlSessionTemplate

4.需要给接口加实现类【】

5.将自己写的实现类,注入到spring中

13 声明式事务

13.1 回顾事务

  • 把一组业务当成一个业务来做;要么都成功,要么都失败!
  • 事务在项目开发中,十分重要,涉及到数据的一致性问题,不能马虎!
  • 确保完整性和一致性;

事务ACID原则:

  • 原子性
  • 一致性
  • 隔离性
    • 多个业务可能操作同一个资源,防止数据损坏
  • 持久性
    • 事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久化写到存储器中!

13.2 spring中的事务管理

  • 声明式事务:AOP
  • 编程式事务:需要在代码中,进行事务的管理

思考:为什么事务?

  • 如果不配置事务,可能存在数据提交不一致的情况;
  • 如果我们不在spring中去配置事务,就需要在代码中手动配置事务!(使用try-catch捕获异常然后回滚)
  • 事务在项目开发中十分重要,涉及到数据的一致性和完整性问题,不容马虎!

14 总结

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2024年04月03日 0

暂无评论

推荐阅读
37TR5pANejWp