Spring 学习笔记

Spring 学习笔记

1、spring 概述

什么是spring

用鱼皮(一个程序员up主)给出思维导图中对spring的描述:Java 轻量级应用框架

spring 要学哪些内容

你去搜索spring该如何学习的话,或者在spring 官网中找的话,逃不开两个术语,IOC 和 AOP,所以,得学

  • IOC
  • AOP

2、IOC (控制反转)

IOC 理念推导

假设一个场景要获取用户的数据。

按照mvc三层架构,编写model层代码。

1、UserDao接口

package com.company.dao;

public interface UserDao {
    //获取用户测试
    public void getUser();
}

2、UserDaoImpl 实现类

package com.company.dao;

public class UserDaoImpl implements UserDao{
    @Override
    public void getUser() {
        System.out.println("模拟实现获取了用户的数据");
    }
}

3、UserService 接口

package com.company.service;

public interface UserService {
    public void getUser();
}

4、UserServiceImpl 业务实现类

package com.company.service;

import com.company.dao.UserDao;
import com.company.dao.UserDaoImpl;

public class UserServiceImpl implements UserService{

    private UserDao userDao = new UserDaoImpl();

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

5、代码测试

// 使用junit单元测试
@Test
public void test() {
    new UserServiceImpl().getUser();
}

6、结果演示
在这里插入图片描述

7、问题提出,思考,如果我们添加了一个UserDaoMySqlImpl类的话

package com.company.dao;

public class UserDaoMySqlImpl implements UserDao{
    @Override
    public void getUser() {
        System.out.println("默认获得了MySql用户的数据");
    }
}

程序该如何调度这个类里面的方法执行呢? 没错,在UserServiceImpl里面改变userDao的指向

package com.company.service;

import com.company.dao.UserDao;
import com.company.dao.UserDaoImpl;

public class UserServiceImpl implements UserService{

    private UserDao userDao = new UserDaoMysqlImpl();

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

结果演示:
在这里插入图片描述

如果程序有成千上万个Dao的实现类呢?而且不同地方指定不同的实现类呢?难道我们要每次跑到ServiceImpl里面去修改指向吗?

那肯定是不能的,代码量十分大,修改一次的成本是很昂贵的,那有什么办法可以较好的解决这个问题呢?见如下代码

package com.company.service;

import com.company.dao.UserDao;
import com.company.dao.UserDaoImpl;
import com.company.dao.UserDaoMysqlImpl;
import org.junit.Test;

public class UserServiceImpl implements UserService{
   private UserDao userDao;

   // 在这里添加一个setter方法
   public void setUserDao(UserDao userDao) {
       this.userDao = userDao;
   }

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


    @Test
    public void test() {
        UserServiceImpl userService = new UserServiceImpl();
        // userService.setUserDao(new UserDaoImpl());
        userService.setUserDao(new UserDaoMysqlImpl());
        userService.getUser();
    }
}

IOC 程序从主动的创建对象 变成了 被动的接收对象

在这里插入图片描述

这种思想,从本质上解决了问题,我们程序员不需要再去管理对象的创建了,系统的耦合性大大降低,可以更加专注的去实现业务。

这是IOC的原型!

还有课外阅读,有不明白IOC的小伙伴可以看下。

大牛文章写的很不错,这里直接贴出源地址。IOC 理解:https://zhuanlan.zhihu.com/p/64001753

我这里只讲述自己阅读文章时觉着很恍然大悟的地方。

而在实际应用中,myApp.jar中往往包含了程序主流程。而主流程一旦完成,就应该保持相对稳定。如果有改变,也应该通过扩展的方式。这也就是架构设计中著名的“开闭原则”,即主业务对修改关闭,对扩展开放。而使用Spring就可以达到这样的目的。

3、第一个spring程序

1、导包 使用spring开发web项目需要导入的包

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.9</version>
</dependency>

2、编写applicationContext.xml 配置文件

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

