尚硅谷-Spring5学习笔记

Spring5

2022年7月20日记

文章目录

1. Spring5框架概述

  1. Spring是轻量级的开源的JavaEE框架。

  2. Spring可以解决企业应用开发的复杂性。

  3. Spring有两个核心部分:IOC和AOP

    1. IOC:控制反转,将创建对象的过程交由Spring进行管理。
    2. AOP:面向切面,在不修改源代码的基础上进行功能增强。
  4. Spring的特点:

    1. 方便解耦,简化开发:

      1. 通过Spring提供的IOC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。有了Spring,用户则不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,而是可以更加专注于应用层的开发。
    2. 支持AOP:

      1. 通过Spring提供的AOP功能,能够更方便进行面向切面的编程,许多不容易用传统OOP方式实现的功能,都可以通过AOP轻松应付。
    3. 方便程序的测试:

      1. 可以用非容器依赖的编程方式进行几乎所有的测试工作,在Spring里,测试不再是昂贵的操作,而是随手可做的事情。例如:Spring对Junit4的支持,可以通过使用@Test注解方便测试Spring程序。
    4. 方便集成各种优秀的框架:

      1. Spring不排斥各种优秀的开源框架,相反,Spring还可以降低各种框架的使用难度,Spring提供了对各种优秀框架的直接支持。比如:Struts、Hibernate、Hessian、Quartz等。虽然现在已经不用这些了。😊
    5. 降低JavaE API的使用难度:

      1. Spring对很多难用的JavaEE API都提供了一层薄薄的封装,比如对:JDBC、JavaMail、远程调用等。通过Spring的简易封装,使得这些JavaEE API的使用难度大为降低。
    6. Java源码是经典的学习范例:

      1. Spring的源码设计精妙、结构清晰、匠心独运,处处体现着大师对Java设计模式的灵活运用,以及对Java技术的高深造诣。Spring框架的源码无疑是Java技术的最佳实践范例。如果想在短时间内迅速提高自己的Java技术水平和应用层开发水平,学习和研究Spring源码将会使你收获到意想不到的效果。净扯淡,我能吃透源码,我还干应用层开发?😊

2. Spring5入门案例

2.1 使用IDEA创建一个Maven项目

image.png
image.png

2.2 引入Spring5相关依赖

注意:会用Maven就没必要再跑到官网下载Spring5框架,有工具不用,你脑残吗?

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--引入spring5的依赖,需要分别引入spring中的jar包,spring包含5个核心部分jar包-->
    <!--1.spring-context部分-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.6.RELEASE</version>
    </dependency>
    <!--2.spring-core部分-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.2.6.RELEASE</version>
    </dependency>
    <!--3.spring-expression部分-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-expression</artifactId>
      <version>5.2.6.RELEASE</version>
    </dependency>
    <!--4.spring-aop部分-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.6.RELEASE</version>
    </dependency>
    <!--5.spring-beans部分-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>5.2.6.RELEASE</version>
    </dependency>
  </dependencies>

2.3 创建配置文件

创建bean.xml配置文件

2.4 创建实体类

创建一个普通Java类

public class User
{
    public void show(){
        System.out.println("show~");
    }

    @Override
    public String toString()
    {
        return "I'm"+getClass().getName();
    }
}

2.5 配置User对象的创建

在bean.xml配置文件中,创建User对象

<?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">

    <!--配置User对象的创建-->
    <bean id="user" class="com.lonelysunset.pojo.User"></bean>
</beans>

2.6 使用Junit4测试结果

public class AppTest 
{
    @Test
    public void test1(){
        // 1.加载Spring配置文件
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        // 2.获取配置文件创建的对象
        User user = context.getBean("user", User.class);
        // 3.输出对象
        System.out.println(user);// I'mcom.lonelysunset.pojo.User
        // 4.调用对象中的方法
        user.show();// show~
    }
}

3. IOC

3.1 什么是IOC?

  1. IOC:即:控制反转,将对象创建和对象之间的调用过程,交由Spring进行管理。
  2. 使用IOC的目的是为了解耦,即:降低程序之间的耦合度,简单来说,就是降低A类和B类之间的耦合度。
  3. 刚刚介绍的入门案例,就是利用了IOC容器技术实现。

3.2 IOC底层原理技术

IOC底层主要使用了:xml解析、工厂模式、反射进行实现

3.3 引出IOC

假设程序中有UserService、UserDao两个类,需求是在UserService类中调用UserDao类中的方法。

实现如下:普通类与类之间的调用方式实现

public class UserDao{
  public void show(){
    System.out.println(123);
  }
}
public class UserService{
  public static void main(String[] args){
    UserDao userDao = new UserDao();
  	userDao.show();// 123
  }
}

分析:按照以上方式实现,存在高耦合度的问题,当UserDao的类路径发生变化时,所有与UserDao产生关联(耦合)的类,都需要去更改,从而导致高耦合,很明显,这不是程序所追求的极致状态。

对以上的假设进行优化。

实现如下:通过使用工厂模式实现。

public class UserDao{
  public void show(){
    System.out.println(123);
  }
}
public class UserFactor{
  public static UserDao getUserDao(){
    return new UserDao;
  }
}
public class UserService{
  public static void main(String[] args){
    UserDao userDao = UserFactor.getUserDao();
		userDao.show();// 123
  }
}

分析:通过使用工厂模式优化后,耦合度有一定的降低,但依然没有达到程序所追求的极致状态。补充说明:任何程序中的类与类之间的关系都不可能做到完全的无耦合,但可以通过程序去实现较低的耦合

对以上的假设再次进行优化。

实现如下:通过使用xml解析、工厂模式、反射实现

  1. 创建bean.xml文件,用于配置User对象的创建。
<?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">

    <!--配置User对象的创建-->
    <bean id="dao" class="com.lonelysunset.UserDao"></bean>
</beans>
  1. 创建工厂方法。
class UserFactor{
  public static UserDao getDao(){
    String classValue = class属性值;// 1.xml解析 //TODO 这里有一点不理解哈,如何解析xml并获取到指定类对象的全类名路径?听说是使用dom4j技术?等以后有机会了解再说吧。
    Class clazz = Class.forName(classValue);// 2.通过反射创建对象
    return (UserDao)cl azz.newInstance();
  }
}

分析:通过使用xml解析、工厂模式、反射进行优化后,耦合度能够更进一步降低,并且依然达到了Spring底层IOC的低耦合程度。因为IOC底层使用的技术就是xml解析、工厂模式、反射。😊挺搞笑的,什么叫耦合度低?就是别人大佬写的,大佬说的,Spring说使用xml解析、工厂模式、反射就是最低的,那就是最低的。呵呵

