Spring学习

spring简介

1、什么是spring?

Spring是开源的、免费的、轻量级的JavaEE框架。

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

        IOC:控制反转,把创建对象的过程交给Spring容器来管理

        AOP:面向切面,在不修改源代码的前提下进行功能的增强

官网 : Spring | Home

下载地址 : JFrog

GitHub : Spring · GitHub

2、Spring的特点

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

(2)支持AOP面向切面编程,Spring 支持面向切面的编程,从而把应用业务逻辑和系统服务分开。

(3)方便程序测试,Spring为测试提供支持,在开发环境即可生成测试

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

(5)方便进行事务操作,Spring 通过 AOP 实现了事务的统一管理,对应用开发中的事务处理提供了非常灵活的支持

(6)降低API开发难度

3、Spring的组成

(1)Spring Core(核心容器)

核心容器提供Spring框架的基本功能。核心容器的主要组件是BeanFactory,负责JavaBean的生产与管理,它是工厂模式实现的。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。

(2)Spring Context(Spring 上下文)

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

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

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

额,什么是EJB呢?通俗来讲,就是"把你编写的软件中那些需要执行制定的任务的类,不放到客户端软件上了,而是给他打成包放到一个服务器上了"。

(4)Spring DAO

Dao模块主要目的是将持久层相关问题与一般的业务规则和工具流隔离开来。Spring中的Dao提供一致的方式访问数据库,不管采用何种持久化技术,Spring都提供一致的编程模型。

(5)Spring ORM

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

(6)Spring Web

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

(7)Spring MVC

MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP。MVC模式为大型程序的开发及维护提供了巨大的便利。

IOC简介

IOC容器(Inversion of Control,控制反转)

1、获取资源的传统方式:

传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道特定资源的获取方式,增加了学习成本,同时降低了开发效率。

2、控制反转的方式获取资源:

改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接受资源的方式即可,极大的降低了学习成本,提高了开发效率。

3、DI:Dependence Injection,依赖注入

DI是IOC的另一种表述形式:即组件以一些预先定义好的方式接受来自于容器的资源注入。IOC是一种反转控制的思想,而DI是对IOC的一种具体实现。

IOC容器在Spring中的实现

IOC容器中管理的组件也叫做bean,在创建bean之前,首先要创建IOC容器,Spring提供了IOC容器的两种实现方式。

1、BeanFactory

这是IOC容器的基本实现,是Spring内部使用的接口,面向Spring本身,不提供给开发人员使用。

2、ApplicationContext

BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext,而不是底层的BeanFactory。

3、ApplicationContext的主要实现类

类名

简介

ClassPathXmlApplicationContext

通过读取类路径下的XML格式的配置文件创建IOC容器对象

FileSystemXmlApplicationContext

通过文件系统路径读取XML格式的配置文件创建IOC容器对象

ConfigurableApplicationContext

ApplicationContext的子接口,包含一些扩展方法refresh()和colse(),让ApplicationContext具有启动、关闭和刷新上下文的能力

WebApplicationContext

专门为Web应用准备,基于Web环境搭建IOC容器对象,并将对象引入存入ServletContext域中

基于XML管理Bean

基于XML管理Bean

1、导入依赖

<!--基于maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.1</version>
</dependency>
<!--junit测试-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

 2、创建HelloWorld类

public class HelloWorld {
    public void springStudy(){
        System.out.println("spring学习");
    }
}

3、在resources下创建applicationContext.xml配置文件

4、配置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="helloWorld" class="com.cyj.spring.HelloWorld"></bean>
</beans>

bean:配置一个bean对象,将对象交给IOC容器管理

        属性:

                id:bean的唯一标识,不能重复

                class:设置bean对象所对应的类型

5、测试

使用步骤:1、获取IOC容器 2、获取IOC容器中的bean对象

@Test
public void helloWorldTest(){
    //获取IOC容器
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    //获取IOC容器中的bean对象
    HelloWorld helloWorld = (HelloWorld)applicationContext.getBean("helloWorld");
    helloWorld.springStudy();    //输出 spring学习
}