    <!-- bean的本质就是创建对象 -->
    <!-- 可以理解为根据Hello这个类模板创建了一个hello对象 -->
    <!-- 上面那番描述像java中的什么,是不是反射,所以ioc底层实现是反射 -->
    <bean id="hello" class="com.company.pojo.Hello">
        <property name="str" value="Spring" />
    </bean>
</beans>

3、代码执行

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

public class MyTest {
    public static void main(String[] args) {
        // 获取Spring的上下文对象(同时也是IOC容器)
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Hello hello = (Hello)context.getBean("hello");
        // 这个等同于上面这句话
        //Hello hello = context.getBean("hello",Hello.class);
        System.out.println(hello);
    }
}

4、结果演示:
在这里插入图片描述

4、IOC 创建对象

1、首先创建一个实体类

package com.company.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

// 使用lombok 需要导入lombok的依赖, 具体依赖请搜索maven repository中的lombok
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String name;
}

2、编写application.xml 文件

使用无参构造创建对象

<!-- 本质上是使用User类的无参构造创建对象 -->
<bean id="user" class="com.company.pojo.User"></bean>

改写User类中的无参构造

package com.company.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor

public class User {
    private String name;

    public User() {
        this.name = "qizai";
    }
}

结果输出
在这里插入图片描述

使用有参构造创建对象

<bean id="user" class="com.company.pojo.User">
    <constructor-arg name="name" value="qizai"/>
</bean>

代码测试

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

public class MyTest {
    public static void main(String[] args) {
        // 获取Spring的上下文对象(同时也是IOC容器)
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        User user = context.getBean("user", User.class);

        System.out.println(user);
    }
}

结果演示:
在这里插入图片描述

这个标签里面还有几个属性讲解一下

其实就是思考一下,已经指定为有参构造了,我们要传参,如何定位到类的成员变量

  • 第一种,像上面那样,指定成员变量的名字

  • 第二种,使用下标确定成员变量

    • 代码演示

    • <bean id="user" class="com.company.pojo.User">
          <constructor-arg index="0" value="qizaiByIndex"/>
      </bean>
      
    • 结果演示

    • 在这里插入图片描述

  • 第三种,使用参数类型(不具有唯一性,谨慎使用)

    • 代码演示

    • <bean id="user" class="com.company.pojo.User">
          <constructor-arg type="java.lang.String" value="qizaiByType"/>
      </bean>
      
    • 结果演示

    • 在这里插入图片描述

  • 拓展

    • 如果使用的是对象赋值,要使用ref属性而不是value属性

      • <!--第四种 : 通过使用ref引用,引用的是一个bean里面创建的类,觉得抽象的话看官方文档的举例-->
        <constructor-arg ref="xxx" />
        
    • 如果使用的只有一个参数的话,可以只写value属性

      • <bean id="user" class="com.company.pojo.User">
            <constructor-arg value="qizaiByDefault" />
        </bean>
        
      • 在这里插入图片描述

5、依赖注入

在官网的文档中有这么一句话: IOC is also known as dependency injection (DI) —> [ IOC 也称之为依赖注入 ]
在这里插入图片描述

两者的关系要看自己怎么理解,我的理解是IOC是创建一个对象放入到IOC容器中,用来实现主程序中被动的接收对象,遵从"开闭原则"(主要是创建对象和被IOC容器托管对象),而依赖注入讲解的基本上就是如何创建对象。所以我很是赞同狂神说的DI(依赖注入)是实现IOC的方法

实现依赖注入的几种方式

  • 基于构造器的依赖注入
  • 基于setter方法的依赖注入

基于xxx实现的依赖注入,意味着如果类中没有这个构造器方法或者setter方法,就不能执行。

1、基于构造器的依赖注入

上面上一小节中讲述的就是这个

2、基于setter方法的依赖注入

给出官网文档中的一句话:

Setter-based DI is accomplished by the container calling setter methods on your beans after invoking a no-argument constructor or a no-argument static factory method to instantiate your bean.

# 基于Setter的DI是在调用无参数构造函数或无参数静态工厂方法实例化bean之后,通过容器调用bean上的setter方法来完成的。

如何理解,我们在代码中去解释,现在要注意里面的两个点

