Spring5框架(从入门到入坟)

1、Spring框架概述

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

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

3、Spring有两个核心部分:IOCAop

(1)IOC:控制反转,把创建对象过程交给Spring管理

(2)Aop:面向切面,不修改源代码进行功能增强

4、Spring特点

(1)方便解耦,简化开发

(2)Aop编程支持

(3)方便程序测试

(4)方便和其他框架进行整合

(5)方便进行事务操作

(6)降低API开发难度

5、选取Spring版本5.x学习

2、Spring框架下载

Spring官网:https://spring.io/projects/spring-framework#learn

下载网址:https://repo.spring.io/release/org/springframework/spring/

5.2.6RELEASE版本

spring-5.2.6.RELEASE-dist.zip

3、案例

3.1 创建一个普通的java项目

导入4个jar包

spring-beans-5.2.6.RELEASE

spring-context-5.2.6.RELEASE

spring-core-5.2.6.RELEASE

spring-expression-5.2.6.RELEASE

commons-logging-1.1.1 //还要导入这个日志包,否则会报错

3.2 在项目中添加依赖

3.3 在项目中创建一个普通的类

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

3.4 创建 Spring 配置文件,在配置文件中配置创建的对象

(1)Spring配置文件使用xml

bean有两个属性,id是自己起的名字,class是类所在路径

<?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.qy.spring5.User"></bean>
    
</beans>

3.5 进行测试代码编写

@Test
public void testAdd(){

    // 1 加载 Spring 配置文件
    ClassPathXmlApplicationContext context =
            new ClassPathXmlApplicationContext("com/qy/spring5/bean1.xml");

    // 2 获取配置创建的对象
    User user = context.getBean("user", User.class);

    // 3 调用获取配置创建的对象的方法
    user.add();
    // 4 输出创建的对象
    System.out.println(user);
}

4、IOC容器

(1)IOC底层原理

(2)IOC接口(BeanFactory)

(3)IOC操作Bean管理(基于xml)

(4)IOC操作Bean管理(基于注解)

1、什么是IOC

(1)控制反转,把对象创建和对象之间的调用过程,交给Spring管理

(2)使用IOC目的:为了耦合度降低

(3)做入门案例就是IOC实现

2、IOC底层原理

(1)xml解析、工厂模式、反射

3、讲解一下IOC底层原理

假设现在有两个类UserDaoUserService

(方便起见,我们写在一个框里)

4.1 IOC底层原理

原始情况:UserService调用UserDao是如何调用的呢?

我们要在UserService中创建一个UserDao,然后再进行调用,但是这样的话导致了UserService和UserDao的耦合度很高,如果UserDao的方法或者路径变化,那么UserService也会变化。

//UserService类
public class UserService{
	execute(){
    	UserDao dao = new UserDao(); 
    	dao.add();
	}
}

------

//UserDao类
public class UserDao{
	add(){
      ......
	}
}



工厂模式

我们有一个工厂类,来统一的创建对象,使用的话只需要调用工厂类来获得这个对象。

Factory.class UserService.class UserDao.class

UserService使用UserDao的时候,通过工厂类来获得UserDao,降低了UserService和UserDao的耦合度,

但是Factory和UserDao还是有耦合度的(耦合度并不会消失,只能尽可能的小)

这样UserDao改变了的话,UserService并不会改变。

class UserService{
    execute(){
		UserDao dao = Factory.getDao();
        dao.add();
    }
}

---
class UserDao{
  add(){
    ......
  }
}
---
//工厂类
class Factory{
  public static UserDao getDao(){
    return new UserDao();
  } 
}

4.1.1 IOC 解耦过程

IOC没有使耦合度消失,只是降低了耦合度,比如说,如果UserDao的路径改变了,那么只需要在xml配置文件中修改class属性即可,不需要修改工厂类。

  • 第一步 xml配置文件 , 配置创建的对象

    <bean id="dao" class="com.at.qy.UserDao"></bean>
    

  • 第二部 有Service类和Dao类, 创建工厂类

    class UserFactory{
      public static UserDao getDao(){
        String classValue = class属性值; //1  xml解析
        Class clazz = Class.forName(classValue); //2  通过反射创建对象
        return (UserDao)clazz.newInstance();
      }
    }
    

4.2 IOC接口

1、IOC思想基于IOC容器完成,IOC容器底层就是对象工厂

2、Spring提供IOC容器两种实现方式:(两个接口)

(1)BeanFactory:IOC容器的基本实现,是Spring内部的使用接口,不提供开发人员进行使用。

*加载配置文件时,不会创建对象,只有在获取(使用)对象才去创建对象

(2)ApplicationContext:BeanFactory接口的子接口,提供更多强大的功能,一般由开发人员使用