3.4 分析IOC接口

  1. IOC思想基于IOC容器完成,IOC容器底层就是对象工厂。
  2. Spring提供了IOC容器的两种实现方式。即:两个接口。
  3. BeanFactory:即IOC容器的基本实现,其主要用于提供给Spring内部使用的接口,并不向开发人员提供。
// 当使用BeanFactory加载配置文件时,不会去创建对象,而是当我们在获取对象时,才会去创建对象
BeanFactory context = new ClassPathXmlApplicationContext("bean1.xml");
  1. ApplicationContext:BeanFactory接口的子接口,提供了更多更强大的功能,一般主要由开发人员进行使用。
// 当使用ApplicationContext加载配置文件时,会将所有的配置文件中的对象进行创建
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
  1. ApplicationContext接口有实现类,其主要实现类为FileSystemXmlApplicationContext和ClassPathXmlApplicationContext,如下所示:
    image.png
  2. 当使用FileSystemXmlApplicationContext实现类获取xml配置文件时,需要传入xml配置文件的全路径。
//TODO 实际测试中,这里会报错,不知道啥原因,具体没有往下探究,可能是路径写错了?应该不会啊😓。
ApplicationContext context = new FileSystemXmlApplicationContext("/Users/wufuqiang/IdeaProjects/spring5/src/main/resources/bean1.xml");
  1. 当使用ClassPathXmlApplicationContext实现类获取xml配置文件时,需要传入xml配置文件的名称即可。
// 这种方式,应该是测试的时候使用最多的,瞧瞧多方便啊,直接获取xml配置文件名称就行
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");

3.5 分析IOC中的Bean管理

3.5.1 什么是Bean管理?

Bean管理,一般指的是两个操作,即:Spring创建对象和Spring注入属性。

3.5.2 Bean管理操作由两种方式

  1. 基于xml配置文件方式实现。
  2. 基于注解方式实现。(推荐方式,也是后期开发企业项目的最佳方式)

3.5.3 IOC操作Bean管理-基于xml方式

3.5.3.1 基于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">

    <!--配置User对象的创建-->
    <bean id="user" class="com.lonelysunset.pojo.User"></bean>
</beans>

分析<bean id="" class=""></bean>标签:

  • 在Spring配置文件中,使用bean标签,并在标签中添加对应的属性,从而实现对象的创建。
  • 在bean标签中,有很多属性,以下着重介绍常用的属性。
  • id属性:即表示唯一标识。
  • class属性:即表示类的全路径,比如com.solitarysunset.pojo.User
  • 创建对象的时候,默认是执行无参构造完成对象创建的。
  • 如果我们在User中创建有参构造,则系统将不再为User分配无参构造,此时再通过xml创建对象,则会报错。
3.5.3.2 基于xml方式注入属性

DI:即IOC的一种具体实现,称之为依赖注入,主要用于注入属性,其注入属性的前提必须在创建对象的基础上完成。

3.5.3.2.1 第一种注入方式:使用set方法进行注入
  1. 创建类,定义属性和对应的set方法。
/**
 * 演示使用set方式进行注入属性
 * @Author wufuqiang
 * @Date 2022/7/25
 * @Version 1.0
 * @Description:
 */
public class Book
{
    private String name;
    private String author;

    public void setAuthor(String author)
    {
        this.author = author;
    }

    public void setName(String name)
    {
        this.name = name;
    }
}
  1. 在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="book" class="com.lonelysunset.pojo.Book">
        <!--使用property完成属性注入
            name:类里面的属性名称
            value:向属性注入值
        -->
        <property name="author" value="老子"/>
        <property name="name" value="道德经"/>
    </bean>
</beans>
3.5.3.2.2 第二种注入方式:使用构造器进行注入
  1. 创建类,定义属性和有参构造。
/**
 * 演示使用构造实现注入
 * @Author wufuqiang
 * @Date 2022/7/25
 * @Version 1.0
 * @Description:
 */
public class Cat
{
    private String name;
    private Integer age;

    public Cat(String name, Integer age)
    {
        this.name = name;
        this.age = age;
    }
}
  1. 在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="cat" class="com.lonelysunset.pojo.Cat">
        <constructor-arg name="name" value="小红"/>
        <constructor-arg name="age" value="18"/>
    </bean>
</beans>

3.5.3.3 使用p名称空间注入属性(了解即可)

  1. 使用p名称空间注入,可以简化基于xml配置方式。
  2. 第一步,添加p名称空间在配置文件中。
<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>
  1. 第二步,在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"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="book" class="com.lonelysunset.pojo.Book" p:name="金瓶梅" p:author="未知"></bean>
</beans>

3.5.4 IOC操作Bean管理-在xml中注入其它类型属性

  1. 设置null值。
  2. 设置包含特殊符号的属性值。
<?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="book" class="com.lonelysunset.pojo.Book">
        <property name="name">
            <!--设置name为null值-->
            <null/>
        </property>
        <property name="author">
            <!--属性值中设置包含特殊符号的内容,只需要将内容写在<![CDATA[内容]]>即可-->
            <value><![CDATA[~无名氏~]]></value>
        </property>
    </bean>

</beans>
3.5.4.1 在xml中注入属性-外部bean

假设已有UserDao、UserService两个接口,以及UserDaoImpl、UserServiceImpl实现类,要求在UserServiceImpl中注入UserDao,调用UserServiceImpl中的add方法时,同时能够调用UserDao中的sub方法。

  1. 创建UserDao、UserDaoImpl。
public interface UserDao
{
    void sub();
}
public class UserDaoImpl implements UserDao
{
    @Override
    public void sub()
    {
        System.out.println("sub.......");
    }
}
  1. 创建UserService、UserServiceImpl,并在UserServiceImpl声明UserDao类型的属性。
public interface UserService
{
    void add();
}
public class UserServiceImpl implements UserService
{
    private UserDao userDao;
  
    // 这里涉及到了多态,形参类型是UserDao,但是实参应该是UserDao接口的实现类,所以在xml注入的时候,实际注入的应该是UserDaoImpl实现类,而不是把接口注入进去
  	public void setUserDao(UserDao userDao)
    {
        this.userDao = userDao;
    }
  
    @Override
    public void add()
    {
        System.out.println("add......");
        // 这里就是动态绑定咯,编译类型是UserDao,运行类型是UserDaoImpl,所以调用的是实现类中的方法
        userDao.sub();
    }
}
  1. 使用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">
    <!--创建UserServiceImpl和UserDaoImpl对象-->
    <bean id="userServiceImpl" class="com.lonelysunset.pojo.UserServiceImpl">
        <!--
            注入userDao对象
            name属性:类里面属性名称。
            ref属性:创建userDao对象bean标签的id值。
        -->
        <property name="userDao" ref="userDaoImpl"/>
    </bean>
    <bean id="userDaoImpl" class="com.lonelysunset.pojo.UserDaoImpl"></bean>