  • 调用无参数构造函数或无参数静态工厂方法
  • 通过容器调用bean上的setter方法来完成的

代码演示:

<!-- <bean id="user" class="com.company.pojo.User"></bean> 
	 这个怎么理解 是不是我们说的无参构造函数
	 <property name="name" value="qizaiBySetter" />
     这个怎么理解 是不是就是上面所说的调用bean上的setter方法来完成的
--> 


<bean id="user" class="com.company.pojo.User">
    <!-- 要注意的点, name里面的内容不能乱填 -->
    <!-- 由于使用的lombok中的@Data注解, 会给我们的实体类生成一个setName(String):void方法,这个name属性填写的是setName中的Name首字母小写后的字符串,也就是name-->
    <!-- 觉着抽象的,我在后面一点点位置中贴上了官方代码 -->
    <property name="name" value="qizaiBySetter" />
</bean>

结果演示:
在这里插入图片描述

官方文档代码

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="misterkaoli"/>
</bean>

测试环境搭建

package com.company.pojo;

import java.util.*;


public class Student {
    //测试基本数据类型依赖注入 type
    private String name;
    //测试引用类依赖注入 ref
    private Address address;
    //测试数组依赖注入
    private String[] books;
    //测试List集合依赖注入
    private List<String> hobbys;
    //测试Map依赖注入
    private Map<String,String> card;
    //测试set集合依赖注入
    private Set<String> games;
    //测试空指针依赖注入
    private String wife;
    //测试配置类依赖注入
    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> getHobbys() {
        return hobbys;
    }

    public void setHobbys(List<String> hobbys) {
        this.hobbys = hobbys;
    }

    public Map<String, String> getCard() {
        return card;
    }

    public void setCard(Map<String, String> card) {
        this.card = card;
    }

    public Set<String> getGames() {
        return games;
    }

    public void setGames(Set<String> games) {
        this.games = games;
    }

    public String getWife() {
        return wife;
    }

    public void setWife(String wife) {
        this.wife = wife;
    }

    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) +
                ", hobbys=" + hobbys +
                ", card=" + card +
                ", games=" + games +
                ", wife='" + wife + '\'' +
                ", info=" + info +
                '}';
    }
}

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id = "address" class="com.company.pojo.Address">
        <property name="address" value="hunan in China"/>
     </bean>

    <bean id="student" class="com.company.pojo.Student">
        <!--第一种,基本数据类型-->
        <property name="name" value="student1"/>

        <!--第二种,引用数据类型-->
        <!--第一个address是student类的成员变量,第二个address是定义的address对象-->
        <property name="address" ref="address"/>

        <!--第三种,数组-->
        <property name="books">
            <array>
                <value>红楼梦</value>
                <value>西游记</value>
                <value>水浒传</value>
                <value>三国演义</value>
            </array>
        </property>

        <!--第四种,list-->
        <property name="hobbys">
            <list>
                <value>听歌</value>
                <value>敲代码</value>
                <value>看电影</value>
            </list>
        </property>

        <!--第五种,Map-->
        <property name="card">
            <map>
                <entry key="id" value="123456789" />
                <entry key="sid" value="123456" />
            </map>
        </property>

        <!--第六种,Set-->
        <property name="games">
            <set>
                <value>游戏1</value>
                <value>游戏2</value>
                <value>游戏3</value>
            </set>
        </property>

        <!--null-->
        <property name="wife">
            <null/>
        </property>

        <!--Properties-->
        <property name="info" >
            <props>
                <prop key="username">root</prop>
                <prop key="password">123456</prop>
            </props>
        </property>
    </bean>
</beans>
3、拓展

p 依赖注入

就是一种简洁的setter注入方式,这里贴官方的,比较好理解

这是setter注入

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="misterkaoli"/>
</bean>

这是p依赖注入

注意 要导入p命名空间

<!-- 注意导入beans 下面这些命名空间 -->
<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">

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="root"
        p:password="misterkaoli"/>

</beans>

c 依赖注入