IOC容器创建对象的方式

1、创建Student实体类,并添加无参和有参构造、getset方法和toString方法

必须有无参构造,因为IOC创建实例化对象是通过无参构造(反射)创建的

public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private String gender;
    //无参构造、有参构造
    //getset方法
    //toString
}

2、配置Student对应的bean对象

<bean id="studentOne" class="com.cyj.spring.Student"></bean>

3、测试

@Test
public void studentOneTest(){
    //获取IOC容器
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    //获取bean对象
    Student studentOne = (Student)applicationContext.getBean("studentOne");
    System.out.println(studentOne);
}

获取Bean对象的三方式

1、根据bean的id获取

<bean id="studentOne" class="com.cyj.spring.Student"></bean>
@Test
public void studentOneTest(){
    //获取IOC容器
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    //获取bean对象
    Student studentOne = (Student)applicationContext.getBean("studentOne");
    System.out.println(studentOne);
}

2、根据bean的类型来获取

<bean id="studentOne" class="com.cyj.spring.Student"></bean>
@Test
public void studentTwoTest(){
    ApplicationContext applicationContext= new ClassPathXmlApplicationContext("applicationContext.xml");
    Student student = applicationContext.getBean(Student.class);
    System.out.println(student);
}

这里要注意,这种情况下要求IOC容器中有且只有一个类型匹配的bean,不然会报错。

(1)首先是IOC里没有类型匹配的bean,会报如下错误:

(2)然后是有多个类型匹配的bean,例如这里有俩个bean

<bean id="studentOne" class="com.cyj.spring.Student"></bean>
<bean id="studentTwo" class="com.cyj.spring.Student"></bean>
@Test
public void studentTwoTest(){
    ApplicationContext applicationContext= new ClassPathXmlApplicationContext("applicationContext.xml");
    Student student = applicationContext.getBean(Student.class);
    System.out.println(student);
}

这种情况下会报如下的错误:

3、根据bean的id和类型获取

<bean id="studentOne" class="com.cyj.spring.Student"></bean>
@Test
public void studentThreeTest(){
    ApplicationContext applicationContext= new ClassPathXmlApplicationContext("applicationContext.xml");
    Student student = applicationContext.getBean("studentOne", Student.class);
    System.out.println(student);
}

依赖注入

依赖注入

1、配置bean对象和属性

<bean id="studentTwo" class="com.cyj.spring.Student">
    <property name="id" value="1001"></property>
    <property name="name" value="张三"></property>
    <property name="age" value="21"></property>
    <property name="gender" value="男"></property>
</bean>

property标签:通过组件类的set方法给组件对象设置属性值

name属性:指定属性名,这个属性名是get、set方法定义的,和成员变量无关

value属性:指定属性值

2、测试

@Test
public void studentSetterTest(){
    ApplicationContext applicationContext= new ClassPathXmlApplicationContext("applicationContext.xml");
    Student student = applicationContext.getBean("studentTwo", Student.class);
    System.out.println(student);
}

输出:Student{id=1001, name='张三', age=21, gender='男'} 

构造器注入

1、配置bean对象和属性

<bean id="studentThree" class="com.cyj.spring.Student">
    <constructor-arg value="1002"></constructor-arg>
    <constructor-arg value="李四"></constructor-arg>
    <constructor-arg value="女"></constructor-arg>
    <constructor-arg value="20"></constructor-arg>
</bean>

2、测试

@Test
public void studentConstructorTest(){
    ApplicationContext applicationContext= new ClassPathXmlApplicationContext("applicationContext.xml");
    Student student = applicationContext.getBean("studentThree", Student.class);
    System.out.println(student);
}

输出:Student{id=1001, name='张三', age=21, gender='男'} 

依赖注入中的特殊值处理

1、null值:例如要给age属性设为null

<bean id="studentTwo" class="com.cyj.spring.Student">
    <property name="id" value="1001"></property>
    <property name="name" value="张三"></property>
    <property name="age">
        <null />
    </property>
    <property name="gender" value="男"></property>