</beans>
  1. 测试结果。
@Test
public void test4(){
  ApplicationContext context = new ClassPathXmlApplicationContext("bean6.xml");
  // 接收类型使用其父类类型接收,还是多态,没什么可说的
  UserService userServiceImpl = (UserService) context.getBean("userServiceImpl");
  userServiceImpl.add();
}
// 结果:
add......
sub.......
3.5.4.2 在xml中注入属性-内部bean

不建议使用,首先呢,不常用,因为这种方式反人类,耦合性太高,了解下即可

  1. 适用于一对多关系,比如部门和员工之间的关系,一个部门有多个员工,一个员工只能属于一个部门。
  2. 创建Dept和Emp实体类。
public class Dept
{
    private String dname;

    public void setDname(String dname)
    {
        this.dname = dname;
    }

    @Override
    public String toString()
    {
        return "Dept{" + "dname='" + dname + '\'' + '}';
    }
}
public class Emp
{
    private String ename;
    private String gender;
    // 我觉得这种注入方式就已经很反人类了,所以在xml中使用内部bean,一样反人类。
    private Dept dept;

    public void setEname(String ename)
    {
        this.ename = ename;
    }

    public void setGender(String gender)
    {
        this.gender = gender;
    }

    public void setDept(Dept dept)
    {
        this.dept = dept;
    }

    @Override
    public String toString()
    {
        return "Emp{" + "ename='" + ename + '\'' + ", gender='" + gender + '\'' + ", dept=" + dept + '}';
    }
}
  1. 使用xml方式创建实体类对象,并完成对象属性内部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="emp" class="com.lonelysunset.bean.Emp">
        <!--设置两个普通属性-->
        <property name="ename" value="Lucy"/>
        <property name="gender" value=""/>
        <!--设置对象类型属性-->
        <property name="dept">
            <bean id="dept" class="com.lonelysunset.bean.Dept">
                <property name="dname" value="超神学院"/>
            </bean>
        </property>
    </bean>
</beans>
  1. 测试结果。
@Test
public void test5(){
  ApplicationContext context = new ClassPathXmlApplicationContext("bean7.xml");
  Emp emp = (Emp) context.getBean("emp");
  System.out.println(emp);// Emp{ename='Lucy', gender='女', dept=Dept{dname='超神学院'}}
}
3.5.4.3 在xml中注入属性-级联赋值
3.5.4.3.1 第一种写法
  1. 采用外部bean的方式进行级联赋值,类就采用刚刚创建的Dept和Emp。
<?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="emp" class="com.lonelysunset.bean.Emp">
        <property name="ename" value="jack"/>
        <property name="gender" value=""/>
        <property name="dept" ref="dept"/>
    </bean>
    <bean id="dept" class="com.lonelysunset.bean.Dept">
        <property name="dname" value="技术部"/>
    </bean>
</beans>
  1. 测试结果
@Test
public void test6(){
    ApplicationContext context = new ClassPathXmlApplicationContext("bean8.xml");
    Emp emp = (Emp) context.getBean("emp");
    System.out.println(emp);// Emp{ename='jack', gender='男', dept=Dept{dname='技术部'}}
}
3.5.4.2 第二种写法
  1. 需要在Emp类中为Dept对象属性生成一个get方法,从而达到覆盖的效果,类依然采用刚刚创建的Dept和Emp。
public class Emp(){
    ...
    public Dept getDept()
    {
        return dept;
    }
    ...
}
  1. 在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="emp" class="com.lonelysunset.bean.Emp">
      	<!--设置两个普通属性-->
        <property name="ename" value="rose"/>
        <property name="gender" value=""/>
      	<!--级联赋值-->
        <property name="dept" ref="dept"/>
        <property name="dept.dname" value="安保部"/>
    </bean>
    <bean id="dept" class="com.lonelysunset.bean.Dept">
        <property name="dname" value="技术部"/>
    </bean>
</beans>
  1. 测试结果。
@Test
public void test7(){
    ApplicationContext context = new ClassPathXmlApplicationContext("bean9.xml");
    Emp emp = (Emp) context.getBean("emp");
    System.out.println(emp);// Emp{ename='rose', gender='女', dept=Dept{dname='安保部'}}
}

3.5.3 IOC操作Bean管理-在xml中注入集合属性

  1. 分别注入数组类型、List类型、Map类型、Set类型的属性。
  2. 创建类,定义数组、List、Map、Set类型属性,并生成对应的set方法。
// 创建Stu实体类
public class Stu
{
    private String[] courses;
    private List<String> list;
    private Map<String, String> map;
    private Set<String> set;

    public void setCourses(String[] courses)
    {
        this.courses = courses;
    }

    public void setList(List<String> list)
    {
        this.list = list;
    }

    public void setMap(Map<String, String> map)
    {
        this.map = map;
    }

    public void setSet(Set<String> set)
    {
        this.set = set;
    }

    @Override
    public String toString()
    {
        return Arrays.asList(courses)+"-"+list+"-"+map+"-"+set;
    }
}
  1. 在xml中创建stu对象,并注入属性。
<?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="stu" class="com.lonelysunset.CollectionsType.Stu">
        <!--数组类型属性注入-->
        <property name="courses">
            <array>
                <value>Java课程</value>
                <value>PHP课程</value>
            </array>
        </property>
        <!--list集合类型属性注入-->
        <property name="list">
            <list>
                <value>SpringCloud从入门到放弃</value>
                <value>SpringSecurity从入门到入土</value>
            </list>
        </property>
        <!--map集合类型属性注入-->
        <property name="map">
            <map>
                <entry key="花果山" value="孙悟空"/>
                <entry key="高老庄" value="猪八戒"/>
            </map>
        </property>
        <!--set集合类型属性注入-->
        <property name="set">
            <set>
                <value>如来</value>
                <value>杀心观音</value>
            </set>
        </property>
    </bean>
</beans>
  1. 测试结果。
@Test
public void test8(){
    ApplicationContext context = new ClassPathXmlApplicationContext("bean10.xml");
    Stu stu = (Stu) context.getBean("stu");
    System.out.println(stu);// [Java课程, PHP课程]-[SpringCloud从入门到放弃, SpringSecurity从入门到入土]-{花果山=孙悟空, 高老庄=猪八戒}-[如来, 杀心观音]
}
3.5.5.1 在集合内部设置对象类型值
  1. 创建Student、Course类。
public class Student
{
    private List<Course> courseList;

    public void setCourseList(List<Course> courseList)
    {
        this.courseList = courseList;
    }

    @Override
    public String toString()
    {
        return "Student{" + "courseList=" + courseList + '}';
    }
}
public class Course
{
    private String name;

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