则是对有参构造的依赖注入的简化版

还是与P命名空间一样的注意点,要导入C的命名空间

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

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>

    <!-- traditional declaration with optional argument names -->
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg name="thingTwo" ref="beanTwo"/>
        <constructor-arg name="thingThree" ref="beanThree"/>
        <constructor-arg name="email" value="something@somewhere.com"/>
    </bean>

    <!-- c-namespace declaration with argument names -->
    <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

</beans>

6、spring 容器配置

给出之前创建Spring IOC容器的代码

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

通过这个配置文件获取的IOC容器,所以我们对容器进行配置的话就是配置这个配置文件。

1、别名
<!-- 别名 给创建的bean起一个别名 -->
<alias name="user" alias="user2" />

<bean id="user" class="com.company.pojo.User" p:name="qizaiByPDI"></bean>

<!-- 等价于 <bean id="user" class="com.company.pojo.User" name="user2" />-->

调用的时候可以使用user,也可以使用user2

2、bean的配置
<!--
        id : bean的唯一标识符,就是getBean()的参数名字
        class : bean 对象所对应的完全限定名,包名 + 类名
                               非完全限定类名 : 类名
        name : 也是别名,而且name还可以取多个别名,功能与alias相同
        -->
<bean id="userT" class="com.company.pojo.UserT" name="userT2,u2">
    <property name="name" value="UserT2"/>
</bean>
3、import

在这里插入图片描述

4、bean的作用域

六种作用域,重点掌握前面两种

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hg9hFmft-1647672738861)(G:/PrtSc/image-20210907151156075.png)]

单例模式:只有一个实例被创建,默认就是创建的单例模式

<!--xml配置-->
<!--单例模式 : 只有一个实例被创建,默认就是创建的单例模式-->
<!--下面的显示scope是显示的定义为单例模式-->
<bean id = "user2" class="com.company.pojo.User" c:name="hello2" c:age="18" scope="singleton"/>

代码测试

import com.company.pojo.Student;
import com.company.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

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

    @Test
    public void test()
    {
        ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml");
        User user = (User) context.getBean("user2");
        User user2 = context.getBean("user2", User.class);
        user.setAge(19);
        System.out.println(user);
        System.out.println(user2);
    }
}

测试结果:

null执行了无参构造函数
hello2执行了有参构造函数
User{name='hello2', age=19}
User{name='hello2', age=19}

结果分析:

1、首先创建了一个user2对象,执行了一次构造函数

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"
       xmlns:p="http://www.springframework.org/schema/p"
       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">

    <bean id="user" class="com.company.pojo.User" p:name="hello" p:age="18" />

    <!--原型模式 : 每次创建都是一个新的实例-->
    <!--下面的显示scope定义的是原型模式-->
    <bean id = "user2" class="com.company.pojo.User" c:name="hello2" c:age="18" scope="prototype"/>
</beans>

代码测试

import com.company.pojo.Student;
import com.company.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

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

    @Test
    public void test()
    {
        ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml");
        User user = (User) context.getBean("user2");
        User user2 = context.getBean("user2", User.class);
        user.setAge(19);
        System.out.println(user);
        System.out.println(user2);
    }
}

测试结果:

null执行了无参构造函数
hello2执行了有参构造函数
hello2执行了有参构造函数
User{name='hello2', age=19}
User{name='hello2', age=18}

结果分析:

1、首先创建了两个user2对象,执行了两次构造函数

2、是两个对象,而不是一个对象,一个对象字段的修改对另外一个对象字段没有影响

其他的作用域,了解即可

在这里插入图片描述

7、Bean的自动装配

官方上称这种方式为Annotation-based Container Configuration 来配置bean的元数据

场景模拟

一个人养了一只宠物猫,一条宠物狗

Cat.java

package com.company.pojo;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Cat {
    private String name;

    public void shout() {
        System.out.println("喵喵喵~");
    }
}

Dog.java

package com.company.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dog {
    private String name;

    public void shout() {
        System.out.println("汪汪汪~");
    }
}

People.java