*加载配置文件时候就会把配置文件中所有对象进行创建(下列代码第一步,就是加载配置文件)

    @Test
    public void testAdd(){
        // 1 加载 Spring 配置文件  
        // ********ApplicationContext可以替换为BeanFactory********
        ApplicationContext context =
                new ClassPathXmlApplicationContext("com/qy/spring5/bean1.xml");
        // 2 获取配置创建的对象
        User user = context.getBean("user", User.class);
        // 3 调用获取配置创建的对象的方法
        user.add();
        // 4 输出创建的对象
        System.out.println(user);
    }

4.3 IOC操作Bean管理

4.3.1 什么是Bean管理?

Bean管理指的是两个操作:

(1)Spring创建对象

(2)Spring注入属性

4.3.2 Bean管理操作有两种方式

(1)基于xml配置文件方式实现

(2)基于注解方式实现

5、IOC操作Bean管理(基于xml方式)

基于xml方式创建对象

(1)在spring配置文件中,使用bean标签,标签里面添加对应属性,就可以实现对象创建

<bean id="user" class="com.qy.spring5.User"></bean>

(2)在bean标签有很多属性,介绍常用的属性:

  • id属性:唯一标识
  • class属性:类全路径(包类路径)

(3)创建对象的时候,默认也是执行无参数的构造方法完成对象创建

基于xml方式注入属性

(1)DI:依赖注入,就是注入属性

第一种注入方式:使用set方法注入

Book类

public class Book {

    private String bname;

    private String bauthor;

    public void setBname(String bname) {
        this.bname = bname;
    }
    public void setBauthor(String bauthor) {
        this.bauthor = bauthor;
    }
    public String getBname() {
        return bname;
    }
    public String getBauthor() {
        return bauthor;
    }
}

xml配置文件

 <bean id="book" class="com.qy.spring5.Book">
      <property name="bname" value="易经"></property>
      <property name="bauthor" value="达摩老祖"></property>
</bean>

测试类

@Test
public void testBook(){
    // 1 加载Spring配置文件
    ApplicationContext context =
            new ClassPathXmlApplicationContext("com/qy/spring5/bean1.xml");
    // 2 获取配置创建的对象
    Book book = context.getBean("book",Book.class);
    // 3 输出配置创建的对象的属性
    System.out.println("书名:"+book.getBname()+"作者:"+book.getBauthor());
}

/***************/
输出:书名:易经作者:达摩老祖

第二种注入方式:使用有参构造注入

Order类

public class Order {
    private String oname;

    private String address;

    public Order(String oname,String address){
        this.oname = oname;
        this.address = address;
    }
    public void orderTest(){
        System.out.println("订单名称:"+oname+" 订单地址:"+address);
    }
}

xml配置文件

使用有参构造注入要在xml配置文件中加入<constructor-arg></constructor-arg>标签

<bean id="order" class="com.qy.spring5.Order">
    <constructor-arg name="oname" value="电脑"></constructor-arg>
    <constructor-arg name="address" value="China"></constructor-arg>
</bean>

测试类

@Test
public void testOrder(){
    // 1 加载Spring配置文件
    ApplicationContext context =
            new ClassPathXmlApplicationContext("com/qy/spring5/bean1.xml");
    // 2 获取配置创建的对象
    Order order = context.getBean("order",Order.class);
    // 3 输出配置创建的对象
    System.out.println(order);
    order.orderTest();
}
/************************/
输出为:

com.qy.spring5.Order@569cfc36

订单名称:电脑 订单地址:China

基于xml方式注入其他类型属性

1、字面量

(1)null值

给name属性设置为null

<property name="bname">
    <null/>
</property>

(2)属性值包含特殊符号

例如,我们想要注入属性值为 <<南京>>

<property name="bname" value="<<南京>>"></property> 

像这样写,因为带有特殊符号,会报错

<property name="bname">
    <value><![CDATA[<<南京>>]]></value>
</property>

应该这样写就可以了。<![CDATA[需要注入的属性值]]>

注入属性-外部bean

两个类UserService和UserDao,UserService调用UserDao

UserService类

public class UserService {
    private UserDao userDao;
    private void setUserDao(UserDao userDao){
        this.userDao = userDao;
    }
    public  void add(){
        System.out.println("service......");
    }
}

UserDao类

public class UserDao {
    public void update(){
        System.out.println("dao update......");
    }
}

xml配置文件

<bean id="userDao" class="com.qy.spring5.dao.UserDao"></bean>
<bean id="userService" class="com.qy.spring5.service.UserService">
    <!--
		注入UserDao对象
		name:类里面属性名称
		ref:创建UserDao对象bean标签的id值
    -->
    <property name="userDao" ref="userDao"></property>
</bean>

注入属性-内部bean和级联赋值

(1)一对多关系:部门和员工

一个部门有多个员工,一个员工属于一个部门

部门是一,员工是多

(2)在实体类之间表示一对多关系

部门类、员工类、xml配置文件

//部门类
public class Dept {
    private String dname;

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

//员工类
public class Emp {
    private String ename;
    private String gender;
    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;
    }
    public void test(){
        System.out.println("员工名称:"+ename+" 性别:"+gender+" 部门名称:"+dept.getDeptName());
    }
}