    @Override
    public String toString()
    {
        return "Course{" + "name='" + name + '\'' + '}';
    }
}
  1. 在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="student" class="com.lonelysunset.CollectionsType.Student">
       	<!--在集合中注入对象类型的值-->
      	<property name="courseList">
            <list>
                <ref bean="course1"/>
                <ref bean="course2"/>
            </list>
        </property>
    </bean>
  	<!--创建多个Course对象-->
    <bean id="course1" class="com.lonelysunset.CollectionsType.Course">
        <property name="name" value="Spring5框架"/>
    </bean>
    <bean id="course2" class="com.lonelysunset.CollectionsType.Course">
        <property name="name" value="MyBatis框架"/>
    </bean>
</beans>
  1. 测试结果。
@Test
public void test9(){
    ApplicationContext context = new ClassPathXmlApplicationContext("bean11.xml");
    Student stu = (Student) context.getBean("student");
    System.out.println(stu);// Student{courseList=[Course{name='Spring5框架'}, Course{name='MyBatis框架'}]}
}
3.5.5.2 将集合内部的值提取成公共部分使用
  1. 创建一个Book类。
public class Book
{
    private List<String> books;

    public void setBooks(List<String> books)
    {
        this.books = books;
    }

    @Override
    public String toString()
    {
        return "Book{" + "books=" + books + '}';
    }
}
  1. 在xml文件中,创建book对象,并将Book类中的books属性提取成公共部分。
  2. 在xml文件中引入util命名空间,和p命名空间差不多,copy就行。
<?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:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    <!--1.提取list集合类型,并完成属性注入-->
    <util:list id="bookList">
        <value>Spring5框架</value>
        <value>MyBatis框架</value>
        <value>SpringMVC框架</value>
    </util:list>
    <!--2.完成属性注入-->
    <bean id="bookClass" class="com.lonelysunset.CollectionsType.Book">
        <property name="books" ref="bookList"/>
    </bean>
</beans>
  1. 测试结果。
@Test
public void test10(){
  ApplicationContext context = new ClassPathXmlApplicationContext("bean12.xml");
  com.lonelysunset.CollectionsType.Book bookClass = (com.lonelysunset.CollectionsType.Book) context.getBean("bookClass");
  System.out.println(bookClass);// Book{books=[Spring5框架, MyBatis框架, SpringMVC框架]}
}

3.5.6 IOC操作Bean管理-FactoryBean

  1. Spring有两种类型的bean,一种是普通的bean,另一种是工厂bean,即:FactoryBean。
  2. 普通bean的特点:在配置文件中定义bean标签类型就是返回类型。
  3. 工厂bean的特点:在配置文件中定义bean标签类型可以和返回类型不一样。举例:
    1. 首先创建类,让这个类作为工厂bean,实现FactoryBean接口即可。
    2. 实现FactoryBean接口里面的方法,在实现的方法中定义返回的bean类型。
    3. 在xml中创建MyBean对象。
    4. 测试结果。
// 泛型中定义需要返回的类型
public class MyBean implements FactoryBean<Course>
{

    @Override
    public Course getObject() throws Exception
    {
        Course course = new Course();
        course.setName("金瓶梅");
        return course;
    }

    @Override
    public Class<?> getObjectType()
    {
        return null;
    }

    @Override
    public boolean isSingleton()
    {
        return false;
    }
}
<?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="myBean" class="com.lonelysunset.factorybean.MyBean"></bean>
</beans>
@Test
public void test11()
{
    ApplicationContext context = new ClassPathXmlApplicationContext("bean13.xml");
    Course myBean = (Course) context.getBean("myBean");
    System.out.println(myBean);// Course{name='金瓶梅'}
    // 为啥我感觉,这个BeanFactory有点SB?😓
}

3.5.7 IOC操作Bean管理-bean作用域

  1. 在Spring中,默认情况下,设置创建bean是单实例对象。举例说明:
  2. 创建Computer类。
public class Computer
{
    private String name;

    public void setName(String name)
    {
        this.name = name;
    }
}
  1. 创建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="computer" class="com.lonelysunset.bean.Computer">
    </bean>
</beans>
  1. 测试结果。
@Test
public void test12()
{
    ApplicationContext context = new ClassPathXmlApplicationContext("bean14.xml");
    Computer computer1 = (Computer) context.getBean("computer");
    Computer computer2 = (Computer) context.getBean("computer");
    System.out.println(computer1==computer2);// true,两个是同一个对象,说明两个对象引用指向的是同一块内存地址,同一块堆中的区域。所以说,默认情况下,是单例模式。
}
  1. 如何设置多实例?需要在Spring配置文件的bean标签里面使用scope属性,指定范围,并设置scope属性值为prototype,表示多实例对象。举例:
<?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">
  	<!--
			scope属性,表示设置bean为单例或多例
			默认值为singleton,表示单实例对象。
			prototype表示多实例对象。
		-->
    <bean id="computer" class="com.lonelysunset.bean.Computer" scope="prototype">
    </bean>
</beans>
  1. 测试结果。
@Test
public void test13()
{
    ApplicationContext context = new ClassPathXmlApplicationContext("bean15.xml");
    Computer computer1 = (Computer) context.getBean("computer");
    Computer computer2 = (Computer) context.getBean("computer");
    System.out.println(computer1==computer2);// false,其实也可以将hashCode打出来看一下,肯定是不一样的。
}
3.5.7.1 bean作用域结论
  1. singleton为单实例,prototype为多实例。
  2. 设置scope属性值为singleton后,会在加载spring配置文件时创建单实例对象。
  3. 设置scope属性值为prototype后,并不会在加载spring配置文件时创建对象,而是在调用getBean方法时创建多个不同的对象。

3.5.8 IOC操作Bean管理-bean生命周期

3.5.8.1 什么是生命周期?

从对象创建到对象销毁的过程,称之为生命周期。

3.5.8.2 bean的生命周期过程?
  1. 通过无参构造器创建bean的实例。(无参构造器)
  2. 为bean的属性设置值和对其它bean引用。(调用set方法)
  3. 调用bean的初始化方法。(需要进行配置初始化方法)
  4. bena可以使用了。(对象获取到了)
  5. 当容器关闭的时候,调用bean的销毁方法。(需要进行配置销毁的方法)
演示bean的生命周期
  1. 创建Order类。
public class Orders
{
    private String name;

    // 创建无参构造
    public Orders()
    {
        System.out.println("第一步 执行无参构造器创建bean实例");
    }

    // 创建set注入方法
    public void setName(String name)
    {
        this.name = name;
        System.out.println("第二步 执行set方法设置属性值");
    }

    // 创建执行初始化的方法
    public void initMethod(){
        System.out.println("第三步 执行初始化方法");
    }