package com.company.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class People {
    private String name;
    private Dog dog;
    private Cat cat;
}	

ApplicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    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">

    <bean id="people" class="com.company.pojo.People" >
        <property name="name" value="qizai" />
        <property name="dog" ref="dog" />
        <property name="cat" ref="cat" />
    </bean>
</beans>

代码测试:

import com.company.pojo.Hello;
import com.company.pojo.People;
import com.company.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        // 获取Spring的上下文对象(同时也是IOC容器)
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        People people = context.getBean("people", People.class);

        people.getCat().shout();
        people.getDog().shout();
    }
}

结果演示:
在这里插入图片描述

byName 类型的 自动装配

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

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

    <!-- 注意的地方就是这个byName里面一定是根据Setter方法set后面的名字进行匹配的 -->
    <!-- 使用lombok生成的setter方法是 setDog(Dog):void   setCat(Cat):void  -->
    <!-- 然后就会去寻找IOC容器中name为dog和name为cat的bean -->
    <bean id="people" class="com.company.pojo.People"  autowire="byName">
        <property name="name" value="qizai" />
    </bean>
</beans>

byType 类型的 自动装配

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

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

    <!-- 注意的地方就是byType是根据成员变量的类型来绑定的 -->
    <!-- 比如成员变量dog是Dog类型的,cat是Cat类型的  -->
    <!-- 然后就会去寻找IOC容器中type为Dog和type为Cat的bean -->
    <!-- 如果定义了多个Cat类型的bean的话,就会报错,有多个候选值 -->
    <bean id="people" class="com.company.pojo.People"  autowire="byType">
        <property name="name" value="qizai" />
    </bean>
</beans>

使用注解的方式进行自动装配 @Autowired

People.java

package com.company.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class People {
    private String name;
    @Autowired
    // @Autowired 会自动使用byName和byType这两种类型
    private Dog dog;
    @Autowired
    private Cat cat;
}

ApplicationContext.xml

切记 要开启注解支持以及导入Context的命名空间

<?xml version="1.0" encoding="UTF8"?>
<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/>

    <!--id的名字一定要和字段名一致-->
    <!--使用了注解开启bean的装配后依赖注入变得很干净-->
    <bean id = "dog" class="com.company.pojo.Dog"/>
    <bean id = "cat" class="com.company.pojo.Cat" />
    <bean id = "cat2" class="com.company.pojo.Cat" />

    <bean id = "people" class="com.company.pojo.People">
        <property name="name" value="七仔"/>
    </bean>

</beans>

@Autowired 使用注意

  • 使用位置
    • 在类的字段名上
    • 在类的set方法上
  • 使用 @AutoWired 之后的改变
    • set方法可以不写

@Qualifier 注解

@Autowired 默认的装配模式的是byName,如果Spring容器中有多个相同类型且不同id的被引用的bean时,ByName方法和ByType都不能使用了,因为Spring容器不知道给引用其他bean的bean赋予什么值。这时候我们可以使用 @Qualifier 注解,指定属性指定的bean是什么,这时候就可以解决问题了

ApplicationContext.xml

<?xml version="1.0" encoding="UTF8"?>
<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/>

    <!--id的名字一定要和字段名一致-->
    <!--使用了注解开启bean的装配后依赖注入变得很干净-->
    <bean id = "dog" class="com.company.pojo.Dog"/>
    <bean id = "cat1" class="com.company.pojo.Cat" />
    <bean id = "cat2" class="com.company.pojo.Cat" />

    <bean id = "people" class="com.company.pojo.People">
        <property name="name" value="七仔"/>
    </bean>

</beans>

People.java

package com.company.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class People {
    private String name;
    @Autowired
    private Dog dog;
    @Autowired
    @Qualifier("cat1")
    // 指定Cat进行装配
    private Cat cat;
}

@Autowired 总结

  • @Autowired 自动装配的底层也是基于byName方式或者byType方式完成的,

  • 默认是byName方法

  • 如果byName方法弄不出来(没有保证byName的唯一性)就会使用byType方式

  • byName方式的优先级高于byType方式。

  • 如果byName 和 byType都不能保证唯一性的话,就会报错