</bean>

2、xml实体:需要用xml实体来代替

        <用&lt代替

        >用&gt代替

为类类型属性赋值

1、引用外部bean的方式

        1、创建Student和Clazz实体类,并添加无参和有参构造、getset方法和toString方法

public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private String gender;
    private Double score;
    private Clazz clazz;
    
    //无参构造、有参构造
    //getset方法
    //toString
}
public class Clazz {
    private Integer cid;
    private String cname;
    
    //无参构造、有参构造
    //getset方法
    //toString
}

        2、配置bean对象

<bean id="studentOne" class="com.cyj.spring.Student">
    <property name="sid" value="1004"></property>
    <property name="sname" value="王五"></property>
    <property name="age" value="25"></property>
    <property name="gender" value="男"></property>
    <property name="clazz" ref="clazzOne"></property>
</bean>
<bean id="clazzOne" class="com.cyj.spring.Clazz">
    <property name="cid" value="111"></property>
    <property name="cname" value="翻斗花园一班"></property>
</bean>

        ref:引用IOC容器中某个bean的id

        字面量类型用value,类类型用ref

        3、测试

@Test
public void Test01(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    Student student = applicationContext.getBean("studentOne", Student.class);
    System.out.println(student);
}

输出  Student{sid=1004, sname='王五', age=25, gender='男', score=0.0, clazz=Clazz{cid=111, cname='翻斗花园一班'}}

2、级联方式(用的不多)

        1、配置bean对象

<bean id="studentTwo" class="com.cyj.spring.Student">
    <property name="sid" value="1004"></property>
    <property name="sname" value="王五"></property>
    <property name="age" value="25"></property>
    <property name="gender" value="男"></property>
    <!--其实就是对clazz的属性进行修改-->
    <property name="clazz" ref="clazzOne"></property>
    <property name="clazz.cid" value="112"></property>
    <property name="clazz.cname" value="翻斗花园二班"></property>
</bean>

<bean id="clazzOne" class="com.cyj.spring.Clazz">
    <property name="cid" value="111"></property>
    <property name="cname" value="翻斗花园一班"></property>
</bean>

        2、测试

@Test
public void Test02(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    Student student = applicationContext.getBean("studentTwo", Student.class);
    System.out.println(student);
}

输出  Student{sid=1004, sname='王五', age=25, gender='男', score=0.0, clazz=Clazz{cid=112, cname='翻斗花园二班'}}

内部bean的方式

        1、配置bean对象

<bean id="studentThree" class="com.cyj.spring.Student">
    <property name="sid" value="1005"></property>
    <property name="sname" value="赵六"></property>
    <property name="age" value="25"></property>
    <property name="gender" value="男"></property>
    <property name="clazz">
        <bean id="classInner" class="com.cyj.spring.Clazz">
            <property name="cid" value="112"></property>
            <property name="cname" value="翻斗花园二班"></property>
        </bean>
    </property>
</bean>

        2、测试

@Test
public void Test03(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    Student student = applicationContext.getBean("studentThree", Student.class);
    System.out.println(student);
}

输出  Student{sid=1005, sname='赵六', age=25, gender='男', score=0.0, clazz=Clazz{cid=112, cname='翻斗花园二班'}}

为数组类型的属性赋值

        1、在Student类中添加字符串数组类型的hobby属性

public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private String gender;
    private Double score;
    private Clazz clazz;
    //新添加的,也要添加相对应的get、set方法和toString方法
    private String[] hobby;
    
    //无参构造、有参构造
    //getset方法
    //toString
}

        2、配置bean对象

<bean id="studentThree" class="com.cyj.spring.Student">
    <property name="sid" value="1005"></property>
    <property name="sname" value="赵六"></property>
    <property name="age" value="25"></property>
    <property name="gender" value="男"></property>
    <property name="clazz">
        <bean id="classInner" class="com.cyj.spring.Clazz">
            <property name="cid" value="112"></property>
            <property name="cname" value="翻斗花园二班"></property>
        </bean>
    </property>
    <!--数组类型赋值-->
    <property name="hobby">
        <array>
            <!--如果是类类型的数组-->
            <!--<ref bean="要引用的bean的id">-->
            <value>吃饭</value>
            <value>睡觉</value>
            <value>打豆豆</value>
        </array>
    </property>