    // 创建执行销毁的方法
    public void destroyMethod(){
        System.out.println("第五步 执行销毁方法");
    }
}
  1. 在xml中创建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="orders" class="com.lonelysunset.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
        <property name="name" value="金刚"/>
    </bean>
</beans>
  1. 测试结果。
@Test
public void test14()
{
    // ApplicationContext 接口中没有close方法
    // ApplicationContext context = new ClassPathXmlApplicationContext("bean16.xml");
    // AbstractApplicationContext实现了ApplicationContext接口,内部新增了close方法,并被ClassPathXmlApplicationContext继承,因此ClassPathXmlApplicationContext可以调用close方法。
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean16.xml");
    Orders order = (Orders) context.getBean("orders");
    System.out.println("第四步 获取创建bean的实例对象");
    System.out.println(order);
    // 手动让bean实例销毁
    context.close();
    /**
     * 运行结果:
     * 第一步 执行无参构造器创建bean实例
     * 第二步 执行set方法设置属性值
     * 第三步 执行初始化方法
     * 第四步 获取创建bean的实例对象
     * com.lonelysunset.bean.Orders@646d64ab
     * 第五步 执行销毁方法
     */
}
3.5.8.4 关于bean生命周期的细化版-后置处理器
3.5.8.4.1 配置后置处理器,bean的生命周期有7步
  1. 通过无参构造器创建bean示例。(无参构造器)
  2. 为bean的属性设置值和对其它bean引用。(调用set方法)
  3. 将bean实例传递给bean的后置处理器方法,postProcessBeforeInitialization。
  4. 调用bean的初始化方法。(需要进行配置初始化方法)
  5. 将bean示例传递给bean的后置处理器方法,postProcessAfterInitialization。
  6. bean可以使用了。(对象获取到了)
  7. 当容器关闭的时候,调用bean的销毁方法。(需要进行配置销毁的方法)
3.5.8.4.2 演示添加后置处理器后的bean的生命周期
  1. 创建类,需要实现BeanPostProcessor接口,创建后置处理器。
public class MyBeanPost implements BeanPostProcessor
{
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
    {
        System.out.println("在初始化之后执行的方法");
        return bean;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
    {
        System.out.println("在初始化之前执行的方法");
        return bean;
    }
}
  1. 在xml中,配置后置处理器,当配置后置处理器后,spring会自动为每一个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="orders" class="com.lonelysunset.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
        <property name="name" value="金刚"/>
    </bean>
    <!-- 直接在xml配置文件中注册即可,都不需要与其他bean关联 -->
    <bean id="myBeanPost" class="com.lonelysunset.bean.MyBeanPost"></bean>
</beans>
  1. 测试结果。
@Test
public void test14()
{
    // ApplicationContext 接口中没有close方法
    // ApplicationContext context = new ClassPathXmlApplicationContext("bean16.xml");
    // AbstractApplicationContext实现了ApplicationContext接口,内部新增了close方法,并被ClassPathXmlApplicationContext继承,因此ClassPathXmlApplicationContext可以调用close方法。
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean16.xml");
    Orders order = (Orders) context.getBean("orders");
    System.out.println("第四步 获取创建bean的实例对象");
    System.out.println(order);
    // 手动让bean实例销毁
    context.close();
    /**
     * 运行结果:
     * 第一步 执行无参构造器创建bean实例
     * 第二步 执行set方法设置属性值
     * 在初始化之前执行的方法
     * 第三步 执行初始化方法
     * 在初始化之后执行的方法
     * 第四步 获取创建bean的实例对象
     * com.lonelysunset.bean.Orders@9a7504c
     * 第五步 执行销毁方法
     */
}

3.5.9 IOC操作Bean管理-xml自动装配

3.5.9.1 什么是自动装配?

根据指定装配规则,通过配置属性名或属性类型,Spring自动将匹配的属性值进行注入。

3.5.9.2 演示自动装配过程
3.5.9.2.1 根据属性名自动注入
  1. 创建Dpet、Emp类,并在Emp类中注入Dept对象引用。
public class Dept
{
    private String name;

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

    @Override
    public String toString()
    {
        return "Dept{" + "name='" + name + '\'' + '}';
    }
}
public class Emp
{
    private String name;
    // 员工类中注入部门类,奇奇怪怪
    private Dept dept;

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

    public void setDept(Dept dept)
    {
        this.dept = dept;
    }

    @Override
    public String toString()
    {
        return "Emp{" + "name='" + name + '\'' + ", dept=" + dept + '}';
    }
}
  1. 在xml配置文件中创建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">
    <!--
        分析:回想一下,以前在xml配置文件中,是通过外部bean或内部bean注入对象属性值,现在
        通过自动装配去实现,这里介绍根据属性名称注入。
        
        实现自动装配
        bean标签中的autowire属性,配置自动装配。
        autowire属性常用两个值:
            byName根据属性名称注入,要求注入的bean的id值和属性名称一致
            byType根据属性类型注入,要求注入的bean的class值和属性类型一致
    -->
    <bean id="emp" class="com.lonelysunset.autowire.Emp" autowire="byName">
        <property name="name" value="小吴"/>
    </bean>
    <bean id="dept" class="com.lonelysunset.autowire.Dept">
        <property name="name" value="技术部"/>
    </bean>
</beans>
  1. 测试结果。
@Test
public void test15(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean17.xml");
    com.lonelysunset.autowire.Emp emp = (com.lonelysunset.autowire.Emp) context.getBean("emp");
    System.out.println(emp);// Emp{name='小吴', dept=Dept{name='技术部'}}
}
3.5.9.2.2 根据属性类型自动注入
  1. 创建Dpet、Emp类,并在Emp类中注入Dept对象引用。
public class Dept
{
    private String name;

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

    @Override
    public String toString()
    {
        return "Dept{" + "name='" + name + '\'' + '}';
    }
}
public class Emp
{
    private String name;
    private Dept dept;

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

    public void setDept(Dept dept)
    {
        this.dept = dept;
    }

    @Override
    public String toString()
    {
        return "Emp{" + "name='" + name + '\'' + ", dept=" + dept + '}';
    }
}
  1. 在xml配置你文件中创建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">
    <!--
        分析:回想一下,以前在xml配置文件中,是通过外部bean或内部bean注入对象属性值,现在
        通过自动装配去实现,这里介绍根据属性类型注入。
        