@Resource注解

上面的@Autowired是spring里面的,你只能在spring中使用,而这个@Resource 则是java里面的自带的注解。

1、使用@Resource 装配的开发步骤

  • 导包
<!--java annotation 依赖的注解-->
<!-- https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api -->
<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

2、装配方式与@Autowired 一致,就不加赘述了,首先byType,然后再是byName

3、@Resource 使用修改属性值来指定字段装配的是哪个bean
s

8、使用注解开发

官方上描述这种方式为Java-based Container Configuration,
配置bean的元数据方法之一

官方演示:

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

等同于

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

如果使用这种方式进行的配置,那么获取IOC容器的方法是

import com.company.config.MyConfig;
import com.company.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        // 这里我们是使用注解实现注入,而不是xml,故使用的类应该为注解配置上下文
        // 使用这个API AnnotationConfigApplcationContext
        // 然后使用这个不仅可以将@Configuration里面的注册到IOC容器中
        // 还可以注册@Component组件
        // 要想添加有三种实现方式
        // 第一种,全部作为 new AnnotationConfigApplicationContext的参数,这个参数类型是一个可变长参数
        // 第二种,使用实例化对象中的register方法
        // 第三种, 在@Configuration中添加@ComponentScan()注解
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        User user = (User) context.getBean("getUser");
        System.out.println(user);
    }
}

@Value 属性注入

可以直接在成员变量中注入,也可以在成员变量的set方法上注入,只能注入基本数据类型,引用数据类型要使用@AutoWired,其他复杂一点的map,list,properties最好使用xml进行注入。

这个属性注入注解只适用于简单的属性注入,复杂的的像map,list,properties等都不适用,最好使用xml进行注入,结构更加清晰

package com.company.pojo;

import org.junit.validator.ValidateWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


// 等价于 <bean id="user" class="com.company.pojo.User"/>
// component 组件
@Component
public class User {

    // <property name="name" value="user2" />
    @Value("user2")
    public String name;

    public String getName() {
        return name;
    }

    // <property name="name" value="user2" />
    @Value("user2")
    public void setName(String name) {
        this.name = name;
    }

    public void User()
    {
        System.out.println("user被装配了");
    }
}

衍生注解

1、@Component的一些衍生注解
在这里插入图片描述

作用域

即bean是单例模式 还是 原型模式

// Scope 注解,标识bean是单例模式还是原型模式
@Scope("singleton")
public class User {

    // <property name="name" value="user2" />
    @Value("user2")
    public String name;

    public void User()
    {
        System.out.println("user被装配了");
    }
}

拓展

1、@AutoWired 的 require属性
在这里插入图片描述

2、@Nullable 注解
在这里插入图片描述

9、Spring 实现 AOP

1、AOP概述

AOP : Aspect-oriented Programming (面向切面编程)

AOP将系统分为核心关注点和横切关注点两部分。核心关注点就是主业务流程,横切关注点就是提到的“方面”。

主业务流程归根到底是一个java方法,而且是对象的方法。在AOP中被称为被通知或被代理对象POJO。AOP的作用就是将核心关注点和横切关注点组合起来,术语叫做“增强”。最后实际用的是增强后的代理对象。

AOP中的一些概念

  • Aspect : 切面,是一个类,里面包含了要切入的方法。通常使用@Aspect注解的常规类或者适应常规类(基于模式的方法)来实现的。通常由PointCut 和 Advice方法组成。
  • join point:方法执行的地方,比如在PointCut前 或者 在PointCut后执行
  • Advice:通知,就是切面中的方法,也是通常在join point处执行的方法。
  • Pointcut: 要切入方法的点

举个例子理解一下:以增删改查为例,不改变原有代码的逻辑往里面增加日志输出的功能

  • Aspect :将日志功能抽象成为了一个类,切面一般是一个类
  • Advice : 日志功能
  • PointCut : 要进行日志增强的位置
  • join point :通常是PointCut 前后