//xml配置文件
<bean id="emp" class="com.qy.spring5.DeptTest.Emp">
    <property name="ename" value="lucy"></property>
    <property name="gender" value="女"></property>
    <property name="dept">
        <bean id="dep" class="com.qy.spring5.DeptTest.Dept">
            <property name="dname" value="安保部"></property>
        </bean>
    </property>
</bean>

//测试类
@Test
public void testOrder(){
    // 1 加载Spring配置文件
    ApplicationContext context =
            new ClassPathXmlApplicationContext("com/qy/spring5/bean2.xml");
    // 2 获取配置创建的对象
    Emp emp = context.getBean("emp", Emp.class);
    // 3 输出配置创建的对象
    System.out.println(emp);
    emp.test();
}

//输出结果
com.qy.spring5.DeptTest.Emp@66d1af89
员工名称:lucy 性别:女 部门名称:安保部

注入属性-级联赋值

<!--级联赋值-->
<bean id="emp" class="com.qy.spring5.DeptTest.Emp">
    <!--设置两个普通属性-->
    <property name="ename" value="lucy"></property>
    <property name="gender" value=""></property>
    <!--级联赋值-->
    <property name="dept" ref="dept"></property>
    <!--dept.name用于设置dept的name,需要给Dept类上添加Dept的get方法-->
    <property name="dept.name" value="技术部"></property>
</bean>
<bean id="dept" class="com.qy.spring5.DeptTest.Dept">
    <property name="dname" value="财务部"></property>
</bean>

注入集合类型属性

(1)注入数组类型属性

(2)注入 List 集合类型属性

(3)注入 Map 集合

综合案例

Stu类和xml配置文件

//Stu类
public class Stu {

    //1 数组类型属性
    private String[] courses;
    //2 集合类型属性
    private List<String> list;
    //3 map类型属性
    private Map<String,String> maps;
    //4 set类型属性
    private Set<String> sets;

    public void setSets(Set<String> sets) {
        this.sets = sets;
    }
    public void setCourses(String[] courses) {
        this.courses = courses;
    }
    public void setList(List<String> list) {
        this.list = list;
    }
    public void setMaps(Map<String, String> maps) {
        this.maps = maps;
    }

    @Override
    public String toString() {
        return "Stu{" +
                "courses=" + Arrays.toString(courses) +
                ", list=" + list +
                ", maps=" + maps +
                ", sets=" + sets +
                '}';
    }
}

//xml配置文件
<bean id="stu" class="com.qy.spring5.ListTest.Stu">
    <!--数组属性注入-->
    <property name="courses">
        <array>
            <value>java课程</value>
            <value>数据库课程</value>
        </array>
    </property>
    <!--list属性注入-->
    <property name="list">
        <list>
            <value>大王</value>
            <value>小王</value>
        </list>
    </property>
    <!--map属性注入-->
    <property name="maps">
        <map>
            <entry key="JAVA" value="java"></entry>
            <entry key="MYSQL" value="mysql"></entry>
        </map>
    </property>
    <!--set属性注入-->
    <property name="sets">
        <set>
            <value>set1</value>
            <value>set2</value>
        </set>
    </property>
</bean>

//测试类
@Test
public void testOrder(){
    // 1 加载Spring配置文件
    ApplicationContext context =
            new ClassPathXmlApplicationContext("com/qy/spring5/ListTest/bean.xml");
    // 2 获取配置创建的对象
    Stu stu = context.getBean("stu", Stu.class);
    // 3 输出配置创建的对象
    System.out.println(stu);
}

注入集合类型属性2

在集合里面设置对象类型值

Stu类、Course类、xml配置文件

//Stu类
public class Stu {
    private List<Course> course;
    public void setCourse(List<Course> course) {
        this.course = course;
    }
    public void test(){
        System.out.println(course);
    }
}

//Course类
public class Course {

    private String cname;
    public void setCname(String cname) {
        this.cname = cname;
    }
    @Override
    public String toString() {
        return "Course{" +
                "cname='" + cname + '\'' +
                '}';
    }
}

//xml配置文件
<bean id="stu" class="com.qy.spring5.ListTest.Stu">
    <property name="course">
        <list>
            <ref bean="course1"></ref>
            <ref bean="course2"></ref>
        </list>
    </property>
</bean>
<bean id="course1" class="com.qy.spring5.ListTest.Course">
    <property name="cname" value="Sprng5框架"></property>
</bean>
<bean id="course2" class="com.qy.spring5.ListTest.Course">
    <property name="cname" value="MyBatis框架"></property>
</bean>

//测试类
@Test
public void testOrder(){
    // 1 加载Spring配置文件
    ApplicationContext context =
            new ClassPathXmlApplicationContext("com/qy/spring5/ListTest/bean.xml");
    // 2 获取配置创建的对象
    Stu stu = context.getBean("stu", Stu.class);
    // 3 输出配置创建的对象
    stu.test();
}