        实现自动装配
        bean标签中的autowire属性,配置自动装配。
        autowire属性常用两个值:
            byName根据属性名称注入,要求注入的bean的id值和属性名称一致
            byType根据属性类型注入,要求注入的bean的class值和属性类型一致
    -->
    <bean id="emp" class="com.lonelysunset.autowire.Emp" autowire="byType">
        <property name="name" value="小吴"/>
    </bean>
    <bean id="dept" class="com.lonelysunset.autowire.Dept">
        <property name="name" value="技术部"/>
    </bean>
</beans>
  1. 测试结果。
@Test
public void test16(){
  ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean18.xml");
  com.lonelysunset.autowire.Emp emp = (com.lonelysunset.autowire.Emp) context.getBean("emp");
  System.out.println(emp);// Emp{name='小吴', dept=Dept{name='技术部'}}
}

3.5.10 IOC操作Bean管理-外部属性文件引入

这里了解一下即可。
主要用于公共文件的引入,比如数据库配置文件之类,但是在真实开发场景中用的也不多,毕竟后期都是使用SpringBoot中的yml进行配置。这里就是顺带一说。毕竟Java都是经典白学嘛。😊

3.5.10.1 原始方式配置数据库信息
<?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="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/userDb"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
</beans>
3.5.10.2 采用外部属性文件引入的方式配置数据库信息
  1. 创建外部属性文件,properties格式文件,写入数据库配置信息。
prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/userDb
prop.userName=root
prop.password=root
  1. 将外部properties属性文件引入到spring配置文件中。首先需要引入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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!--引入外部属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--其实也没啥,就是动态些-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${prop.driverClass}"/>
        <property name="url" value="${prop.url}"/>
        <property name="username" value="${prop.userName}"/>
        <property name="password" value="${prop.password}"/>
    </bean>
</beans>

3.5.11 IOC操作Bean管理-基于注解方式

3.5.11.1 什么是注解?
  1. 注解是代码的特殊标记,格式:@注解名称(属性名称=属性值,属性名称=属性值…)
  2. 注解主要作用于类、方法和属性上面。
  3. 使用注解的目的是为了简化开发,简化xml配置。就目前位为止,以上都是通过xml配置文件方式去创建对象,以及设置对应的属性值。那真实开发场景一个项目中肯定是有很多类的,那这个配置文件得有多少行哦,从而导致后期难以维护。
3.5.11.2 Spring针对Bean管理-创建对象提供了四个注解
  • @Component
  • @Service
  • @Controller
  • @Repository
    以上4个注解的功能都是完全一样的,都是用来创建bean实例的。只不过在开发中为了更有效的区分,通常我们建议在接口层使用哦@Controller,业务处理层使用@Service,数据访问层使用@Repository,而其它不容易区分的Bean实例,则使用@Component标识
3.5.11.3 基于注解方法实现对象的创建

重点,以上全是白学,毕竟开发的真实场景中,需要用注解嘛!😊

3.5.11.3.1 引入依赖

补充:通常,我们如果使用Spring5框架进行开发,应将Spring5所涉及的依赖全部引入,其实吧,后面学到SpringMVC时,直接引入SpringMVC就行了。

<!--4.spring-aop部分-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>5.2.6.RELEASE</version>
</dependency>
3.5.11.3.2 开启组件扫描

补充:开启组件扫描的前提是,需要引入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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--
        开启组件扫描:
        1.如果需要扫描多个包,则多个包之间使用逗号进行分割即可。
        2.另一种方式也可以实现扫描多个包,直接扫描多个包的公共上层目录即可。
    -->
    <context:component-scan base-package="com.lonelysunset.annotation"></context:component-scan>
</beans>
3.5.11.3.3 创建类,并在类上添加注解
/**
 * 注解中的value可以省略不写,默认值就是类的名称,首字母小写
 * 如下,如果不写,默认就是userService
 */
//@Service(value = "userService")
@Service
public class UserService
{
    public void show()
    {
        System.out.println("show。。。");
    }
}
3.5.11.3.4 测试结果
@Test
public void test18(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean21.xml");
    com.lonelysunset.annotation.service.UserService userService = (com.lonelysunset.annotation.service.UserService) context.getBean("userService");
    userService.show();// show。。。
}
3.5.11.3.5 关于开启组件扫描的细节补充
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--
        细节1:
        添加use-default-filters="false"属性,表示不使用默认的filter。
        通过自己配置filter,即context:exclude-filter,表示指定扫描哪些内容。
        如下配置解释:只扫描com.lonelysunset.annotation包下,所有被@Service的注解标识的类。
    -->
    <context:component-scan base-package="com.lonelysunset.annotation" use-default-filters="false">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    </context:component-scan>
</beans>
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--
        默认情况下,只要配置了context:exclude-filter,则表示使用filter过滤器扫描包
        
        细节2:
        通过添加配置filter,即context:exclude-filter,表示指定不扫描哪些内容。
        如下配置解释:com.lonelysunset.annotation包下,所有被@Service的注解标识的类都不进行扫描。
    -->
    <context:component-scan base-package="com.lonelysunset.annotation">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    </context:component-scan>
</beans
3.5.11.4 基于注解方式实现属性注入
3.5.11.4.1 @Autowired
  • @Autowried,根据属性类型自动装配。
    演示使用:
  1. 开启组件扫描。
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.lonelysunset.annotation"></context:component-scan>
</beans>
  1. 创建UserService、UserDao、UserDaoImpl。并在UserService和UserDaoImpl类上添加注解,用于创建bean对象。然后在UserService类中注入UserDao接口的属性。使用@Autowried通过属性类型进行注入。
@Service
public class UserService
{
  	// 不需要添加set方法
    @Autowired
    private UserDao userDao;

    public void show()
    {
        System.out.println("show。。。");
        userDao.add();
    }
}
public interface UserDao
{
    public void add();
}
@Repository
public class UserDaoImpl implements UserDao
{
    @Override
    public void add()
    {
        System.out.println("userDao 中的 add...");
    }
}
  1. 测试结果。
@Test
public void test19(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean24.xml");
    com.lonelysunset.annotation.service.UserService userService = (com.lonelysunset.annotation.service.UserService) context.getBean("userService");
    userService.show();
    /**
     * 运行结果:
     * show。。。
     * userDao 中的 add...
     */
}
3.5.11.14.2 @Qualifier
  • @Qualifier,根据名称进行注入,使用前提是和@Autowried搭配使用。
  • 解释:已知@Autowried根据属性类型完成属性注入,而@Qualifier根据属性名称完成属性注入,当UserDao有多个实现类时,则Spring使用@Autowried时,将无法区分我们到底需要注入哪一个属性,因此需要使用@Qualifier注解声明区分。
    演示使用:
  1. 创建类和接口,并完成对象的创建和属性的注入。
public interface UserDao
{
    public void add();
}
//@Repository(value = "userDaoImpl1")
@Repository
public class UserDaoImpl implements UserDao
{
    @Override
    public void add()
    {
        System.out.println("userDaoImpl1 中的 add...");
    }
}
//@Repository(value = "userDaoImpl2")
@Repository
public class UserDaoImpl2 implements UserDao
{
    @Override
    public void add()
    {
        System.out.println("userDaoImpl2 中的 add...");
    }
}
@Service
public class UserService
{
    @Autowired
    @Qualifier(value = "userDaoImpl2")
    private UserDao userDao;