Spring AOP 有如下增强

  • Before advice: 在PointCut的前面进行增强
  • After returning advice: 在PointCut 的后面进行增强 (通常没有报异常)
  • After throwing advice: 在抛出异常的地方进行增强
  • After (finally) advice: 顾名思义,在异常返回的时候增强
  • Around advice: 在PointCut的周围进行增强
2、实现AOP的几种方式

使用@AspectJ的方式实现AOP

首先,使用这个AOP织入功能,我们需要导入AspectJ的依赖包

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
    <scope>runtime</scope>
</dependency>

然后我们需要配置支持使用@AspectJ注解,两种方式

  • 使用 java @Configuration 的方式

    • @Configuration
      @EnableAspectJAutoProxy
      public class AppConfig {
      
      }
      
  • 使用 xml 配置方式

    • <aop:aspectj-autoproxy/>
      

定义一个Aspect 切面, 也和上面一样有两种方式

  • 使用 xml 配置方式

    • <bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
          <!-- configure properties of the aspect here -->
      </bean>
      
  • 使用 注解的方式

    • package org.xyz;
      import org.aspectj.lang.annotation.Aspect;
      
      @Aspect
      public class NotVeryUsefulAspect {
      
      }
      
    • 但是这种方式注册的对象不会装配到IOC容器中,我们需要手动注入

      • 使用@Component,然后在@Configuration 中使用@ComponentScan()
      • 在@Configuration 中使用 @Bean 的方式注册一个

定义PointCut 并使用注解的方式实现Join Cut

package com.company.aop;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

// 使用注解实现AOP功能
// 这个不会自动注入到IOC容器中, 需要我们手动注入
@Aspect
public class AnnoAspect {

    @Before("execution(* com.company.service.UserServiceImpl.*(..))")
    public void beforeNote() {
        System.out.println("=============");
        System.out.println("在方法执行前输出");
    }

    @After("execution(* com.company.service.UserServiceImpl.*(..))")
    public void afterNote() {
        System.out.println("在方法执行后输出");
        System.out.println("=============");
    }
}

UserServiceImpl.java

package com.company.service;

public class UserServiceImpl {

    public void add() {
        System.out.println("插入操作");
    }

    public void remove() {
        System.out.println("移除操作");
    }

    public void update() {
        System.out.println("更新操作");
    }

    public void query() {
        System.out.println("查询操作");
    }
}

代码测试:

import com.company.config.Myconfig;
import com.company.pojo.Hello;
import com.company.pojo.People;
import com.company.pojo.User;
import com.company.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        // 获取Spring的上下文对象(同时也是IOC容器)
        ApplicationContext context =  new ClassPathXmlApplicationContext("applicationContext.xml");
        UserServiceImpl userService = context.getBean("userService", UserServiceImpl.class);

        userService.add();
        userService.query();
        userService.remove();
        userService.update();
    }
}

xml 配置

<!--方式三-->
<bean id="annoAspect" class="com.company.aop.AnnoAspect" />
<!--开启注解支持 默认是基于接口实现的AOP(proxy-target-class="false") 还有一种就是通过类实现的AOP cglib-->
<aop:aspectj-autoproxy />


结果输出:
在这里插入图片描述

使用Spring的方式实现AOP

这里面会使用到xml配置,要使用xml配置,就要使用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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>

有两个API接口 ,

  • AfterReturningAdvice 顾名思义,在要切入点方法后面进行切入
  • MethodBeforeAdvice 顾名思义,在切入点方法前面进行切入

步骤实现

  • 首先创建两个实现了上面那两个接口的类
  • 创建这两个类的bean对象
  • 实现AOP配置

实现配置代码