</bean>

        3、测试

@Test
public void Test04(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    Student student = applicationContext.getBean("studentThree", Student.class);
    System.out.println(student);
}

输出  Student{sid=1005, sname='赵六', age=25, gender='男', score=0.0, clazz=Clazz{cid=112, cname='翻斗花园二班'}, hobby=[吃饭, 睡觉, 打豆豆]}

为集合类型属性赋值

        1、在Clazz类中添加集合类型的属性students

public class Clazz {
    private Integer cid;
    private String cname;
    //新添加的,也要添加相对应的get、set方法和toString方法
    private List<Student> students;
    
    //无参构造、有参构造
    //getset方法
    //toString
}

        2、配置bean对象

<!--配置三个学生bean-->
<bean id="student1" class="com.cyj.spring.Student">
    <property name="sid" value="1004"></property>
    <property name="sname" value="张三"></property>
    <property name="age" value="25"></property>
    <property name="gender" value="男"></property>
</bean>
<bean id="student2" class="com.cyj.spring.Student">
    <property name="sid" value="1004"></property>
    <property name="sname" value="李四"></property>
    <property name="age" value="18"></property>
    <property name="gender" value="女"></property>
</bean>
<bean id="student3" class="com.cyj.spring.Student">
    <property name="sid" value="1004"></property>
    <property name="sname" value="王五"></property>
    <property name="age" value="25"></property>
    <property name="gender" value="男"></property>
</bean>

<bean id="clazzOne" class="com.cyj.spring.Clazz">
    <property name="cid" value="111"></property>
    <property name="cname" value="翻斗花园一班"></property>
    <property name="students">
        <list>
            <ref bean="student1"></ref>
            <ref bean="student2"></ref>
            <ref bean="student3"></ref>
        </list>
    </property>
</bean>

        3、测试

@Test
public void Test05(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    Clazz clazz = applicationContext.getBean("clazzOne", Clazz.class);
    System.out.println(clazz);
}

//输出
Clazz{cid=111, cname='翻斗花园一班', students=[
    Student{sid=1004, sname='张三', age=25, gender='男', score=0.0, clazz=null, hobby=null}, 
    Student{sid=1004, sname='李四', age=18, gender='女', score=0.0, clazz=null, hobby=null}, 
    Student{sid=1004, sname='王五', age=25, gender='男', score=0.0, clazz=null, hobby=null}]}

为map类型的属性赋值

        1、Student类中添加map类型的teacher属性,新建Teacher实体类

public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private String gender;
    private Double score;
    private Clazz clazz;
    private String[] hobby;
    //新添加的,也要添加相对应的get、set方法和toString方法
    private Map<String, Teacher> teacherMap;
    
    //无参构造、有参构造
    //getset方法
    //toString
}

public class Teacher {
    private Integer tid;
    private String tname;
    
    //无参构造、有参构造
    //getset方法
    //toString
}

        2、配置bean对象

<!--配置两个teacher对象-->
<bean id="teacherOne" class="com.cyj.spring.Teacher">
    <property name="tid" value="10001"></property>
    <property name="tname" value="Tom老师"></property>
</bean>
<bean id="teacherTwo" class="com.cyj.spring.Teacher">
    <property name="tid" value="10002"></property>
    <property name="tname" value="Jone老师"></property>
</bean>

<bean id="studentOne" class="com.cyj.spring.Student">
    <property name="sid" value="1005"></property>
    <property name="sname" value="赵六"></property>
    <property name="age" value="25"></property>
    <property name="gender" value="男"></property>
    <property name="clazz">
        <bean id="classInner" class="com.cyj.spring.Clazz">
            <property name="cid" value="112"></property>
            <property name="cname" value="翻斗花园二班"></property>
        </bean>
    </property>
    <property name="hobby">
        <array>
            <value>吃饭</value>
            <value>睡觉</value>
            <value>打豆豆</value>
        </array>
    </property>
    <property name="teacherMap">
        <map>
            <entry key="10001" value-ref="teacherOne"></entry>
            <entry key="10002" value-ref="teacherTwo"></entry>
        </map>
    </property>