    public void show()
    {
        System.out.println("show。。。");
        userDao.add();
    }
}
  1. 测试结果。
@Test
public void test19(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean24.xml");
    com.lonelysunset.annotation.service.UserService userService = (com.lonelysunset.annotation.service.UserService) context.getBean("userService");
    userService.show();
    /**
     * 运行结果:
     * show。。。
     * userDaoImpl2 中的 add...
     */
}
3.5.11.14.3 @Resource

@Resource,可以根据属性类型注入,也可以根据属性名称注入。

@Resource // 根据属性类型注入
private UserDao userDao;

@Resource(value="userDao1") // 根据属性名称注入
private UserDao userDao1;
3.5.11.14.4 @Value

@Value,注入普通类型属性。

@Value(value="黄飞鸿")
private String name;

3.5.12 IOC操作Bean管理-完全注解开发

  1. 创建配置类,用于替代xml配置文件
@Configuration // 声明将SpringConfig类作为配置了,替代xml配置文件
//@ComponentScan(basePackages = {"com.lonelysunset"}) // 开启组件扫描
@ComponentScan("com.lonelysunset")
public class SpringConfig
{

}
  1. 测试结果。
@Test
public void test20(){
    ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    com.lonelysunset.annotation.service.UserService userService = (com.lonelysunset.annotation.service.UserService) context.getBean("userService");
    userService.show();
    /**
     * 运行结果:
     * show。。。
     * userDaoImpl2 中的 add...
     */
}

4. AOP

4.1 什么是AOP?

  1. AOP:面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
  2. 通俗描述:不通过修改源代码的方式,在主干功能里面添加新功能。
  3. 举个例子:
    1. 我们的程序有一个登录功能,通常在没有鉴权系统的登录模块会直接判断用户名和密码,如果都通过了,则直接放行进入系统,那么后期产品希望给程序加上一个新的功能-鉴权系统,方便程序自动区分哪些用户具有哪些权限,可以做哪些事情。这时我们可以使用AOP来解决,即通过不修改源代码的基础上,实现此功能。

4.2 AOP底层原理

  • AOP底层使用的是动态代理。
  • 实现动态代理的方式有两种,需要区分是否有接口。

4.2.1 第一种有接口情况,使用JDK动态代理

创建接口实现类代理对象,增强类的方法。

4.2.2 第二种无接口情况,使用CGLIB动态代理

创建子类的代理对象,增强类的方法。(以下没有介绍这种用法)

4.3 AOP-JDK动态代理

使用JDK动态代理,使用Proxy类里面的方法创建代理对象。

  1. 模拟用于登录的操作。
  2. 创建UserDao接口,声明login方法。
public interface UserDao
{
    Boolean login(String username,String password);
}
  1. 创建UserDaoImpl实现类,实现userDao接口,重写login方法。
public class UserDaoImpl implements UserDao
{
    @Override
    public Boolean login(String username, String password)
    {
        if (!(StringUtils.hasText(username) && StringUtils.hasText(password))){
            System.out.println("用户名或密码不能为空");
            return false;
        }
        if (!(username.equals("admin") || username.equals("user"))){
            System.out.println("查无此用户");
            return false;
        }
        if (!password.equals("123")){
            System.out.println("密码错误");
            return false;
        }
        System.out.println("登录成功");
        return true;
    }
}
  1. 创建JDKProxy类和UserDaoProxy类。
public class JDKProxy
{
    public static void main(String[] args)
    {
        UserDaoImpl userDao = new UserDaoImpl();
        Class[] classes = {UserDao.class};
        /**
         * newProxyInstance方法有三个参数:
         * 第一个参数:当前代理类的类加载器
         * 第二个参数:需要进行增强方法的类所实现的接口,支持多个接口
         * 第三个参数:实现InvocationHandler接口,创建代理对象,在实现该接口中编写增加的业务部分。
         */
        UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), classes, new UserDaoProxy(userDao));
        Boolean user = dao.login("admin", "123");
        System.out.println(user);
    }
}

class UserDaoProxy implements InvocationHandler
{

    // 通过构造器传参,参数类型未知,则直接使用基类Object
    // 创建谁的代理对象,就像谁传过来
    private Object obj;

    public UserDaoProxy(Object obj)
    {
        this.obj = obj;
    }

    // 增加的业务逻辑
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        // 执行原有的登录功能
        Boolean res = (Boolean) method.invoke(obj, args);
        // 为登录模块增加鉴权功能
        if (res)
        {
            if (args[0].equals("admin"))
            {
                System.out.println("管理员权限");
            }
            else if (args[0].equals("user"))
            {
                System.out.println("普通用户权限");
            }
        }
        return res;
    }
}

4.4 AOP-术语

  1. 连接点:类里面哪些方法可以被增强,这些方法就称之为连接点。

  2. 切入点:实际被真正增强的方法,称之为切入点。

  3. 通知(增强):实际增强的逻辑部分称之为通知(增强)。

  4. 通知多有种类型:

    1. 前置通知。
    2. 后置通知。
    3. 环绕通知。
    4. 异常通知。
    5. 最终通知。
  5. 切面:切面是一个动作,把通知应用到切入点的过程就叫切面。

4.5 AOP操作-准备工作

4.5.1 Spring框架一般都是基于AspectJ实现AOP操作

AspectJ不是Spring的组成部分,而是一款独立的AOP框架,一般将AspectJ和Spring框架一起使用,进行AOP操作。

4.5.2 基于AspectJ实现AOP操作-两种方式

  1. 基于xml配置文件实现。
  2. 基于注解方式实现。(推荐,主流方式)

4.5.3 引入AspectJ依赖

<!--AspectJ-->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.8.M1</version>
</dependency>

4.5.4 切入点表达式

  1. 切入点表达式的作用:用于对那个类里面的哪个方法进行增强。
  2. 语法结构:execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
  3. 举例1:对com.atguigu.dao.BookDao类里面的add进行增强。
execution(* com.atguigu.dao.BookDao.add(.. ))
  1. 举例2:对com.atguigu.dao.BookDao类里面的所有方法进行增强。
execution(* com.atguigu.dao.BookDao.*(..))
  1. 举例3:对com.atguigu.dao包里面的所有类,类里面的所有方法进行增强。
execution(* com.atguigu.dao.*.*(..))

4.6 AOP操作-基于AspectJ注解方式

  1. 创建类,并在类中定义方法。
  2. 使用注解创建User类的bean对象。