<!--方式一 : 使用原生Spring API接口-->
<!--配置AOP : 需要导入AOP的约束-->
<aop:config>
    <!--配置切入点 即横切到哪些方法里面去执行自己的代码
		expression 里面配置的就是方法名,也是切入点,切入点一般是一个方法
		第一个 * 代表 方法的返回值 * 代表任意返回值
        com.company.service.UserServiceImpl 切入点所属的类
		com.company.service.UserServiceImpl.*() 切入点所属的类下面的所有方法,
		..代表切入点所属的类下面的所有方法下的任意参数
        -->
    <aop:pointcut id="pointcut" expression="execution(* com.company.service.UserServiceImpl.*(..))"/>

    <!--执行环绕增加! 什么是执行环绕, 就是在执行代码的上下添加其他可执行的方法-->
    <!--在id为pointcut的方法里面使用afterlog类 afterlog 是一个实现了AfterReturningAdvice的接口,然后这个接口要实现某个方法-->
    <aop:advisor advice-ref="afterlog" pointcut-ref="pointcut" />
    <aop:advisor advice-ref="beforeAdvice" pointcut-ref="pointcut" />
</aop:config>

自定义类来实现AOP(使用切面 )

要了解切面的含义,切面就是横切关注点被模块化的类,就是有一个类,这个类里面包含了所有你要添加的功能。

由于自己写的普通方法没有上面那两个接口的功能(在切入点前后进行插入),而且我们把方法写进了一个类里面,故这次要配置的是切面,大同小异,无非要自己再配置切面里面的内容

自定义类

package com.company.diy;

public class DiyPointCut {

    public void before()
    {
        System.out.println("========方法执行前========");
    }

    public void after()
    {
        System.out.println("========方法执行后========");
    }
}

配置切面

那么怎么判断是before 方法呢?哪个是after方法呢? 简单,进行绑定不就好了

<!--方式二 : 自定义类-->
<aop:config>
    <aop:aspect ref="diypointcut">
        <!--切入点-->
        <aop:pointcut id="point" expression="execution(* com.company.service.UserServiceImpl.*(..))"/>

        <!--通知绑定 要将自定义类里面的方法和切入点进行绑定-->
        <aop:before method="before" pointcut-ref="point"/>
        <aop:after method="after" pointcut-ref="point" />
    </aop:aspect>
</aop:config>

N、小结

不是总结,就提出一些要区分开来的知识点

IOC 配置元数据的几种方式

1、使用xml配置元数据

<bean id="xxx" class="xxx">
</bean>

2、使用注解配置元数据

自动装配里面使用的到的注释@Resource,@Autowired,@Qualifier

注意 : 注释注入在XML注入之前执行。因此,XML配置覆盖了通过这两种方法连接的属性的注释。

@Required

位置:放在Setter方法上,

功能:标明这个属性必须填充,如果未填充,则会引发异常

@Autowired

位置:放在setter方法上以及引用属性上

功能:实现自动填充

@Primary

位置:放在setter属性上,

功能:@Autowired带来的可能有多个候选值的情况下,优先使用有@Primary注解标识的bean实例

@Qualifier

位置:放在setter属性上

功能:@Autowired带来的可能有多个候选值的情况下,优先使用有@Qualifier注解指定的bean实例

@Resource

位置:放在setter属性上或者字段上

功能:

3、使用java配置元数据

  • @Configuration
  • @Bean
  • @Import

4、总结
这几种方法都是可以配置bean的元数据,也只会做这一件事情,不会将bean注入到IOC容器中,要注入到IOC容器中需要另外的配置。比如,编写xml的时候,intellij idea 会要我们绑定IOC容器,没有就创建。而像自动注入,和使用java_base Configuration 都需要自己Register到IOC容器中,或者使用@Component 和 @ComponentScan注解。

学习方式

没有基础的建议首先去找些视频观看,做项目,达到基本上能用这个框架了(虽然有些地方不知道为什么要这么弄没关系)。

复习的时候根据官网,复习自己之前记录的笔记,在官网上找到对应的知识,然后看下相关解释,为什么这么用,还能怎么用?

可能阅读的过程有些难受(一般都是英文的),但是也要耐下心来,耐住性子。慢慢看不急,有收获就行。建议在电脑上下载一个网易有道词典(可以小窗查词,划词查词),因为网页翻译整篇文章有些术语可能是直译中文,反倒妨碍阅读

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值