//输出
[Course{cname='Sprng5框架'}, Course{cname='MyBatis框架'}]

6、IOC操作Bean管理(FactoryBean)

1、Spring有两种类型bean,一种普通bean,另一种工厂bean(FactoryBean)

2、普通bean:在配置文件中定义bean类型就是返回类型

3、工厂bean:在配置文件定义bean类型可以和返回类型不一样

第一步 创建类,让这个类作为工厂bean,实现接口 FactoryBean

第二部 实现接口里卖弄的方法,在实现的方法中定义返回的bean类型

工厂Bean实现

MyBean类、Course类、xml配置文件、测试类

//MyBean类
public class MyBean implements FactoryBean {
    private String cname;

    @Override
    // 定义返回的类型为Course
    public Course getObject() throws Exception {
        Course course = new Course();
        course.setCname("Java课程");
        return course;
    }

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

//Course类
public class Course {
    private String cname;

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }
}

//xml配置文件
<bean id="myBean" class="com.qy.spring5.MyBeanTest.MyBean"></bean>

//测试类
//会发现获取myBean的返回值已经变为了Course
@Test
public void test(){
    //1 加载xml配置文件
    ApplicationContext context =
            new ClassPathXmlApplicationContext("com/qy/spring5/MyBeanTest/bean.xml");
    //2 获取配置文件创建的对象
    Course course = context.getBean("myBean", Course.class);
    //3 输出获得的对象
    System.out.println(course.getCname());
}

7、IOC操作Bean管理(bean作用域)

1、在Spring里边,默认情况下,bean是单实例对象

// 测试类
@Test
public void testBook(){
    // 1 加载Spring配置文件
    ApplicationContext context =
            new ClassPathXmlApplicationContext("com/qy/spring5/bean1.xml");
    // 2 获取配置创建的对象
    Book book1 = context.getBean("book",Book.class);
    Book book2 = context.getBean("book",Book.class);
    // 3 输出配置创建的对象的属性
    System.out.println(book1);
    System.out.println(book2);
}
// 输出结果
com.qy.spring5.Book@365185bd
com.qy.spring5.Book@365185bd
  
两个对象地址相同,说明Spring创建的是单实例对象

2、在Spring里边,如何设置创建对象是单实例还是多实例?

在Spring配置文件bean标签里有属性(scope)y用于设置单实例还是多实例

scope属性值:

  • 第一个值,默认值,singleton,表示单实例对象
  • 第二个值,prototype,表示多实例对象

设置创建多实例对象演示:

//测试类
@Test
public void testBook(){
    // 1 加载Spring配置文件
    ApplicationContext context =
            new ClassPathXmlApplicationContext("com/qy/spring5/bean1.xml");
    // 2 获取配置创建的对象
    Book book1 = context.getBean("book",Book.class);
    Book book2 = context.getBean("book",Book.class);
    // 3 输出配置创建的对象的属性
    System.out.println(book1);
    System.out.println(book2);
}

//xml配置文件
<bean id="book" class="com.qy.spring5.Book" scope="prototype"></bean>

//测试类输出
com.qy.spring5.Book@365185bd
com.qy.spring5.Book@18bf3d14
 
// 发现创建的对象地址不同,说明创建了多实例对象

singleton 和 prototype 区别:

(1)singleton创建单实例对象,prototype创建多实例对象

(2)设置scope是singleton的时候,加载spring配置文件的时候就会创建单实例对象

​ 设置scope是prototype的时候,不是在加载spring配置文件的时候创建对象,在调用getBean方法的时候创建多实例对象

8、IOC操作Bean管理(bean生命周期)

(1)生命周期

(1)从对象创建到对象销毁的过程

(2)bean生命周期

​ (1)通过构造器创建bean实例(无参数构造)

​ (2)为bean的属性设置值和对其他bean的引用(调用类中的set方法)

​ (3)调用bean的初始化的方法(需要配置)

​ (4)bean可以使用了(对象获取到了)

​ (5)当容器关闭的时候,调用bean的销毁的方法(需要进行配置销毁的方法)

(3)案例演示bean的生命周期

Order类、测试类、xml配置文件

// Order类
public class Order {

    // 无参数构造
    public Order(){
        System.out.println("第一步 通过构造器创建bean实例(无参数构造)");
    }
    public String oname;

    public void setOname(String oname) {
        this.oname = oname;
        System.out.println("第二步 为bean的属性设置值(调用set方法)");
    }

    // bean初始化方法
    public void initMethod(){
        System.out.println("第三步 调用bean的初始化的方法(需要配置)");
    }
    // bean销毁方法
    public void destroyMethod(){
        System.out.println("第五步 当容器关闭的时候,调用bean的销毁的方法(需要进行配置销毁的方法)");
    }
}


// xml配置文件
<bean id="order" class="com.qy.spring5.bean.Order" 
  init-method="initMethod" 
  destroy-method="destroyMethod">
    <property name="oname" value="手机"></property>
</bean>