</bean>

        3、测试

@Test
public void test01(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    Student student = applicationContext.getBean("studentOne", Student.class);
    System.out.println(student);
}

//输出  
Student{sid=1005, sname='赵六', age=25, gender='男', score=0.0, clazz=Clazz{cid=112, cname='翻斗花园二班', students=null}, 
hobby=[吃饭, 睡觉, 打豆豆], 
teacherMap={10001=Teacher{tid=10001, tname='Tom老师'}, 10002=Teacher{tid=10002, tname='Jone老师'}}}

p命名空间

        1、配置bean对象

<bean id="clazzOne" class="com.cyj.spring.Clazz">
    <property name="cid" value="111"></property>
    <property name="cname" value="翻斗花园一班"></property>
</bean>

<bean id="studentTwo" class="com.cyj.spring.Student"
      p:sid="1005" p:sname="小明" p:clazz-ref="clazzOne">
</bean>

        2、测试

@Test
public void test02(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    Student student = applicationContext.getBean("studentTwo", Student.class);
    System.out.println(student);
}

//输出
Student{sid=1005, sname='小明', age=null, gender='null', score=0.0, 
clazz=Clazz{cid=111, cname='翻斗花园一班', students=null}, hobby=null, teacherMap=null}

Spring管理数据源和引入外部属性文件

管理数据源

        1、添加pom依赖

<!--数据源-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.31</version>
</dependency>
<!--MYSQL驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
</dependency>

        2、创建配置文件spring-datasource

<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost:3306/数据库名?characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;rewriteBatchedStatements=true"></property>
    <property name="username" value="数据库用户名"></property>
    <property name="password" value="数据库密码"></property>
</bean>

        3、测试

@Test
public void test03(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-datasource.xml");
    DruidDataSource druidDataSource = applicationContext.getBean(DruidDataSource.class);
    System.out.println(druidDataSource);
}

引入外部属性文件

        1、新建jdbc.properties文件

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/数据库名?characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;rewriteBatchedStatements=true
jdbc.username=数据库用户名
jdbc.password=数据库密码

        2、修改spring-datasource配置文件

<context:property-placeholder location="jdbc.properties"></context:property-placeholder>
<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>

bean的作用域

类别

说明

singleton

单例模式,默认值

prototype

每次从容器中获取bean都返回一个新的实例

request

每次HTTP请求都会创建一个新的bean,仅适用于WebApplicationContext环境

session

同一个HTTP Session共享一个bean,不同的Session使用不同的bean,仅适用于WebApplicationContext环境

Singleton(单例模式)

        当一个bean的作用域为Singleton时,那么 IOC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建容器的同时自动创建了一个bean的对象。无论你以后是否使用它,他都存在。

        1、创建配置文件spring-scope.xml

<bean id="student" class="com.cyj.spring.Student">
    <property name="sid" value="101"></property>
    <property name="sname" value="张三"></property>
</bean>

        2、测试

@Test
public void scopeTest(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-scope.xml");
    Student student1 = ioc.getBean(Student.class);
    Student student2 = ioc.getBean(Student.class);
    System.out.println(student1 == student2);        //输出true
}

prototype(原型模式)

        当一个bean的作用域为prototype时,表示一个bean定义对应多个对象实例。每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。

<bean id="student" class="com.cyj.spring.Student" scope="prototype">
    <property name="sid" value="101"></property>
    <property name="sname" value="张三"></property>
</bean>
@Test
public void scopeTest(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-scope.xml");
    Student student1 = ioc.getBean(Student.class);
    Student student2 = ioc.getBean(Student.class);
    System.out.println(student1 == student2);        //输出false
}

Bean的自动装配

        自动装配是指根据指定的策略,在IOC容器中匹配某个bean,自动为bean中的类类型的属性或者接口类型的属性赋值。