@Component
public class User
{
    public void add(){
        System.out.println("add...");
    }
}
  1. 创建配置类。
  2. 开启注解扫描。
  3. 开启AspectJ生成代理对象。
@Configuration
@ComponentScan("com.lonelysunset.aopanno")
@EnableAspectJAutoProxy(proxyTargetClass = true) // 开启AspectJ生成代理对象
public class SpringConfig
{

}
  1. 创建增强类。
  2. 编写增强逻辑。
  3. 在增强类中,在通知方法上面添加通知类型注解,使用切入点表达式进行配置。
@Component
@Aspect // 表示生成代理对象
public class UserProxy
{
    // @Before注解表示前置通知
    @Before(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
    public void before(){
        System.out.println("before...");
    }
    // @AfterReturning注解表示后置通知,表示切点返回结果时执行。如果有异常,则无法返回结果,不能执行。
    @AfterReturning(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
    public void afterReturning(){
        System.out.println("afterReturning...");
    }
    // @AfterThrowing注解表示异常通知,表示切入点有异常时,才执行,执行后。
    @AfterThrowing(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing...");
    }
    // @Around注解表示环绕通知,表示在切入点前后环绕执行
    @Around(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable
    {
        System.out.println("环绕之前...");
        proceedingJoinPoint.proceed();
        System.out.println("环绕之后...");
    }
    // @After注解表示最终通知,就算切入点有异常,也会执行
    @After(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
    public void after(){
        System.out.println("after...");
    }
}
  1. 测试结果。
public class TestAop
{
    public static void main(String[] args)
    {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        User user = context.getBean("user", User.class);
        user.add();
        /**
         * 运行结果:
         * 环绕之前...
         * before...
         * add...
         * 环绕之后...
         * after...
         * afterReturning...
         */
    }
}

4.6.1 相同的切入点抽取

参考以上现有代码进行抽取。

@Component
@Aspect // 表示生成代理对象
public class UserProxy
{
    // 抽取相同的切入点
    @Pointcut(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
    public void pointDemo()
    {

    }

    // @Before注解表示前置通知
    @Before(value = "pointDemo()")
    public void before()
    {
        System.out.println("before...");
    }

    // @AfterReturning注解表示后置通知,表示切点返回结果时执行。如果有异常,则无法返回结果,不能执行。
    @AfterReturning(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
    public void afterReturning()
    {
        System.out.println("afterReturning...");
    }

    // @AfterThrowing注解表示异常通知,表示切入点有异常时,才执行,执行后。
    @AfterThrowing(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
    public void afterThrowing()
    {
        System.out.println("afterThrowing...");
    }

    // @Around注解表示环绕通知,表示在切入点前后环绕执行
    @Around(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable
    {
        System.out.println("环绕之前...");
        proceedingJoinPoint.proceed();
        System.out.println("环绕之后...");
    }

    // @After注解表示最终通知,就算切入点有异常,也会执行
    @After(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
    public void after()
    {
        System.out.println("after...");
    }
}

4.6.2 在多个增强类中,对同一个方法进行增强,并设置增强类的优先级

  1. 在增强类上面添加@Order(数字类型值),数字类型值越小,优先级越高。
@Component
@Aspect
@Order(1)
public class PersonProxy
{
    @Before(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
    public void personProxy(){
        System.out.println("优先级最高的增强类...");
    }
}
  1. 测试结果。
public class TestAop
{
    public static void main(String[] args)
    {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        User user = context.getBean("user", User.class);
        user.add();
        /**
         * 运行结果:
         * 优先级最高的增强类...
         * 环绕之前...
         * before...
         * add...
         * 环绕之后...
         * after...
         * afterReturning...
         */
    }
}

4.7 AOP操作-基于xml配置文件方式

  1. 创建两个类,一个增强类和一个被增强类,然后创建对应的方法。
public class Book
{
    public void buy(){
        System.out.println("购买一本书...");
    }
}
public class BookProxy
{
    public void buyBookBefore(){
        System.out.println("我得先摸摸口袋有没有钱");
    }
}
  1. 在Spring配置文件中创建两个类的对象。
  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" 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 https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--使用配置文件创建对象-->
    <bean id="book" class="com.lonelysunset.aopxml.Book">
    </bean>
    <bean id="bookProxy" class="com.lonelysunset.aopxml.BookProxy"/>
    <!--配置aop增强-->
    <aop:config>
        <!--切入点-->
        <aop:pointcut id="p" expression="execution(* com.lonelysunset.aopxml.Book.buy(..))"/>
        <!--配置切面-->
        <!--什么是配置切面?将通知引用在切入点上,就是切面-->
        <aop:aspect ref="bookProxy">
            <!--增强作用在具体的方法上-->
            <!--简单理解:通知作用在切入点,这里配置的是前置通知-->
            <aop:before method="buyBookBefore" pointcut-ref="p"/>
        </aop:aspect>
    </aop:config>
</beans>
  1. 测试结果:
@Test
public void test21(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean25.xml");
    com.lonelysunset.aopxml.Book book = context.getBean("book", com.lonelysunset.aopxml.Book.class);
    book.buy();
    /**
     * 运行结果:
     * 我得先摸摸口袋有没有钱
     * 购买一本书...
     */
}

5. spring5框架新功能

整个Spring5框架的代码基于Java8,运行时兼容JDK9,许多不建议使用的类和方法都在代码库中删除。

5.1 日志

  1. Spring5框架自带了通用的日志封装。
  2. Spring5已经移除了Log4jConfigListener,官方推荐使用Log4j2。
  3. 有关Log4j2部分,有通用的配置模板,一般整合SpringBoot使用。(这里不过多介绍)

5.2 @Nullable

  1. @Nullable注解可以使用在方法、属性、参数上面。
  2. @Nullable注解使用在方法上面时,表示返回值可以为空。
@Nullable
public String getName()
{
    return name;
}
  1. @Nullable注解使用在方法参数里面时,表示方法参数可以为空。
public House(@Nullable String name, double price)
{
    this.name = name;
    this.price = price;
}
  1. @Nullable注解使用在属性上时,表示属性值可以为空。
@Nullable
private String name;

5.3 Spring5核心容器支持函数式风格

// Spring5支持使用函数式风格创建对象,并将对象交由Spring管理
@Test
public void test23(){
    // 1.创建GenericApplicationContext对象
    GenericApplicationContext context = new GenericApplicationContext();
    // 2.掉用context的方法进行对象的注册
    context.refresh();
    context.registerBean("user1",User.class, User::new);
    // 3.验证刚刚注册的对象是否已经交由Spring进行管理?
    User user1 = context.getBean("user1", User.class);
    user1.show();// show~
}

5.4 Webflux

很牛逼的技术,记得去系统学。后期来补。

  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值