// 测试类
@Test
public void test(){
    // 1 加载Spring配置文件
    ClassPathXmlApplicationContext context =
            new ClassPathXmlApplicationContext("com/qy/spring5/bean/bean.xml");
    // 2 获取配置文件创建的对象
    Order order = context.getBean("order", Order.class);
    System.out.println("第四步 获取到创建的bean对象");
    // 3 使用创建的对象
    System.out.println(order);
    // 4 手动让bean实例销毁
    context.close();
}

// 输出
第一步 通过构造器创建bean实例(无参数构造)
第二步 为bean的属性设置值(调用set方法)
第三步 调用bean的初始化的方法(需要配置)
第四步 获取到创建的bean对象
com.qy.spring5.bean.Order@2c039ac6
第五步 当容器关闭的时候,调用bean的销毁的方法(需要进行配置销毁的方法)

(4)bean的后置处理器,加入之后,bean的生命周期有七步

​ (1)通过构造器创建bean实例(无参数构造)

​ (2)为bean的属性设置值和对其他bean的引用(调用类中的set方法)

(3)把bean实例传递到bean后置处理器的方法

​ (4)调用bean的初始化的方法(需要配置)

(5)把bean实例传递到bean后置处理器的方法

​ (6)bean可以使用了(对象获取到了)

​ (7)当容器关闭的时候,调用bean的销毁的方法(需要进行配置销毁的方法)

(5)演示添加后置处理器的效果

Order类,xml配置文件,MyPostBean类(后置处理器),测试类

(1)Order类

public class Order {

    // 无参数构造
    public Order(){
        System.out.println("第一步 通过构造器创建bean实例(无参数构造)");
    }
    public String oname;

    public void setOname(String oname) {
        this.oname = oname;
        System.out.println("第二步 为bean的属性设置值(调用set方法)");
    }

    // bean初始化方法
    public void initMethod(){
        System.out.println("第四步 调用bean的初始化的方法(需要配置)");
    }
    // bean销毁方法
    public void destroyMethod(){
        System.out.println("第七步 当容器关闭的时候,调用bean的销毁的方法(需要进行配置销毁的方法)");
    }
}

(2)xml配置文件

在xml配置文件中配置后置处理器

<bean id="order" class="com.qy.spring5.bean.Order" init-method="initMethod" destroy-method="destroyMethod">
    <property name="oname" value="手机"></property>
</bean>

<!--创建后置处理器-->
<bean id="myBeanPost" class="com.qy.spring5.bean.MyPostBean"></bean>

(3)创建后置处理器

创建MyBeanPost类,实现接口 BeanPostProcessor ,这个MyBeanPost类就是后置处理器

public class MyPostBean implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("第三步 把bean实例传递到bean后置处理器的方法(在初始化之前执行的方法)");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("第五步 把bean实例传递到bean后置处理器的方法(在初始化之后执行的方法)");
        return bean;
    }
}

(4)测试类

@Test
public void test(){
    // 1 加载Spring配置文件
    ClassPathXmlApplicationContext context =
            new ClassPathXmlApplicationContext("com/qy/spring5/bean/bean.xml");
    // 2 获取配置文件创建的对象
    Order order = context.getBean("order", Order.class);
    System.out.println("第六步 获取到创建的bean对象");
    // 3 使用创建的对象
    System.out.println(order);
    // 4 手动让bean实例销毁
    context.close();
}

(5)输出

第一步 通过构造器创建bean实例(无参数构造)
第二步 为bean的属性设置值(调用set方法)
第三步 把bean实例传递到bean后置处理器的方法(在初始化之前执行的方法)
第四步 调用bean的初始化的方法(需要配置)
第五步 把bean实例传递到bean后置处理器的方法(在初始化之后执行的方法)
第六步 获取到创建的bean对象
com.qy.spring5.bean.Order@66d1af89
第七步 当容器关闭的时候,调用bean的销毁的方法(需要进行配置销毁的方法)

Process finished with exit code 0

9、IOC操作Bean管理(xml自动装配)

(1)什么是自动装配?

根据指定装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入

(2)演示自动装配过程

Dept类、Emp类、xml配置文件、测试类

(1)Dept类

public class Dept {
    @Override
    public String toString() {
        return "Dept{}";
    }
}

(2)Emp类

public class Emp {
    private Dept dept;

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

    public void test(){
        System.out.println("dept : "+dept);
    }
}

(3)xml配置文件 (在配置文件中开启自动装配 )

实现自动装配,bean标签属性autowire,配置自动装配

autowire属性常用两个值:

(1)byName 根据属性名称注入,注入值bean的id值和类属性名称不一样

(2)byType根据属性类型注入

<bean id="emp" class="com.qy.spring5.autowire.Emp" autowire="byName"></bean>
<bean id="dept" class="com.qy.spring5.autowire.Dept"></bean>

(4)测试类

public class test {
    @Test
    public void test(){
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("com/qy/spring5/autowire/bean.xml");
        Emp emp = context.getBean("emp", Emp.class);
        emp.test();
    }
}