基于xml的自动装配 byType

        根据要赋值的属性的类型,在IOC容器中匹配某个bean为属性赋值。当使用byType实现自动装配时,IOC容器中必须有且只有一个类型匹配的bean能够为其属性赋值

        1、创建User实体类和对应的dao、service、controller

public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    
    //无参构造、有参构造
    //getset方法
    //toString
}

        UserController

public class UserController {
    private UserService userService;
    public UserService getUserService() {
        return userService;
    }
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    public void addUser(){
        userService.addUser();
    }
}

        UserService

public class UserServiceImpl implements UserService {
    private UserDao userDao;
    public UserDao getUserDao() {
        return userDao;
    }
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    public void addUser() {
        userDao.addUser();
    }
}

        UserDao

public class UserDaoImpl implements UserDao {
    public void addUser() {
        System.out.println("保存成功");
    }
}

        2、配置bean对象(spring-autowired.xml)

<bean id="userController" class="com.cyj.spring.controller.UserController" autowire="byType">
    <property name="userService" ref="userService"></property>
</bean>
<bean id="userService" class="com.cyj.spring.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.cyj.spring.dao.impl.UserDaoImpl"></bean>

        3、测试

@Test
public void test01(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-autowired.xml");
    UserController userController = ioc.getBean(UserController.class);
    userController.addUser();
}

//输出    "保存成功"

基于xml的自动装配 byName

属性的name和bean的id保持一致就可以将该bean赋值给该属性

<bean id="userController" class="com.cyj.spring.controller.UserController" autowire="byType">
    <property name="userService" ref="userService"></property>
</bean>
<bean id="userService" class="com.cyj.spring.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.cyj.spring.dao.impl.UserDaoImpl"></bean>

基于注解管理bean

        和xml配置文件一样,注解本身不能执行,仅仅只是做标记。具体的功能是框架检测到到注解标记的位置,然后对这个位置按照注解标记的功能来执行具体操作

标识组件的常用注解

        1、@Component:将类标识为普通组件(下边三个是这个的具体分层)

        2、@Controller:将类标识为控制层组件

        3、@Service:将类标识为业务层组件

        4、@Repository:将类标识为持久层组件

扫描组件

<context:component-scan base-package="com.cyj.spring"></context:component-scan>

@Autowire

        1、可以标识在成员变量上,此时不需要再设置该成员变量的set方法

        2、默认通过byType的方式,在IOC容器中通过类型匹配某个bean为属性赋值

        3、如果有多个类型匹配的bean,此时会自动转换为byName的方式实现自动装配

@Controller("controller")
public class UserController {
    @Autowired
    private UserService userService;

    public void addUser(){
        userService.addUser();
    }
}
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    public void addUser() {
        userDao.addUser();
    }
}
@Repository
public class UserDaoImpl implements UserDao {
    public void addUser() {
        System.out.println("添加成功");
    }
}

        测试

@Test
public void test01(){
    ApplicationContext ioc= new ClassPathXmlApplicationContext("spring-annotation.xml");
    UserController userController = ioc.getBean(UserController.class);
    userController.addUser();
}

@Qualifier

        1、@Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配。通过注解的value属性值,指定某个bean的id,将这个bean为属性赋值

        2、@Qualifier不能单独使用。

代理模式

        代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。我们在访问实际对象时,是通过代理对象来间接访问的。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起,有利于统一维护。

静态代理

创建Calculator接口,传入两个参数分别实现加减乘除功能。

public interface Calculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}
public class CalculatorImpl implements Calculator{
    public int add(int i, int j) {
        return i + j;
    }
    public int sub(int i, int j) {
        return i - j;
    }
    public int mul(int i, int j) {
        return i * j;
    }
    public int div(int i, int j) {
        return  i / j;
    }
}

但是现在想打印日志怎么办呢,比如要打印每个方法传入的参数和返回结果,那要怎么写呢?

public class CalculatorImpl implements Calculator{
    public int add(int i, int j) {
        System.out.println("日志, 方法: add, 参数: " + i + ", " + j);
        int result = i + j;
        System.out.println("日志, 方法: add, 结果: " + result);
        return result;
    }
    public int sub(int i, int j) {
        System.out.println("日志, 方法: add, 参数: " + i + ", " + j);
        int result = i - j;
        System.out.println("日志, 方法: add, 结果: " + result);
        return result;
    }
    public int mul(int i, int j) {
        System.out.println("日志, 方法: add, 参数: " + i + ", " + j);
        int result = i * j;
        System.out.println("日志, 方法: add, 结果: " + result);
        return result;
    }
    public int div(int i, int j) {
        System.out.println("日志, 方法: add, 参数: " + i + ", " + j);
        int result = i / j;
        System.out.println("日志, 方法: add, 结果: " + result);
        return result;
    }
}

        这时就需要在每个业务方法中写相同的代码,并且会将业务代码和与业务无关的代码冗杂在一起,增加了程序的耦合性。这是就想到了用一个类专门负责处理这种场景,这就是代理类。

新建代理类CalculatorStaticProxy并实现Calculator接口。

public class CalculatorStaticProxy implements Calculator{
    private Calculator target;
    public CalculatorStaticProxy(Calculator target) {
        this.target = target;
    }
    public int add(int i, int j) {
        System.out.println("日志, 方法: add, 参数: " + i + ", " + j);
        int result = target.add(i, j);     //调用业务代码方法
        System.out.println("日志, 方法: add, 结果: " + result);
        return result;
    }
    public int sub(int i, int j) {
        System.out.println("日志, 方法: sub, 参数: " + i + ", " + j);
        int result = target.sub(i, j);     //调用业务代码方法
        System.out.println("日志, 方法: sub, 结果: " + result);
        return result;
    }
    public int mul(int i, int j) {
        System.out.println("日志, 方法: mul, 参数: " + i + ", " + j);
        int result = target.mul(i, j);     //调用业务代码方法
        System.out.println("日志, 方法: mul, 结果: " + result);
        return result;
    }
    public int div(int i, int j) {
        System.out.println("日志, 方法: div, 参数: " + i + ", " + j);
        int result = target.div(i, j);    //调用业务代码方法
        System.out.println("日志, 方法: div, 结果: " + result);
        return result;
    }
}

这样就可以将与业务无关的代码和业务代码分开来,实现了解耦。

静态代理的缺点:

        静态代理确实实现了解耦,但是由于代码都是写死的,不具备任何灵活性。比如这里的日志功能,如果其他地方也需要加日志,那就得声明更多个静态代理类,就会产生大量的重复代码,并且日志功能分散,没有统一管理。

那么怎么将日志功能集中到一个代理类中呢?将来有任何日志需求,都通过这个代理类来实现。这就需要使用动态代理技术了。

动态代理

新建ProxyFactory动态代理类

public class ProxyFactory {
    private Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }
    public Object getProxy(){
        //指定加载动态生成的代理类的类加载器
        ClassLoader classLoader = this.getClass().getClassLoader();
        //获取目标对象实现的所有接口的class对象的数组。
        Class<?>[] interfaces = target.getClass().getInterfaces();
        //设置代理类中抽象方法如何重写
        InvocationHandler handler = new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("日志,方法:" + method.getName() + ", 参数:" + Arrays.toString(args));
                Object result = method.invoke(target, args);
                System.out.println("日志,方法:" + method.getName() + ", 结果:" + result);
                return result;
            }
        };
        return Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
}

Proxy.newProxyInstance()方法需要三个参数:

        ClassLoader classLoader:指定加载动态生成的代理类的类加载器

        Class[] interfaces:获取目标对象实现的所有接口的class对象的数组。

        InvocationHandler handler:设置代理类中抽象方法如何重写

InvocationHandler静态内部类中的invoke()方法中有三个参数

        proxy:表示代理对象

        method:表示要执行的方法

        args:表示要执行方法的参数列表

method.invoke方法有两个参数:

        第一个:要代理的目标对象

        第二个:参数列表