输出:

dept : Dept{}

10、IOC操作Bean管理(基于注解方式)

1、什么是注解

(1)注解是代码特殊标记,格式:@注解名称(属性名称=属性值)

(2)注解作用在类,方法,属性上

(3)使用注解目的:简化xml配置

2、Spring针对Bean管理创建对象提供注解

(1)@Component

(2)@Service

(3)@Controller

(4)@Repository

3、基于注解方式实现对象创建

(1)引入依赖

使用到注解需要引入 spring-aop-5.2.6.RELEASE jar包

(2)开启组件扫描

在xml配置文件中,需要引入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 写上需要扫描包的上层目录
             扫描 com.qy 包下所有的类
    -->
    <context:component-scan base-package="com.qy"></context:component-scan>

</beans>

(3)创建UserService类和测试类

UserService类

@Service
public class UserService {
    public void add(){
        System.out.println("service add...");
    }
}

测试类

public class test {
    @Test
    public void test(){
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("com/qy/spring5/zhujie/bean.xml");
        // 这里getBean的name属性值,默认是需要创建对象的类的首字母小写
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

//输出
service add...

4、开启组件扫描细节配置

<!--示例1
        use-default-filters="false" 表示现在不适用默认filter,自己配置filter
        context:include-filter , 设置扫描哪些内容
-->
<context:component-scan base-package="com.qy" use-default-filters="false">
    <context:include-filter type="annotation"
                            expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<!--示例2
        下面配置扫描包的所有内容
        context:exclude-filter , 设置哪些内容不扫描
-->
<context:component-scan base-package="com.qy">
    <context:exclude-filter type="annotation" 
                            expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

5、基于注解方式实现属性注入

四个注解:@Autowired @Qulifier @Resource @Value

(1)@AutoWired:根据属性类型进行自动注入

案例演示

使用到的类: Service类、Dao类

第一步:把Service和Dao对象创建 (在Service类上添加@Service,在Dao类上添加@Repository)

第二步:在Service中注入Dao对象,在Service中添加dao类型属性,在属性上添加注解@Autowired

/***************代码演示 **********************/
//Service类
public class Service{
  @Autowired
  private Dao dao;
  public void add(){
    System.out.println("service add ...");
    dao.add();
  }
}

//Dao类
public class Dao{
  public void add(){
    System.out.println("dao add ...");
  }
}

//XML配置文件  记得引入context命名空间,上边有
 <context:component-scan base-package="com.qy"></context:component-scan>

//测试类
public class test {
    @Test
    public void test(){
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("com/qy/spring5/zhujie/bean.xml");

        // 这里getBean的name属性值,默认是需要创建对象的类的首字母小写
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

//输出结果
service add...
dao add...

(2)@Qualifier:根据属性名称进行注入,需要和@Autowired配合使用

@Qualifier注解用在什么场合呢?

我们来举个例子:

比如说现在有 Service类,Dao接口,DaoImpl1实现类(实现Dao接口),DaoImpl2实现类(实现Dao接口),这时候我们再使用@Autowired根据类型进行注入的话,因为有两个实现类,那么就不知道要注入哪个实现类了,所以要使用@Qualifier注解。

案例演示:

UserService类,UserDao接口,UserDaoImpl1实现类,UserDaoImpl2实现类,测试类

//UserService类
@Service(value = "userService")
public class UserService {
    @Autowired
    @Qualifier(value = "userDaoImpl1")
    private UserDao userDaoImpl1;
    @Autowired
    @Qualifier(value = "userDaoImpl2")
    private UserDao userDaoImpl2;

    public void add(){
        System.out.println("service add ...");
        userDaoImpl1.add();
        userDaoImpl2.add();
    }
}

//UserDao接口
public interface UserDao {
    public void add();
}

//UserDaoImpl1实现类
@Repository(value = "userDaoImpl1")
public class UserDaoImpl1 implements UserDao{
    @Override
    public void add() {
        System.out.println("UserDaoImpl1 第一个实现类 add ...");
    }
}

//UserDaoImpl2实现类
@Repository(value = "userDaoImpl2")
public class UserDaoImpl2 implements UserDao{
    @Override
    public void add() {
        System.out.println("UserDaoImpl2 第二个实现类 add ...");
    }
}

//XML配置文件
<context:component-scan base-package="com.qy"></context:component-scan>

//测试类
public class test {
    @Test
    public void test(){
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("com/qy/spring5/qualifier/bean.xml");
        // 这里getBean的name值默认是UserService首字母小写,可以在UserService上通过注解设置 @Service(value = "")
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

//输出
service add ...
UserDaoImpl1 第一个实现类 add ...
UserDaoImpl2 第二个实现类 add ...

(3)@Resource:可以根据类型注入,可以根据名称注入

上边案例已经写的很清楚,这里简单写一下

@Resource根据类型注入

@Resource
private UserDao userDaoImpl;

@Resource根据名称注入

和@Qualifier的区别,@Resource可以自己单独使用,@Qualifier需要和@Autowired配合使用

@Resource(name = "userDaoImpl2")
private UserDao userDaoImpl2;

(4)@Value:注入普通类型属性

@Value(value = "abc")
private String name;  // 将abc注入到属性name中

11、IOC操作Bean管理(完全注解开发)

(1)创建配置类(添加@Configuration注解),替代xml配置文件

@Configuration // 作为配置类,替代xml配置文件
@ComponentScan(basePackages = {"com.qy"})
public class SpringConfig {
}

(2)之前加载xml配置文件的代码也要修改为加载配置类

public class test {
    @Test
    public void test(){
        // 加载配置类
        AnnotationConfigApplicationContext context = 
                new AnnotationConfigApplicationContext(SpringConfig.class);
        
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

12、AOP

12.1 基本概念

(1)面向切面编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可用性,同时提高了开发的效率。

(2)通俗描述:不通过修改源代码方式,在主干功能里面添加新功能。

12.2 AOP底层原理

(1)AOP底层使用动态代理

  • 有两种情况动态代理

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

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

interface UserDao{
  public void login();
}

class UserDaoImpl implements UserDao{
  public void login(){
    //登录实现过程
  }
}

JDK动态代理
创建UserDao接口实现类代理对象

第二种 没有接口情况,使用 CGLIB 动态代理

class User{
  public void add(){
    ...
  }
}

//子类
class Person extends User{
  public void add(){
    super.add();
    //增强逻辑
  }
}

CGLIB动态代理
创建当前类子类的代理对象

12.3 AOP(JDK动态代理)

(1)使用JDK动态代理,使用 Proxy 类里面的方法创建出代理对象

java.lang.reflect.Proxy

  • 调用 newProxyInstance 方法
public static Object newProxyInstance(
ClassLoader loader,<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentExceptio

第一个参数:类加载器来定义代理类

第二个参数:增强方法所在的类,这个类实现的接口,支持多个接口

第三个参数:实现这个接口 InvocationHandler,创建代理对象,写增强的方法

12.4 编写JDK动态代理代码

加强UserDao接口的add方法。

用到了UserDao接口,UserDaoImpl实现类,JDKProxy类(创建动态代理)

UserDao接口

public interface UserDao {
    public int add(int arg1,int arg2);
    public String update(String id);
}

UserDaoImpl实现类

public class UserDaoImpl implements UserDao{
    @Override
    public int add(int arg1, int arg2) {
        System.out.println("add方法执行...");
        return arg1 + arg2;
    }

    @Override
    public String update(String id) {
        return id;
    }
}

JDKProxy类

public class JDKProxy {

    public static void main(String[] args) {

        /*
            三个参数:  类加载器、类实现的接口、InvocationHandler
        */
        // 2 第二个参数
        Class[] instances = {UserDao.class};
        UserDaoImpl userDao = new UserDaoImpl();
        // 1 调用Proxy的newProxyInstance方法创建动态代理
        UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), instances, new UserDaoProxy(userDao));
        int res = dao.add(1, 2);
        System.out.println("res : "+res);
    }

}
// 3 创建代理对象代码 (第三个参数)
class UserDaoProxy implements InvocationHandler{

    // 创建谁的代理对象,就把谁传过来
    // 使用有参数构造的方法进行传递
    // 这里应该传递UserDaoImpl,为了更通用,传递Object
    
    private Object obj;
    public UserDaoProxy(Object obj){
        this.obj = obj;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 方法执行之前的增强
        System.out.println("方法执行之前..."+method.getName()+" 方法的参数:"+ Arrays.toString(args));

        // 方法执行
        Object result = method.invoke(obj, args);

        // 方法执行之后的增强
        System.out.println("方法执行之后..."+method.getName());
        return result;
    }
}

执行JDKProxy后的输出:

方法执行之前...add 方法的参数:[1, 2]

add方法执行...

方法执行之后...add

res : 3

Process finished with exit code 0

12.5 AOP操作术语

1、连接点 2、切入点 3、通知(增强) 4、切面

连接点

类里面哪些方法可以被增强,这些方法称为连接点

切入点

实际被真正增强的方法,成为切入点

通知

(1)实际增强的逻辑部分称为通知(增强)

(2)通知有多种类型

  • 前置通知
  • 后置通知
  • 环绕通知
  • 异常通知
  • 最终通知(finally)

切面

是动作

(1)把通知应用到切入点的过程

12.6 AOP操作演示(准备工作)

(1)Spring框架一般都是基于 AspectJ

  • 什么是AspectJ

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

(2)基于 AspectJ 实现AOP操作

  • 基于xml配置文件实现
  • 基于注解方式实现(使用)

(3)引入依赖

在这里插入图片描述

(4)切入点表达式

  • 切入点表达式作用:知道对哪个类里面的哪个方法进行增强
  • 语法结构:
    • execution( [权限修饰符][返回类型][类全路径][方法名称]([参数列表]) )

举例1:对com.qy.dao.BookDao类里面的add方法进行增强

execution(* com.qy.dao.BookDao.add(..))

权限修饰符可以是public private都可,*代表可以是任意的权限修饰符

举例2:对com.qy.dao.BookDao类里面的所有方法进行增强

execution(* com.qy.dao.BookDao.*(..))

举例1:对com.qy.dao包里的所有类里面的所有方法进行增强

execution(* com.qy.dao.*.*(..))

12.7 AOP操作演示(AspectJ注解)

1、创建类,在类里面定义方法

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

2、创建增强类(编写增强逻辑)

在增强类里面,创建方法,让不同的方法代表不同的通知类型

public class UserProxy {
    // 前置通知
    public void before(){
        System.out.println("before ......");
    }
}

3、进行通知的配置

(1)在spring配置文件中,开启注解扫描(添加context和aop命名空间)

<!--开启注解扫描-->
<context:component-scan base-package="com.qy"></context:component-scan>

(2)使用注解创建User和UserProxy对象

@Component // 被增强的类
public class User {

(3)在增强类上添加注解@Aspect

@Component // 增强类
@Aspect // 生成代理对象
public class UserProxy {

(4)在spring配置文件中开启生成代理对象

<!--开启Aspect生成代理对象-->
<!--如果在类上发现@Aspect注解,就把该类生成对代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

4、配置不同类型的通知

在增强类的里面 ,在作为通知方法上面添加通知类型注解,使用切入点表达式配置

@Component // 增强类
@Aspect // 生成代理对象
public class UserProxy {
    // 前置通知
    //@Before注解表示作为前置通知
    @Before(value = "execution(* com.qy.spring5.aopanno.User.add(..))")
    public void before(){
        System.out.println("before ......");
    }
}

5、完整代码

User类

@Component // 被增强的类
public class User {
    public void add(){
        System.out.println("add ......");
    }
}

UserProxy增强类

@Component // 增强类
@Aspect // 生成代理对象
public class UserProxy {
    // 前置通知
    //@Before注解表示作为前置通知
    @Before(value = "execution(* com.qy.spring5.aopanno.User.add(..))")
    public void before(){
        System.out.println("before ......");
    }
}

XML配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--开启注解扫描-->
    <context:component-scan base-package="com.qy"></context:component-scan>

    <!--开启Aspect生成代理对象-->
    <!--如果在类上发现@Aspect注解,就把该类生成对代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

测试类

public class test {
    @Test
    public void test(){
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("com/qy/spring5/aopanno/bean.xml");
        User user = context.getBean("user", User.class);
        user.add();
    }
}

输出:

before …
add …

6、增强类的其他类型通知

@Component // 增强类
@Aspect // 生成代理对象
public class UserProxy {
    // 前置通知
    //@Before注解表示作为前置通知
    @Before(value = "execution(* com.qy.spring5.aopanno.User.add(..))")
    public void before(){
        System.out.println("before ......");
    }

    //后置通知(也叫做最终通知)
    @After(value = "execution(* com.qy.spring5.aopanno.User.add(..))")
    public void after(){
        System.out.println("after ......");
    }

    //返回通知(如果有异常,不会执行返回通知)
    @AfterReturning(value = "execution(* com.qy.spring5.aopanno.User.add(..))")
    public void afterReturning(){
        System.out.println("afterReturning ......");
    }

    //异常通知
    @AfterThrowing(value = "execution(* com.qy.spring5.aopanno.User.add(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing ......");
    }

    //环绕通知
    @Around(value = "execution(* com.qy.spring5.aopanno.User.add(..))")
    public void before(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        System.out.println("环绕之前 ......");
        // 执行被增强的方法
        proceedingJoinPoint.proceed();
        System.out.println("环绕之后 ......");
    }
}

执行测试后的输出:

环绕之前 ......

before ......

add ......

环绕之后 ......

after ......

afterReturning ......

7、相同的切入点抽取

我们可能很多通知的切入点都是同一个方法或是同一个类,那么我们就可以把它抽取出来

UserProxy增强类:

@Component // 增强类
@Aspect // 生成代理对象
public class UserProxy {

    //抽取相同的切入点
    @Pointcut(value = "execution(* com.qy.spring5.aopanno.User.add(..))")
    public void pointdemo(){

    }

    // 前置通知
    // value值写我们抽取出来的相同切入点的方法名
    @Before(value = "pointdemo()")
    public void before(){
        System.out.println("before ......");
    }
}

8、有多个增强类对同一个方法进行增强,设置增强类的优先级

在增强类上面添加注解@Order(数字类型值),数字类型值越小优先级越高

@Component
@Aspect
@Order(1)
public class PersonProxy {

    @Before(value = "execution(* com.qy.spring5.aopanno.User.add(..))")
    public void before(){
        System.out.println("Person before ......");
    }

}

猜你喜欢

转载自blog.csdn.net/qq_45260619/article/details/106897879
今日推荐