AOP

        AOP是一种设计思想,意为面向切面编程,通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。

相关名词

1、横切关注点

从每个方法中抽取出来的同一类非核心业务。同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。

2、通知

每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法

        前置通知:在被代理的目标方法前执行

        返回通知:在被代理的目标方法成功结束后执行

        异常通知:在被代理的目标方法异常结束后执行

        后置通知:在被代理的目标方法最终结束后执行

        环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上边四种通知对应的所有位置

3、切面

封装通知方法的类就叫切面

4、目标

被代理的目标对象

5、代理

向目标对象应用通知之后创建的代理对象

6、连接点

把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x和y轴的交叉点就是连接点

7、切入点

定位连接点的方式

每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物。如果把连接点看作数据库中的记录,那么切入点就是查询记录的SQL语句

基于注解的AOP

1、新建Module,添加依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.1</version>
</dependency>

2、新建Calculator接口和CalculatorImpl实现类

public interface Calculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}
public class CalculatorImpl implements Calculator {
    public int add(int i, int j) {
        return i + j;
    }
    public int sub(int i, int j) {
        return i - j;
    }
    public int mul(int i, int j) {
        return i * j;
    }
    public int div(int i, int j) {
        return i / j;
    }
}

3、新建切面类LoggerAspect和spring配置文件aop-annotation.xml,并将切面和目标对象交给IOC容器管理

//切面
@Component
@Aspect
public class LoggerAspect {
}
//目标对象
@Component
public class CalculatorImpl implements Calculator {
    public int add(int i, int j) {
        return i + j;
    }
    public int sub(int i, int j) {
        return i - j;
    }
    public int mul(int i, int j) {
        return i * j;
    }
    public int div(int i, int j) {
        return i / j;
    }
}
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
    
    <context:component-scan base-package="com.cyj.spring"></context:component-scan>
    <!--开启基于注解的AOP-->
    <aop:aspectj-autoproxy />
</beans>

前置通知:在目标对象方法执行之前执行

@Component
@Aspect
public class LoggerAspect {
    @Before("execution(public int com.cyj.spring.CalculatorImpl.add(int, int))")
    public void beforeAdviceMethod(JoinPoint joinPoint) {
        //获取连接点所对应方法的方法名
        Signature signature = joinPoint.getSignature();
        //获取连接点所对应方法的参数列表
        Object[] args = joinPoint.getArgs();
        System.out.println("前置通知, 方法:" + signature.getName() + ", 参数:" + Arrays.toString(args));
    }
}
@Before("execution(* com.cyj.spring.*.*(..))")

这样写表示任意修饰符和返回值类型,com.cyj.spring包下的任意类中任意参数列表的任意方法

        测试

@Test
public void aopTest1(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("aop-annotation.xml");
    Calculator calculator = ioc.getBean(Calculator.class);
    calculator.add(1, 2);
}

//输出   前置通知, 方法:add, 参数:[1, 2]

切入点表达式的重用

@Pointcut("execution(* com.cyj.spring.*.*(..))")
public void pointCut(){}

//直接调用方法即可
@Before("pointCut()")
public void beforeAdviceMethod(JoinPoint joinPoint) {
    //获取连接点所对应方法的方法名
    Signature signature = joinPoint.getSignature();
    //获取连接点所对应方法的参数列表
    Object[] args = joinPoint.getArgs();
    System.out.println("前置通知, 方法:" + signature.getName() + ", 参数:" + Arrays.toString(args));
}

后入通知:在目标对象方法的finally子句中执行

@After("pointCut()")
public void afterAdviceMethod(JoinPoint joinPoint){
    //获取连接点所对应方法的方法名
    Signature signature = joinPoint.getSignature();
    System.out.println("后置通知, 方法:" + signature.getName() + ", 执行完毕");
}

返回通知:在目标对象方法返回值之后执行

@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result){
    //获取连接点所对应方法的方法名
    Signature signature = joinPoint.getSignature();
    System.out.println("返回通知, 方法:" + signature.getName() + ", 结果:" + result);
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

迟小歪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值