⭐⭐Spring总结(全面+详细+易懂+新手)⭐⭐

目录

1.概述

1.1Spring是什么

1.2Spring Framework特点

2.入门

2.1构建模块

2.2引入依赖

2.3创建对象

2.4创建配置文件

2.5测试

2.6总结

2.7启用Log4j2日志框架 

2.7.1log4j2的概述 

2.7.2引入依赖

2.7.3加入日志配置文件

3.Ioc容器

3.1控制反转(ioc)

3.2依赖注入

3.3IOC在spring的实现

3.4xml管理bean

3.4.1获取bean

3.4.2依赖注入_setter注入

3.4.3依赖注入_构造器注入

3.4.4特殊值的处理

3.4.5为对象属性赋值

方式一:引入外部bean

方式二:内部bean

方式三:级联属性赋值

3.4.6为数组类型属性赋值

3.4.7集合类型属性注入

1.为list集合类型属性赋值

2.为map集合类型属性赋值

 3.引用集合类型的bean

 4.p命名空间

5.引入外部属性文件

3.4.8bean作用域

3.4.9bean的生命周期

3.4.10FactoryBean

3.4.11自动装配

 3.5注解管理bean

3.5.1开启组件扫描

3.5.2使用注解定义bean

3.5.3@Autowired注入

①属性注入

②set注入

③构造方法注入

④形参注入

⑤只有一个构造函数,无注解

⑥@Autowried注解和@Quailfier注解联合

3.5.4@Resource注入

3.5.5全注解开发

4.手写IOC

4.1回顾java反射

4.2ioc手写

1.创建@Bean注解,@Di注解:

2.自定义ApplicationContext接口:

3.创建ApplicationContext接口的实现类:(⭐⭐⭐)

5.面向切面:AOP

5.1代理模式

5.1.1静态代理

5.1.2动态代理

5.2AOP

5.2.1概念

5.2.2术语

1.横切关注点

2.通知(增强)

3.切面

4.目标

5.代理

6.连接点

7.切入点

5.2.3注解Aop

1.引入依赖:

2.切面类 

3.切入点表达式

4.重用切入点表达式

5.2.4xmlAoP

1.spring配置文件:

2.切面类:

6.整合junit

6.1整合junit5

6.1.1引入依赖

6.1.1测试

6.2整合junit4

6.2.1引入依赖

6.2.2测试

7.事务

7.1JdbcTemplate

1.引入依赖

2.创建jdbc资源文件

3.创建spring配置文件

4.注入JdbcTemplate

7.2声明式事务

7.2.1基本概念

1.什么是事务

2.事务的特性

7.3基于注解的声明式事务

7.3.1无添加事务 

7.3.2加入事务

1.添加事务配置

2.添加注解

7.3.3@Transactional属性

1.只读

2.超时

3.回滚策略

4.隔离级别 

5.传播行为 

7.3.4全注解事务

 7.4基于xml的声明式事务

8.资源操作

 8.1Resource接口

1.UrlResource⭐

2.ClassPathResource⭐

3.FileSystemResource⭐

4.ServletContextResource

5.InputStreamResource

6.ByteArrayResource

8.2Resourceloader接口

8.3ResourceLoaderAware接口

8.4使用Resource作为属性

8.5应用程序下上文和资源路径

9.国际化:i18n

9.1java国际化

1.配置文件命名规则

9.2Spring国际化

10.数据校验Validation

方式一: Validator接口

1.引入依赖

2.创建实体类

3.创建类实现接口和方法

4.测试:

方式二:Bean Validation注解实现

1.创建配置类

2.创建实体类

3.创建校验器

4.测试

方式三:基于方法实现校验

1.创建配置类

2.创建实体类

3.创建校验器

4.测试

方式四:自定义注解校验

10.提前编译AOT


1.概述

1.1Spring是什么

Spring是一款主流的Java EE轻量级开源框架 Spring由"Spring之父"Rod Johnson 提出并创立,其目的是用于简化Java企业级应用的开发难度和开发周期。Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring框架除了自己提供功能外,还提供整合其他技术和框架的能力。

1.2Spring Framework特点

1.非侵入式

2.控制反转

3.面向切面编程

4.容器

5.组件化

6.一站式

2.入门

2.1构建模块

注意更改为自己的maven~

2.2引入依赖

    <dependencies>
        <!--spring context依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.2</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.10.0</version>
        </dependency>
    </dependencies>

2.3创建对象

public class User {
    public void test(){
        System.out.println("hello 小张~");
    }
}

2.4创建配置文件

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


    <!--完成user对象创建
        bean标签:
             id属性:唯一标识
             class属性:要创建对象所在类的全路径(包名+类名)
    -->
    <bean id="user" class="com.xz.User"></bean>
</beans>

2.5测试

public class TestUser {
    @Test
    public void test1(){
        //加载核心配置文件
        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
 
        //获取创建对象
        User user = context.getBean("user", User.class);

        //调用方法
        user.test();
    }
}

结果:

2.6总结

如何使用反射创建的对象?

1.加载bean.xml

2.对xml文件进行解析操作

3.获取xml文件bean标签属性值,id属性值和class属性值

4.使用反射根据类全路径名创建对象

Class clazz = Class.forName("com.xz.User");

User user = (User) clazz.getDeclaredConstructor().newInstance();

user.test();

创建的对象放到了哪里?(Map集合)

Map<String, BeanDefinition> beanDefinitionMap

key:唯一标识

value:类的定义(描述信息)

2.7启用Log4j2日志框架 

2.7.1log4j2的概述 

日志记录了系统行为的时间、地点、状态等相关信息,能够帮助我们了解并监控系统状态,在发生错误或者接近某种危险状态时能够及时提醒我们处理,同时在系统产生问题时,能够帮助我们快速的定位、诊断并解决问题。

1.日志的优先级别

日志信息的优先级从高到低有TRACE < DEBUG < INFO <WARN < ERROR < FATAL

TRACE:追踪,是最低的日志级别 相当于追踪程序的执行

DEBUG:调试,一般在开发中,都将其设置为最低的日志级

INFO:信息,输出重要的信息,使用较多

WARN:警告,输出警告的信息

ERROR:错误,输出错误信息

FATAL:严重错误

特点:级别高的会自动屏蔽级别低的日志

2.日志的输出目的地

日志信息的输出目的地指定了日志将打印到控制台还是文件

3.日志信息的输出格式

控制了日志信息的显示内容

2.7.2引入依赖

        <!--log4j2的依赖-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.19.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j2-impl</artifactId>
            <version>2.19.0</version>
        </dependency>

2.7.3加入日志配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <loggers>
        <!--
            level指定日志级别,从低到高的优先级:
                TRACE < DEBUG < INFO < WARN < ERROR < FATAL
                trace:追踪,是最低的日志级别,相当于追踪程序的执行
                debug:调试,一般在开发中,都将其设置为最低的日志级别
                info:信息,输出重要的信息,使用较多
                warn:警告,输出警告的信息
                error:错误,输出错误信息
                fatal:严重错误
        -->
        <root level="DEBUG">
            <appender-ref ref="spring6log"/>
            <appender-ref ref="RollingFile"/>
            <appender-ref ref="log"/>
        </root>
    </loggers>

    <appenders>
        <!--输出日志信息到控制台-->
        <console name="spring6log" target="SYSTEM_OUT">
            <!--控制日志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
        </console>

        <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
        <File name="log" fileName="F:/log4j2/xz" append="false">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
        </File>

        <!-- 这个会打印出所有的信息,
            每次大小超过size,
            则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,
            作为存档-->
        <RollingFile name="RollingFile" fileName="F:/log4j2/xz_test"
                     filePattern="log/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
            <PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
            <SizeBasedTriggeringPolicy size="50MB"/>
            <!-- DefaultRolloverStrategy属性如不设置,
            则默认为最多同一文件夹下7个文件,这里设置了20 -->
            <DefaultRolloverStrategy max="20"/>
        </RollingFile>
    </appenders>
</configuration>

3.Ioc容器

1.loC-->“控制反转",它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合、更优良的程序。

2.Spring通过loC容器来管理所有Java对象的实例化初始化,控制对象与对象之间的依赖关系

3.我们将由loC容器管理的Java对象称为Spring Bean,它与使用关键字 new 创建的Java对象没有任何区别

3.1控制反转(ioc)

1.控制反转是一种思想

2.控制反转是为了降低程序的耦合度,提高程序扩展能力

3.控制反转,反转的是什么?

  • 将对象的创建权交出去,交给第三方容器负责
  • 将对象和对象之间的关系维护权交出去,交给第三方容器负

4.控制反转这种思想如何实现?

  •  DI:依赖注入   

3.2依赖注入

DI:依赖注入,实现了控制反转的思想。

依赖注入:spring创建对象的过程中,将对象依赖属性通过配置进行注入

常见方式有两种:

  • 第一种:set注入
  • 第二种‘:构造器注入

结论:IOC就是一种思想,DI是对IOC的一种具体实现

Bean管理:Bean对象的创建,以及Bean对象中属性的赋值(Bean对象之间关系的维护)

3.3IOC在spring的实现

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

①BeanFactory

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

②ApplicationContext

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

ApplicationContext主要实现类

ClassPathXmlApplicationContext通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
ConfigurableApplicationContextApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close(),让 ApplicationContext 具有启动、关闭和刷新上下文的能力。
WebApplicationContext专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域

3.4xml管理bean

3.4.1获取bean

方式一:根据id的获取

 public void test1(){
        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
        User user =(User) context.getBean("user");
        user.test();
    }

方式二:根据类型获取

    public void test1(){
        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
        User user = context.getBean(User.class);
        user.test();
    }

方式三:根据id+类型获取

    public void test1(){
        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
        User user = context.getBean("user", User.class);
        user.test();
    }

注意:根据类型获取bean时,要求IOC容器中指定类型的bean有且只能有一个!!!

3.4.2依赖注入_setter注入

1.创建一个Book类

public class Book {
    private String name;
    private String title;

    public Book() {
    }

    public Book(String name, String title) {
        this.name = name;
        this.title = title;
    }

    public String getName() {
        return name;
    }

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

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

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

2.bean.xml配置文件

    <!--1.set方法注入-->
    <bean id="book" class="com.xz.demo1.Book">
        <property name="name" value="三国演义"></property>
        <property name="title" value="小说"></property>
    </bean>

 3.测试

public void test1(){
        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
        Book book = context.getBean("book", Book.class);
        System.out.println(book);
    }

4.结果:

3.4.3依赖注入_构造器注入

1.创建Book,并声明构造器(同上)
2.bean.xml配置文件

    <!--2.构造器注入-->
    <bean id="book2" class="com.xz.demo1.Book">
        <constructor-arg name="name" value="红楼梦"></constructor-arg>
        <constructor-arg name="title" value="小说"></constructor-arg>
    </bean>

3.测试

public void test2(){
        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
        Book book2 = context.getBean("book2", Book.class);
        System.out.println(book2);
    }

4.结果


 

3.4.4特殊值的处理

1.字面量赋值

什么是字面量?

int a= 10;声明一个变量a,初始化为10,此时a就不代表字母a了,而是作为一个变量的名字。当我们引用a的时候,我们实际上拿到的值是10。

 <!--使用value属性给bean的属性赋值时,spring会把value属性的值看作字面量-->
        <property name="name" value="三国演义"></property>

 2.null值

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

3.xml实体

        <!--小于号和大于号在xml文档中用来定义标签的开始,不能随便使用-->
        <!--解决方法:用xml实体来代替-->
        <property name="others" value="&lt;&gt;"></property>

4.CDATA节

        <!-- CDATA中的C代表Character,是文本、字符的含义, CDATA就表示纯文本数据-->
        <!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析-->
        <!-- 所以CDATA节中写什么符号都随意 -->
        <property name="others">
            <value><![CDATA[a<b]]></value>
        </property>

3.4.5为对象属性赋值

准备部门和员工两个基本类

部门:

/**                                                    
 * 部门                                                  
 */                                                    
public class Dept {                                    
    private String dname;                              
                                                       
                                                       
    public void info(){                                
        System.out.println("部门:"+dname);               
    }                                                  
                                                       
    public String getDname() {                         
        return dname;                                  
    }                                                  
                                                       
    public void setDname(String dname) {               
        this.dname = dname;                            
    }                                                  
                                                       
    @Override                                          
    public String toString() {                         
        return "Dept{" +                               
                "dname='" + dname + '\'' +             
                '}';                                   
    }                                                  
}                                                      

员工:

/**
 * 员工
 */
public class Emp {
    private String ename;
    private Integer age;

    //对象属性:员工属于某个部门
    private Dept dept;


    public void work() {
        System.out.println(ename+"正在工作。。。。年龄:"+age);
        dept.info();
    }

    public Dept getDept() {
        return dept;
    }

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

    public String getEname() {
        return ename;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "ename='" + ename + '\'' +
                ", age=" + age +
                ", dept=" + dept +
                '}';
    }
}
方式一:引入外部bean
    <!--
      方式一:外部bean
        1.创建两个类的对象:dept和emp
        2.在emp的bean标签里面,使用property引入dept的bean
    -->
    <bean id="dept" class="com.xz.demo2.Dept">
        <property name="dname" value="财务部"></property>
    </bean>
    <bean id="emp" class="com.xz.demo2.Emp">
        <!--普通属性注入-->
        <property name="ename" value="小张"></property>
        <property name="age" value="18"></property>
        <!--对象类型注入-->
        <property name="dept" ref="dept"></property>
    </bean>

使用 ref: 表示引入外部bean 

测试:

   @Test
    public void test1(){
        ApplicationContext context=new ClassPathXmlApplicationContext("bean-di.xml");
        Emp emp = context.getBean("emp", Emp.class);
        emp.work();
        System.out.println(emp.toString());
    }

结果:

  

方式二:内部bean
    <!--
    方式二:内部bean:
    -->
    <bean id="emp2" class="com.xz.demo2.Emp">
        <!--普通属性注入-->
        <property name="ename" value="小王"></property>
        <property name="age" value="18"></property>
        <!--内部bean-->
        <property name="dept">
            <bean id="dept2" class="com.xz.demo2.Dept">
                <property name="dname" value="安保部"></property>
            </bean>
        </property>
    </bean> 

测试:

 @Test
    public void test2(){
        ApplicationContext context=new ClassPathXmlApplicationContext("bean-di.xml");
        Emp emp2 = context.getBean("emp2", Emp.class);
        emp2.work();
        System.out.println(emp2.toString());
    }

结果:

方式三:级联属性赋值

    <!--
     方式三:级联属性赋值
    -->
    <bean id="dept3" class="com.xz.demo2.Dept">
        <property name="dname" value="人事部"></property>
    </bean>
    <bean id="emp3" class="com.xz.demo2.Emp">
        <!--普通属性注入-->
        <property name="ename" value="小李"></property>
        <property name="age" value="18"></property>
        <property name="dept" ref="dept3"></property>
        <property name="dept.dname" value="技术部"></property>
    </bean>

可以更改部门名称~ 

测试:

   @Test
    public void test3() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");
        Emp emp3 = context.getBean("emp3", Emp.class);
        emp3.work();
        System.out.println(emp3.toString());
    }

结果:

3.4.6为数组类型属性赋值

为Emp添加数组类型属性

//爱好
private String[] loves;
    <!--数组类型属性-->
    <bean id="dept1" class="com.xz.demo2.Dept">
        <property name="dname" value="财务部"></property>
    </bean>
    <bean id="emp1" class="com.xz.demo2.Emp">
        <!--普通属性注入-->
        <property name="ename" value="小张"></property>
        <property name="age" value="18"></property>
        <!--对象类型注入-->
        <property name="dept" ref="dept1"></property>
        <!--数组类型注入-->
        <property name="loves">
            <array>
                <value>吸烟</value>
                <value>喝酒</value>
                <value>烫头</value>
            </array>
        </property>
    </bean>

测试:

   @Test
    public void test1() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-di-array.xml");
        Emp emp1 = context.getBean("emp1", Emp.class);
        emp1.work();
        System.out.println(emp1.toString());
    }

结果:

3.4.7集合类型属性注入

1.为list集合类型属性赋值

在部门中添加员工,生成get,set方法。 

//一个部门中有很多员工                  
private List<Emp> empList;    
    <!--第一个员工-->
    <bean id="emp1" class="com.xz.demo2.Emp">
        <property name="ename" value="小张"></property>
        <property name="age" value="18"></property>
    </bean>
    <!--第二个员工-->
    <bean id="emp2" class="com.xz.demo2.Emp">
        <property name="ename" value="小王"></property>
        <property name="age" value="18"></property>
    </bean>

    <bean id="dept" class="com.xz.demo2.Dept">
        <property name="dname" value="安保部"></property>
        <property name="empList">
            <list>
                <ref bean="emp1"></ref>
                <ref bean="emp2"></ref>
            </list>
        </property>
    </bean>

测试:

    @Test
    public void test2() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-di-list.xml");
        Dept dept = context.getBean("dept", Dept.class);
        dept.info();
    }

 结果:

2.为map集合类型属性赋值

创建两个类

学生类:

public class Student {
    //学号
    private Integer Sid;
    //姓名
    private String SName;

    private Map<String,Teacher> teacherMap;

    public void run() {
        System.out.println("学生的编号:" + Sid + ",姓名:" + SName);
        System.out.println(teacherMap);
    }

    public Map<String, Teacher> getTeacherMap() {
        return teacherMap;
    }

    public void setTeacherMap(Map<String, Teacher> teacherMap) {
        this.teacherMap = teacherMap;
    }

    public Integer getSid() {
        return Sid;
    }

    public void setSid(Integer sid) {
        Sid = sid;
    }

    public String getSName() {
        return SName;
    }

    public void setSName(String SName) {
        this.SName = SName;
    }

    @Override
    public String toString() {
        return "Student{" +
                "Sid=" + Sid +
                ", SName='" + SName + '\'' +
                ", teacherMap=" + teacherMap +
                '}';
    }
}

老师类:

public class Teacher {
    private Integer Tid;
    private String TName;

    public Integer getTid() {
        return Tid;
    }

    public void setTid(Integer tid) {
        Tid = tid;
    }

    public String getTName() {
        return TName;
    }

    public void setTName(String TName) {
        this.TName = TName;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "Tid=" + Tid +
                ", TName='" + TName + '\'' +
                '}';
    }
}

xml配置文件: 

    <!--
       1.创建两个类的对象
       2.普通属性注入
       3.学生中注入map
    -->

    <bean id="teacher1" class="com.xz.dimap.Teacher">
        <!--普通属性注入-->
        <property name="tid" value="1001"></property>
        <property name="TName" value="张老师"></property>
    </bean>
    <bean id="teacher2" class="com.xz.dimap.Teacher">
        <!--普通属性注入-->
        <property name="tid" value="1002"></property>
        <property name="TName" value="李老师"></property>
    </bean>
    <bean id="student" class="com.xz.dimap.Student">
        <property name="sid" value="101"></property>
        <property name="SName" value="小张"></property>
        <!--注入map集合-->
        <property name="teacherMap">
            <map>
                <entry>
                    <key>
                        <value>1001</value>
                    </key>
                    <!--value值是一个对象,引入对象-->
                    <ref bean="teacher1"></ref>
                </entry>
                <entry>
                    <key>
                        <value>1002</value>
                    </key>
                    <ref bean="teacher2"></ref>
                </entry>
            </map>
        </property>
    </bean>

测试:

   @Test
    public void test1(){
        ApplicationContext context=new ClassPathXmlApplicationContext("bean-di-map.xml");
        Student student = context.getBean("student", Student.class);
        student.run();
    }

 结果:

 3.引用集合类型的bean

    <!--
      1.创建三个对象
      2.注入普通属性
      3.使用util:类型
      4.在学生bean引入util,完成list,map属性注入
    -->

    <bean id="lesson1" class="com.xz.dimap.Lesson">
        <property name="LName" value="JAVA"></property>
    </bean>
    <bean id="lesson2" class="com.xz.dimap.Lesson">
        <property name="LName" value="前端"></property>
    </bean>
    <bean id="teacher1" class="com.xz.dimap.Teacher">
        <property name="tid" value="1001"></property>
        <property name="TName" value="张老师"></property>
    </bean>
    <bean id="teacher2" class="com.xz.dimap.Teacher">
        <property name="tid" value="1002"></property>
        <property name="TName" value="王老师"></property>
    </bean>

    <bean id="student" class="com.xz.dimap.Student">
        <!--普通属性注入-->
        <property name="sid" value="101"></property>
        <property name="SName" value="小张"></property>
        <!--注入list和map类型的属性-->
        <property name="lessonList" ref="lessonList1"></property>
        <property name="teacherMap" ref="teacherMap1"></property>
    </bean>

     <util:list id="lessonList1">
         <ref bean="lesson1"></ref>
         <ref bean="lesson2"></ref>
     </util:list>

    <util:map id="teacherMap1">
        <entry>
            <key>
                <value>1001</value>
            </key>
            <ref bean="teacher1"></ref>
        </entry>
        <entry>
            <key>
                <value>1002</value>
            </key>
            <ref bean="teacher2"></ref>
        </entry>
    </util:map>

测试:

    @Test
    public void test2(){
        ApplicationContext context=new ClassPathXmlApplicationContext("bean-di-ref.xml");
        Student student = context.getBean("student", Student.class);
        student.run();
    }

结果:

 4.p命名空间

引入约束

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

结构:

    <!--
       p命名空间注入
    -->
    <bean id="student2" class="com.xz.dimap.Student"
    p:sid="102" p:SName="小王" p:lessonList-ref="lessonList1"  p:teacherMap- 
    ref="teacherMap1">

    </bean>

测试:

    @Test
    public void test3(){
        ApplicationContext context=new ClassPathXmlApplicationContext("bean-di-ref.xml");
        Student student2 = context.getBean("student2", Student.class);
        student2.run();
    }

结果:

5.引入外部属性文件

好处:只需要修改外部属性文件,而不用修改属性配置。

实现步骤:

1.引入数据库相关依赖

2.创建外部属性文件,定义数据库信息

3.创建spring配置文件,引入context命名空间,引入属性文件,使用表达式完成注入

加入数据库依赖:

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
        <!--数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.16</version>
        </dependency>

创建属性文件,定义mysql信息:

jdbc.user=root
jdbc.password=123456
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.driver=com.mysql.cj.jdbc.Driver

spring配置文件:

    <!--引入外部属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property- 
    placeholder>

    <!--完成数据库信息的注入-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="driverClassName" value="${jdbc.driver}"></property>
    </bean>

注意:driverClassName  而不是 driver!!!

3.4.8bean作用域

 在spring中可以通过配置bean标签的scope属性来指定bean的作用域范围。

取值含义创建对象的时机
singleton(默认)在IOC容器中,这个bean的对象始终为单实例IOC容器初始化时
prototype这个bean在IOC容器中有多个实例获取bean时

 spirng文件:

    <!--通过scope属性能够配置单实例或多实例,不写默认单实例-->
    <bean id="orders" class="com.xz.scope.Orders" scope="prototype"></bean>

测试:

    @Test
    public void test(){
        ApplicationContext context=new ClassPathXmlApplicationContext("bean-scope.xml");
        Orders orders1 = context.getBean("orders", Orders.class);
        System.out.println("orders1:"+orders1);
        Orders orders2 = context.getBean("orders", Orders.class);
        System.out.println("orders2:"+orders2);

    }

单实例测试结果:

多实例测试结果:

3.4.9bean的生命周期

声明周期的过程:

1.bean对象的创建(调用无参构造)

2.给bean对象设置相关属性

3.bean后置处理器(初始化之前)

4.bean对象初始化(调用指定的初始化方法)

5.bean后置处理器(初始化之后)

6.bean对象创建完成,可以使用了

7.bean对象销毁(配置指定的销毁方法)

8.IoC容器关闭

创建测试类:

public class User {
    private String Name;

    public User() {
        System.out.println("1.调用无参构造,创建对象...");
    }

    public User(String name) {
        Name = name;
    }

    public String getName() {
        return Name;
    }

    public void setName(String name) {
        System.out.println("2.给bean对象设置属性值...");
        Name = name;
    }
    //初始化方法
     public  void initMethod(){
         System.out.println("4.bean对象初始化,调用指定的初始化方法...");
     }
    //销毁方法
     public void destroyMethod(){
         System.out.println("7.bean对象销毁,调用指定的销毁方法...");
     }
     
}

后置处理器类:

public class MyBean implements BeanPostProcessor {
    //初始化之前执行
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("3.后置处理器,初始化之前执行...");

        return bean;
    }

    //初始化之后执行
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("5.后置处理器,初始化之后执行...");
        return bean;
    }
}

spring配置文件:

    <!--init-method:初始化方法,destroy-method:销毁方法-->
    <bean id="user" class="com.xz.live.User" init-method="initMethod" destroy- 
     method="destroyMethod">
        <property name="name" value="小张"></property>
    </bean>


    <!--bean的后置处理器要放入到IoC容器中才能生效-->
    <bean id="myBean" class="com.xz.live.MyBean"></bean>

 测试:

    @Test
    public void test1(){
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("bean- 
        live.xml");
        User user = context.getBean("user", User.class);
        System.out.println("6.bean对象创建完成...");
        context.close();//bean销毁
    }

结果:

3.4.10FactoryBean

FactoryBean是Spring提供的一种整合第三方框架的常用机制。

和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring只把最简洁的使用界面展示给我们。

将来我们整合Mybatis时, Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的.

public class MyFactory implements FactoryBean<User> {
    @Override
    public User getObject() throws Exception {
        //返回的对象
        return new User();
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }
}

3.4.11自动装配

根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值

 配置bean:

使用bean标签的autowire属性设置自动装配效果:

自动装配方式一: byType.

byType:根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值

若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值null.

若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常NoUniqueBeanDefinitionExceptipn.
 

自动装配方式二:byNam

byName:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值

bean标签里面的 id属性值必须和 自动装配的属性的属性名一致

 模拟架构图:

 

controller:

public class UserController {

    private UserService userService;

    public void userAdd() {
        System.out.println("添加用户。。。controller");
        //调用service方法
        userService.userAddService();
    }



    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

serviceImp:

public class UserServiceImpl implements UserService {

    private UserDao userDao;

    @Override
    public void userAddService() {
        System.out.println("添加用户。。。service");
        //调用UserDao方法
        userDao.userAddDao();
    }



    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

daoImp:

public class UserDaoImpl implements UserDao {
    @Override
    public void userAddDao() {
        System.out.println("添加用户。。。dao");
    }
}

spring配置文件:

    <!--创建controller,service,dao对象-->
  
    <!--
         autowire:
         属性值:
         byType:根据类型注入 UserDao  ( private UserDao userDao;)
         byName:根据名称注入 userDao  ( private UserDao userDao;)
    -->
    <bean id="userController" class="com.xz.auto.controller.UserController" 
    autowire="byType">

    </bean>

    <bean id="userService" class="com.xz.auto.service.impl.UserServiceImpl" 
    autowire="byType">

    </bean>

    <bean id="userDao" class="com.xz.auto.dao.impl.UserDaoImpl">

    </bean>

 3.5注解管理bean

spring通过注解实现自动装配的步骤如下:

1.引入依赖

2.开启组件扫描

3.使用注解定义Bean

4.依赖注入 

3.5.1开启组件扫描

1.Spring默认不使用注解装配Bean。

2.通过context:component-scan元素开启Spring Beans的自动扫描功能。

3.开启此功能后, Spring会自动从扫描指定的包(base-package属性设置)及其子包下的所有类,如果类上使用了@Component注解,就将该类装配到容器中。

 情况一:基本的扫描方式

    <!--开启组件扫描-->
    <context:component-scan base-package="com.xz"></context:component-scan>

情况二:指定要排除的组件

    <!--
       context:exclude-filter标签:指定排除规则
       type:设置排除或包含的依据
       type="annotation",根据注解排除,expression中设置要排除的注解的全类名
       type="assignable",根据类型排除,expression中设置要排除的类型的全类名
    -->
    <context:component-scan base-package="com.xz">
        <context:exclude-filter type="annotation" expression=""/>
    </context:component-scan>

 情况三:仅扫描的组件

    <!--
         context:include-filter标签:指定在原有扫描规则的基础上追加的规则
         use-default-filters属性:false表示关闭默认扫描规则
         type:设置排除或包含的依据
         type="annotation",根据注解排除,expression中设置要排除的注解的全类名
         type="assignable",根据类型排除,expression中设置要排除的类型的全类名
    -->
    <context:component-scan base-package="com.xz" use-default-filters="false">
        <context:include-filter type="annotation" expression=""/>
    </context:component-scan>

3.5.2使用注解定义bean

 加注解等价于:

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

value值--------id

注意:如果注解不加value属性,id默认为类名首字母小写!!!

注解说明
@Component该注解用于描述 Spring 中的 Bean,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次。如:dao,controller,service。
@Repository该注解将数据访问层(Dao 层)的类标识为 Spring 中的 Bean。
@Service该注解将将业务层(Service 层)的类标识为Spring 中的 Bean。
@Controller该注解将控制层(Controller层) 的类标识为Spring 中的 Bean。

3.5.3@Autowired注入

@Autowired注解默认根据类型装配【byType】,如果想根据名称装配,需要配合@Qualifier注解一起用。

①属性注入
    //方式一:属性注入
    //注入Service
    @Autowired//根据类型找对象
    private UserService userService;
②set注入
    //方式二:set方法注入
    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
③构造方法注入
    //方式三:构造方法注入
    private UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }
④形参注入
    //方式四:形参注入
    private UserService userService;

    public UserController(@Autowired UserService userService) {
        this.userService = userService;
    }
⑤只有一个构造函数,无注解

当有参数的构造方法只有一个时,@Autowried注解可以省略。

    //方式五:无注解
    private UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

神奇的是测试通过哦~

⑥@Autowried注解和@Quailfier注解联合

两个dao实现类实现同一个接口,如果根据类型注入会报错

 测试出现错误:

解决:

    //方式五:两个注解,根据名称注入
    @Autowired
    @Qualifier(value = "userDaoImplCopy")
    private UserDao userDao;

3.5.4@Resource注入

1.@Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。

2.@Resource注解默认根据名称装配【byName】,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配

注意:根据类型【byType】注入时,某种类型的Bean只能有一个!!!

@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖: 【如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入以下依赖。】

        <!--resource注解-->
        <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>2.1.1</version>
        </dependency>

注意version版本,不然会由于版本过低,导致报错!!! 

3.5.5全注解开发

全注解开发就是不再使用spring配置文件了,写一个配置类来代替配置文件。

配置类:

@Configuration:将该类标明为配置类。

@ComponentScan:开启组件扫描。

@Configuration//将该类标注为配置类
@ComponentScan("com.xz")//开启组件扫描
public class SpringConfig {
}

测试类:

AnnotationConfigApplicationContext():加载配置类。

   @Test
    public void test1(){
        ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);
        EmpController empController = context.getBean("empController", EmpController.class);
        empController.add();
    }

4.手写IOC

4.1回顾java反射

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息

1.反射获取对象的方式:

    //1.获取class的多种方式
    @Test
    public void test1() throws ClassNotFoundException {
        //1.类名.class
        Class clazz1 = Car.class;

        //2.对象.getClass()
        Class clazz2 = new Car().getClass();

        //3.Class.forName("类全路径")
        Class clazz3 = Class.forName("com.xz.reflect.Car");
    }

实例化:

       //实例化(创建对象)
        Car car = (Car) clazz3.getDeclaredConstructor().newInstance();
        System.out.println(car);

2.获取构造方法:

    //2.获取构造方法
    @Test
    public void test2() throws Exception {
        Class clazz = Car.class;

        /*
            获取所有构造
             getConstructors:只针对public方法,如果是private,则获取不到
             getDeclaredConstructors:可以获取public也可以获取private
        */

        Constructor[] constructors = clazz.getDeclaredConstructors();
        for (Constructor c : constructors) {
            System.out.println("参数名称:" + c.getName() + ",参数个数:" + 
       c.getParameterCount());
        }

        //指定无参构造创建对象
        //1.构造public
        Constructor c1 = clazz.getConstructor(String.class, int.class, String.class);
        Car car1 = (Car) c1.newInstance("奔驰", 2, "黑");
        System.out.println(car1);

        //2.构造private
        Constructor c2 = clazz.getDeclaredConstructor(String.class, String.class);
        c2.setAccessible(true);//表示能访问私有的构造,不设置不能访问
        Car car2 = (Car) c2.newInstance("宝马", "白");
        System.out.println(car2);
    }

3.获取属性:

    //获取属性
    @Test
    public void test3() throws Exception {
        Class clazz = Car.class;
        //实例化
        Car car = (Car) clazz.getDeclaredConstructor().newInstance();

        //获取所有的public属性
        Field[] fields1 = clazz.getFields();

        //获取所有的属性,包含private
        Field[] fields2 = clazz.getDeclaredFields();
        for (Field filed:fields2){
            if (filed.getName().equals("name")){
                //设置允许访问
                filed.setAccessible(true);
                filed.set(car,"小马");
            }
            System.out.println("属性名:"+filed.getName());
            System.out.println(car);
        }
    }

4.获取方法:

    //4.获取方法
    @Test
    public void test4() throws InvocationTargetException, IllegalAccessException {
        Car car = new Car("保时捷", 1, "粉");
        Class clazz = car.getClass();

        //1.public方法
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            //执行方法toString
            if (method.getName().equals("toString")) {
                String invoke = (String) method.invoke(car);
                System.out.println("toString:"+invoke);

            }
        }
        //2.private方法
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method method:declaredMethods){
            if (method.getName().equals("run")){
                method.setAccessible(true);
                method.invoke(car);
            }
        }
    }

4.2ioc手写

1.创建@Bean注解,@Di注解:

@Target:表示作用对象

@Retention:表示什么时候生效

@Target(ElementType.TYPE)//作用在类上面
@Retention(RetentionPolicy.RUNTIME)//运行时生效
public @interface Bean {
}
@Target(ElementType.FIELD)//作用在属性
@Retention(RetentionPolicy.RUNTIME)//运行时生效
public @interface Di {
}

2.自定义ApplicationContext接口:

public interface ApplicationContext {
    Object getBean(Class clazz);
}

3.创建ApplicationContext接口的实现类:(⭐⭐⭐)

通过ioc创建对象,把创建的对象放到map集合中进行存储。

哪个类上有@Bean注解,把这个类通过反射实例化放到map集合中。

哪个属性上有@Di注解,把这个属性注入。

public class AnnotationApplicationContext implements ApplicationContext {

    //创建一个map集合,放bean对象
    private Map<Class, Object> beanFactory = new HashMap<>();
    private static String rootPath;


    //返回对象
    @Override
    public Object getBean(Class clazz) {
        return beanFactory.get(clazz);
    }

    //创建有参构造,传递路径,设置包扫描规则
    //当前包及其子包,哪个类有@Bean注解,把这个类通过反射实例化
    public AnnotationApplicationContext(String basePackage) {
        //1.把.替换成\
        String packPath = basePackage.replaceAll("\\.", "\\\\");
        //2.获取包的绝对路径
        try {
            Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(packPath);
            while (resources.hasMoreElements()) {
                URL url = resources.nextElement();
                String filePath = URLDecoder.decode(url.getFile(), "utf-8");
                //获取包前面路径部分,字符串截取
                rootPath = filePath.substring(0, filePath.length() - packPath.length());

                //包扫描
                try {
                    loadBean(new File(filePath));
                } catch (Exception e) {
                    e.printStackTrace();
                }

                //属性注入
                loadDi();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    //包扫描过程 实例化
    private void loadBean(File file) throws Exception {
        //1.判断当前内容是否是文件夹
        if (file.isDirectory()) {

            //2.获取文件夹的所有内容
            File[] childFiles = file.listFiles();

            //3.判断文件夹内容为空,直接返回
            if (childFiles == null || childFiles.length == 0) {
                return;
            }

            //4.文件夹不为空,遍历所有内容
            for (File child : childFiles) {
                //4.1遍历得到每个file对象,继续判断,如果还是文件夹,递归
                if (child.isDirectory()) {
                    //递归
                    loadBean(child);
                } else {
                    //4.2遍历得到的file对象不是文件夹,是文件,
                    //4.3得到包路径+类名称--------字符串截取
                    String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);

                    //4.4当前文件类型是否是.class
                    if (pathWithClass.contains(".class")) {
                        //4.5如果是.class,把路径\替换成.  把.class去掉
                        String allName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");

                        //4.6判断类上面是否有注解@Bean,如果有则实例化
                        //4.6.1获取类的Class
                        Class<?> clazz = Class.forName(allName);

                        //4.6.2判断不是接口
                        if (!clazz.isInterface()) {
                            //4.6.3判断类上面是否有@Bean
                            Bean annotation = clazz.getAnnotation(Bean.class);
                            //annotation!=null表示类上有注解
                            if (annotation != null) {
                                //4.6.4实例化
                                Object instance = clazz.getConstructor().newInstance();
                                //4.7把对象实例化之后放到map集合beanFactory
                                //4.7.1判断当前类如果有接口,让接口的class作为map的key
                                if (clazz.getInterfaces().length > 0) {
                                    beanFactory.put(clazz.getInterfaces()[0], instance);
                                } else {
                                    beanFactory.put(clazz, instance);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    //属性的注入
    private void loadDi() {
        //实例化的对象都在beanFactory的map里面
        //1.遍历beanFactory的map集合
        Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();
        for (Map.Entry<Class, Object> entry : entries) {
            //2.获取map集合每个对象(value),每个对象属性获取到
            Object obj = entry.getValue();

            //2.1获取对象Class
            Class<?> clazz = obj.getClass();

            //2.2获取所有属性
            Field[] declaredFields = clazz.getDeclaredFields();

            //3.遍历得到每个对象属性数组,得到每个属性
            for (Field field : declaredFields) {
                //4.判断属性上是否有@Di注解
                Di annotation = field.getAnnotation(Di.class);
                //annotation!=null表示属性上有注解
                if (annotation != null) {
                    //表示可以设置属性
                    field.setAccessible(true);

                    //5.如果有@Di注解,把对象进行注入
                    try {
                        field.set(obj, beanFactory.get(field.getType()));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

5.面向切面:AOP

5.1代理模式

二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来--解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。

5.1.1静态代理

1.静态代理确实实现了解耦,但是由于代码都写死了,

2.完全不具备任何的灵活性。

3.声明更多个静态代理类

4.产生了大量重复的代码

5.日志功能还是分散

6.没有统一管理。

public class CalculatorStaticProxy implements Calculator {

    //把代理对象传递
    private Calculator calculator;

    public CalculatorStaticProxy(Calculator calculator) {
        this.calculator = calculator;
    }

    @Override
    public int add(int i, int j) {
        //之前输出
        System.out.println("【日志】add方法开始执行,参数是:"+i+","+j);

        //调用目标对象的方法
        int addResult = calculator.add(1, 2);

        //之后输出
        System.out.println("【日志】add方法执行结束,结果是:"+addResult);
        return addResult;
    }

    @Override
    public int sub(int i, int j) {
        return 0;
    }

    @Override
    public int mul(int i, int j) {
        return 0;
    }

    @Override
    public int div(int i, int j) {
        return 0;
    }
}

5.1.2动态代理

 1.动态代理分类:JDK动态代理和cglib动态代理

 

创建动态代理类:

public class ProxyFactory {

    //目标对象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }


    //返回代理对象
    public Object getProxy(){

        ClassLoader classLoader=target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler invocationHandler = new InvocationHandler() {

            /**
             * 第一个参数:代理对象
             * 第二个参数:需要重写目标对象的方法
             * 第三个参数:method方法里面的参数
             */
            @Override
            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("结果:"+result);
                //方法调用之后输出
                System.out.println("[动态代理]" + method.getName() + ",结果:" + result);
                return result;
            }
        };
        /**Proxy.newProxyInstance()
         * args:
         * 1.ClassLoader:加载动态生成代理类的类加载器
         * 2.Class<?>[] </>interfaces:目标对象实现的所有接口的class类型数组
         *3.InvocationHandler:设置代理对象实现目标对象方法的过程
         */
        return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
    }
}

5.2AOP

5.2.1概念

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

5.2.2术语

1.横切关注点

从每个方法中抽取出来的同一类非核心业务。

2.通知(增强)

增强,通俗说,就是你想要增强的功能,比如 安全,事物,日志等。

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

  • 前置通知:在被代理的目标方法执行
  • 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
  • 异常通知:在被代理的目标方法异常结束后执行(死于非命)
  • 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
  • 环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

3.切面

封装通知方法的类

4.目标

被代理的目标对象

5.代理

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

6.连接点

spring中允许使用通知的地方 

7.切入点

spring的AOP技术可以通过切入点定位到特定的连接点,要实际去增强的方法

5.2.3注解Aop

1.引入依赖:

        <!--spring aop 依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>6.0.2</version>
        </dependency>
        <!--spring aspects依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>6.0.2</version>
        </dependency>
2.切面类 
@Aspect//表示切面类
@Component//加到ioc容器
public class LogAspect {

    //设置切入点和通知类型
    //通知类型:
    // 1.前置:  @Before(value="切入点表达式配置切入点")
    //切入点表达式:execution(权限修饰符 增强方法的返回类型 增强方法所在类型的全类名.方法名(方法参数))
    @Before(value = "execution(public int com.xz.annaop.CalculatorImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("前置通知=========,增强的方法名:" + name + ",参数:" + Arrays.toString(args));
    }

    // 返回:  @AfterReturning
    //returning:能得到目标方法的返回值
    @AfterReturning(value = "execution(* com.xz.annaop.CalculatorImpl.*(..))", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result) {
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("返回通知=========,增强的方法名:" + name + ",参数:" + Arrays.toString(args) + ",结果:" + result);
    }

    // 异常:  @AfterThrowing
    //目标方法出现异常,这个通知执行,并获取异常信息
    @AfterThrowing(value = "execution(* com.xz.annaop.CalculatorImpl.*(..))", throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) {
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("异常通知=========,增强的方法名:" + name + ",参数:" + Arrays.toString(args) + ",异常信息:" + ex);

    }

    // 后置:  @After()
    @After(value = "execution(* com.xz.annaop.CalculatorImpl.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("后置通知=========,增强的方法名:" + name + ",参数:" + Arrays.toString(args));
    }

    // 环绕:  @Around()
    @Around(value = "execution(* com.xz.annaop.CalculatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        Object result = null;
        System.out.println("环绕通知=========,增强的方法名:" + name + ",参数:" + Arrays.toString(args));
        try {
            System.out.println("环绕通知===目标方法之前执行");
            //调用目标方法
            result = joinPoint.proceed();
            System.out.println("结果:"+result);

            System.out.println("环绕通知===目标方法之后执行");
        } catch (Throwable throwable) {
            System.out.println("环绕通知===目标方法异常执行");

        } finally {
            System.out.println("环绕通知===目标方法执行完毕");
        }
        return result;
    }


}
3.切入点表达式

4.重用切入点表达式
   //重用切入点表达式
    @Pointcut(value = "execution(* com.xz.annaop.CalculatorImpl.*(..))")
    public void pointCut(){
    }

用法:

    @Before(value = "pointCut()")
    public void beforeMethod(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("前置通知=========,增强的方法名:" + name + ",参数:" + Arrays.toString(args));
    }

5.2.4xmlAoP

1.spring配置文件:
    <!--开启组件扫描-->
    <context:component-scan base-package="com.xz.xmlaop"></context:component-scan>


    <!--配置五种通知类型-->
    <aop:config>
        <!--1.配置切面类-->
        <aop:aspect ref="logAspect">
            <!--2.配置切入点-->
            <aop:pointcut id="pointcut" expression="execution(* com.xz.xmlaop.CalculatorImpl.*(..))"/>
            <!--3.配置五种通知类型-->
            <!--前置通知-->
            <aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
            <!--后置通知-->
            <aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
            <!--返回通知-->
            <aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointcut"></aop:after-returning>
            <!--异常通知-->
            <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointcut"></aop:after-throwing>
            <!--环绕通知-->
            <aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>
        </aop:aspect>
    </aop:config>
2.切面类:
@Component
public class LogAspect {

    
    public void beforeMethod(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("前置通知=========,增强的方法名:" + name + ",参数:" + Arrays.toString(args));
    }

    public void afterReturningMethod(JoinPoint joinPoint, Object result) {
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("返回通知=========,增强的方法名:" + name + ",参数:" + Arrays.toString(args) + ",结果:" + result);
    }

    public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) {
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("异常通知=========,增强的方法名:" + name + ",参数:" + Arrays.toString(args) + ",异常信息:" + ex);

    }


    public void afterMethod(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("后置通知=========,增强的方法名:" + name + ",参数:" + Arrays.toString(args));
    }


    public Object aroundMethod(ProceedingJoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        Object result = null;
        System.out.println("环绕通知=========,增强的方法名:" + name + ",参数:" + Arrays.toString(args));
        try {
            System.out.println("环绕通知===目标方法之前执行");
            //调用目标方法
            result = joinPoint.proceed();
            System.out.println("结果:" + result);

            System.out.println("环绕通知===目标方法之后执行");
        } catch (Throwable throwable) {
            System.out.println("环绕通知===目标方法异常执行");

        } finally {
            System.out.println("环绕通知===目标方法执行完毕");
        }
        return result;
    }
}

6.整合junit

6.1整合junit5

6.1.1引入依赖

        <!--junit-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.10.0</version>   
        </dependency>
        <!--spring整合junit依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>6.0.2</version>
        </dependency>

6.1.1测试

@SpringJUnitConfig(locations="spring配置文件的位置" )

@SpringJUnitConfig(locations = "classpath:bean.xml")
public class TestJunit5 {

    @Autowired
    private User user;

    @Test
    public void  testUser(){
        System.out.println(user);
      user.run();
    }
}

6.2整合junit4

6.2.1引入依赖

        <!--junit4-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
           <version>4.13.1</version>
        </dependency>

6.2.2测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:bean.xml")
public class TestJunit4 {
    @Autowired
    private User user;

    @Test
    public void test(){
        System.out.println(user);
        user.run();
    }
}

7.事务

7.1JdbcTemplate

 JdbcTemplate是Spring对JDBC的封装,目的是使JDBC更加易于使用。JdbcTemplate是Spring的一部分。

执行语句分三类:

  • execute:可以执行所有SQL语句,一般用于执行DDL语句。
  • update:用于执行INSERTUPDATEDELETE等DML语句。
  • queryXxx:用于DQL数据查询语句。

1.引入依赖

        <!--spring jdbc-->
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!--Mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>

        <!--数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.16</version>
        </dependency>

2.创建jdbc资源文件

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.user=root
jdbc.password=123456

3.创建spring配置文件

    <!--引入外部属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <!--创建数据源对象-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="username" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!--创建jdbcTemplate对象,注入数据源-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

4.注入JdbcTemplate

@SpringJUnitConfig(locations = "classpath:bean.xml")
public class JDBCTemplateTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;


    //查询返回对象
    @Test
    public void select1() {
//        //写法一:
//        String sql = "select * from t_emp where id=?";
//        Emp emp = jdbcTemplate.queryForObject(sql,
//                (rs, rowNum) -> {
//                    Emp emp1 = new Emp();
//                    emp1.setId(rs.getInt("id"));
//                    emp1.setName(rs.getString("name"));
//                    emp1.setAge(rs.getInt("age"));
//                    emp1.setSex(rs.getString("sex"));
//                    return emp1;
//                },
//                1);
//        System.out.println(emp);
        //写法二
        String sql = "select * from t_emp where id=?";
        Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class), 1);
        System.out.println(emp);
    }

    //查询返回list集合
    @Test
    public void select2() {
        String sql = "select * from t_emp";
        List<Emp> empList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class));
        for (Emp emp : empList) {
            System.out.println(emp);
        }
    }
    //查询返回单个值
    @Test
    public void select3(){
        String sql="select count(*) from t_emp";
        Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println("查询到的结果:"+count);
    }

    //添加 修改,删除
    //添加
    @Test
    public void add() {
        //第一步.写sql语句
        String sql = "INSERT INTO t_emp VALUES(NULL,?,?,?)";
        //第二步调用jdbcTemplate的方法,传入相关的参数
        int row = jdbcTemplate.update(sql, "小王", 22, "男");
        System.out.println("成功添加:" + row + "条记录");
    }

    //修改
    @Test
    public void update() {
        String sql = "update  t_emp set name=? where id=?";
        int row = jdbcTemplate.update(sql, "庞博", 2);
        System.out.println("成功修改" + row + "条记录");
    }

    //删除
    @Test
    public void delete() {
        String sql = "delete  from t_emp where id=?";
        int row = jdbcTemplate.update(sql, 2);
        System.out.println("成功删除" + row + "条记录");

    }
}

7.2声明式事务

7.2.1基本概念

1.什么是事务

数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始事务结束之间执行的全部数据库操作组成。 

2.事务的特性

A: 原子性(Atomicity)

一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

C: 一致性(Consistency)

事务的一致性指的是在一个事务执行之前执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态

I:隔离性(Isolation)

指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。

D:持久性(Durability)

指的是只要事务成功结束,它对数据库所做的更新就必须保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态

7.3基于注解的声明式事务

7.3.1无添加事务 

图书表:

CREATE TABLE `t_book` (
  `book_id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `book_name` varchar(20) NOT NULL COMMENT '图书名',
  `price` int NOT NULL COMMENT '价格',
  `stock` int unsigned NOT NULL COMMENT '库存(无符号)',
  PRIMARY KEY (`book_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

 用户表:

CREATE TABLE `t_user` (
  `user_id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_name` varchar(20) NOT NULL COMMENT '用户名',
  `balance` int unsigned NOT NULL COMMENT '余额(无符号)',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

 controller:

@Controller
public class BookController {

    @Autowired
    private BookService bookService;

    //买书的方法
    public void buyBook(Integer bookId,Integer userId){

        bookService.buyBook(bookId,userId);
    }
}

service:

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;

    @Override
    public void buyBook(Integer bookId, Integer userId) {
        //根据图书id查询图书价格
        Integer price = bookDao.getBookPriceById(bookId);
        //跟新图书表存量 -1
        bookDao.updateStock(bookId);
        //更新用户表余额 -图书价格
        bookDao.updateUserBalance(userId, price);

    }
}

dao:

@Repository
public class BookDaoImpl implements BookDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    //根据id查询图书价格
    @Override
    public Integer getBookPriceById(Integer bookId) {
        String sql = "select price from t_book where book_id=?";
        Integer price = jdbcTemplate.queryForObject(sql, Integer.class, bookId);
        return price;
    }

    //更新库存 -1
    @Override
    public void updateStock(Integer bookId) {
        String sql="update t_book set stock=stock-1 where book_id=?";
        jdbcTemplate.update(sql, bookId);
    }

    //跟新用户余额 -图书价格
    @Override
    public void updateUserBalance(Integer userId, Integer price) {
        String sql="update t_user set balance=balance-? where user_id=?";
        jdbcTemplate.update(sql,price,userId);

    }
}

测试:

@SpringJUnitConfig(locations = "classpath:bean.xml")
public class TestTx {

    @Autowired
    private BookController bookController;

    @Test
    public void testBuyBook(){
        bookController.buyBook(1,1);
    }
}

结果:

余额和库存变动!!

 若用户余额小于五十!!

 发现用户余额没有变化,但是库存任然减一

7.3.2加入事务

1.添加事务配置
 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>

    <!--开启事务的注解驱动通过注解@Transactiona1所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务-->
        <!--transaction-manager属性的默认值是transactionManager,
        如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
2.添加注解

因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在service层处理在BookServicelmpl的buybook()添加注解@Transactional

注解标记的位置

  • @Transactional标识在方法上:则只会影响该方法
  • @Transactional标识的类上:则会影响类中所有的方法

测试:

用20元买书,库存不变,事务生效!!

7.3.3@Transactional属性

1、只读:设置只读,只能查询,不能修改添加删除

2、超时:在设置超时时候之内没有完成,抛出异常回滚

3、回滚策略:设置哪些异常不回滚

4、隔离级别:读问题编

5、传播行为:事务方法之间调用,事务如何使用ESDN 

1.只读

对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。

  @Transactional(readOnly = true)

如出现修改操作则报错!!!

2.超时

事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。

概括来说就是一句话:超时回滚,释放资源。

   @Transactional(timeout = 3)

如出现超时作则报错!!!

3.回滚策略

 声明式事务默认只针对运行时异常回滚,编译时异常不回滚。

可以通过@Transactional中相关属性设置回滚策略

  • rollbackFor属性:需要设置一个Class类型的对象
  • rollbackForClassName属性:需要设置一个字符串类型的全类名
  • noRollbackFor属性:需要设置一个Class类型的对象
  • rollbackFor属性:需要设置一个字符串类型的全类名 
4.隔离级别 

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱

隔离级别一共有四种:

  • 读未提交: READ UNCOMMITTED

            允许Transaction01读取Transaction02未提交的修改。

  • 读已提交:READ COMMITTED

            要求Transaction01只能读取Transaction02已提交的修改。

  • 可重复读: REPEATABLE READ

             确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。

  • 串行化: SERIALIZABLE

             确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

5.传播行为 

在service类中有a()方法和b()方法, a()方法上有事务, b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。

一共有七种传播行为:

  1. REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
  2. SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】
  3. MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】
  4. REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一不新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】
  5. NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】
  6. NEVER:以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】。
  7. NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】

7.3.4全注解事务

@Configuration//配置类
@ComponentScan("com.xz.tx")
@EnableTransactionManagement//开启事务
public class SpringConfig {

    //创建数据源对象
    @Bean
    public DataSource getDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }

    //创建jdbcTemplate对象,注入数据源
    @Bean(name = "JdbcTemplate")
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate=new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager=new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);

        return dataSourceTransactionManager;
    }
}

 7.4基于xml的声明式事务

 

    <!--开启组件扫描-->
    <context:component-scan base-package="com.xz.xml"></context:component-scan>

    <!--引入外部属性文件-->
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>

    <!--创建数据源对象-->
    <bean id="druidDataSource" 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.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!--JdbcTemplate创建,注入数据源-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>

    <!--创建事务管理器,注入数据源-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>

    <!--配置事务的通知,设置属性-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="update*" read-only="false"/>
            <tx:method name="buy*" read-only="false" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!--配置切入点和通知使用的方法-->
    <aop:config>
        <aop:pointcut id="pt" expression="execution(* com.xz.xml.service.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor>
    </aop:config>

8.资源操作

Java的标准java.net.URL类和各种URL前缀的标准处理程序无法满足所有对low-level资源的访问,比如:没有标准化的URL实现可用于访问需要从类路径或相对于ServletContext获取的资源。并且缺少某些Spring所需要的功能,例如检测某资源是否存在等。而Spring的Resource声明了访问low-level资源的能力。

 8.1Resource接口

Spring的Resource接口位于org.springframework.core.io 中。旨在成为一个更强大的接口,用于抽象对低级资源的访问。 

1.UrlResource⭐

Resource的一个实现类,用来访问网络资源,它支持URL的绝对路径。

http------该前缀用于访问基于HTTP协议的网络资源。

ftp:------该前缀用于访问基于FTP协议的网络资源。

file:------该前缀用于从文件系统中读取资源。

public class UrlResource1 {
    public static void main(String[] args) {
        //http前缀
        loadUrlResource("http://www.baidu.com");

        //file前缀
        loadUrlResource("file:xz.txt");

    }

    //访问前缀是http、file
    public static void loadUrlResource(String path) {
        try {
            //1.创建Resource实现类UrlResource
            UrlResource urlResource = new UrlResource(path);
            //2.获取资源的相关信息
            System.out.println(urlResource.getFilename());
            System.out.println(urlResource.getURL());
            System.out.println(urlResource.getDescription());

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
}

2.ClassPathResource⭐

ClassPathResource用来访问类加载路径下的资源,相对于其他的Resource实现类,其主要优势是方便访问类加载路径里的资源,尤其对于Web应用, ClassPathResource可自动搜索位于classes下的资源文件,无须使用绝对路径访问。

public class ClassPathResourceDemo {

    public static void main(String[] args) {
        loadClassPathResource("xz.txt");
    }

    public static  void loadClassPathResource(String path){
        //创建对象
        ClassPathResource classPathResource=new ClassPathResource(path);
        System.out.println(classPathResource.getFilename());
        System.out.println(classPathResource.getPath());
        //获取文件内容
        try {
            InputStream inputStream = classPathResource.getInputStream();
            byte[] b=new byte[1024];
            while (inputStream.read(b)!=-1){
                System.out.println(new String(b));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

3.FileSystemResource⭐

 Spring提供的FileSystemResource类用于访问文件系统资源,使用FileSystemResource来访问文件系统资源并没有太大的优势,因为Java提供的File类也可用于访问文件系统资源。

//访问系统中的资源
public class FileSystemResourceDemo {
    public static void main(String[] args) {
        loadFileSystemResource("F:\\log4j2\\xz");

    }
 
    public static void loadFileSystemResource(String path){
        //创建对象
        FileSystemResource resource=new FileSystemResource(path);

        System.out.println(resource.getFilename());
        System.out.println(resource.getDescription());
        try {
            InputStream in= resource.getInputStream();
            byte[] b=new byte[1024];
            while (in.read(b)!=-1){
                System.out.println(new String(b));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.ServletContextResource

5.InputStreamResource

6.ByteArrayResource

8.2Resourceloader接口

Spring 提供如下两个标志性接口: 

  • (1) ResourceLoader:该接口实现类的实例可以获得一个Resource实例。
  • (2) ResourceLoaderAware :该接口实现类的实例将获得一个ResourceLoader的引用。

在ResourceLoader接口里有如下方法:

  • (1) Resource getResource (String location) : 该接口仅有这个方法,用于返回一个Resource实例。ApplicationContext实现类都实现ResourceLoader接口,因此ApplicationContext可直接获取Resource实例。

Spring将采用和ApplicationContext相同的策略来访问资源。也就是说

  • 如果ApplicationContext是FileSystemXmlApplicationContext, res就是FileSystemResource实例;
  • 如果ApplicationContext是ClassPathXmlApplicationContext, res就是ClassPathResource实例

当Spring应用需要进行资源访问时,实际上并不需要直接使用Resource实现类,而是调用ResourceLoader实例的getResource()方法来获得资源, ReosurceLoader将会负责选择Reosurce实现类,也就是确定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来

8.3ResourceLoaderAware接口

ResourceLoaderAware接口实现类的实例将获得一个ResourceLoader的引用, ResourceLoaderAware接口也提供了一个setResourceLoader()方法,该方法将由Spring容器负责调用, Spring容器会将一个ResourceLoader对象作为该方法的参数传入。如果把实现ResourceLoaderAware接口的Bean类部署在Spring容器中, Spring容器会将自身当成ResourceLoader作为setResourceLoader()方法的参数传入。由于ApplicationContext的实现类都实现了ResourceLoader接口,Spring容器自身完全可作为ResorceLoader使用。

8.4使用Resource作为属性

当应用程序中的Bean实例需要访问资源时, Spring有更好的解决方法:直接利用依赖注入

归纳起来,如果 Bean 实例需要访问资源,有如下两种解决方案:

  • 代码中获取 Resource 实例。
  • 使用依赖注入。

依赖注入:

<bean id="resourceBean" class="com.xz.di.ResourceBean">
        <property name="resource" value="classpath:xz.txt"></property>
    </bean>
public class ResourceBean {

    private Resource resource;

    public Resource getResource() {
        return resource;
    }

    public void setResource(Resource resource) {
        this.resource = resource;
    }

    public void test(){
        System.out.println(resource.getFilename());
    }
}

测试:

public class TestBean {
    @Test
    public void test1(){
        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
        ResourceBean resourceBean = context.getBean("resourceBean", ResourceBean.class);
        resourceBean.test();
    }
}

8.5应用程序下上文和资源路径

不管以怎样的方式创建ApplicationContext实例,都需要为ApplicationContext指定配置文件, Spring允许使用一份或多分XML配置文件。当程序创建ApplicationContext实例时,通常也是以Resource的方式来访问配置文件的,所以ApplicationContext完全支持ClassPathResource, FileSystemResource, ServletContextResource等资源访问方式。

ApplicationContext确定资源访问策略通常有两种方法:

  1. 使用ApplicationContext实现类指定访问策略。
  2. 使用前缀指定访问策略。

9.国际化:i18n

国际化也称作i18n,其来源是英文单词 internationalization的首末字符i和n, 18为中间的字符数。由于软件发行可能面向多个国家,对于不同国家的用户,软件显示不同语言的过程就是国际化。通常来讲,软件中的国际化是通过配置文件来实现的,假设要支撑两种语言,那么就需要两个版本的配置文件。

9.1java国际化

Java自身是支持国际化的。

java.util.Locale用于指定当前用户所属的语言环境等信息,。

java.util.ResourceBundle用于查找绑定对应的资源文件

Locale包含了language信息和country信息, Locale创建默认locale对象时使用的静态方法。

1.配置文件命名规则

basename_language_country.properties(基本名字+语言+国家)

必须遵循以上的命名规则, java才会识别。basename是必须的,语言和国家是可选的。

如果同时提供了messages.propertiesmessages_zh_CN.propertes两个配置文件,如果提供的locale符合en_CN,那么优先查找messages_en_CN.propertes配置文件,如果没查找到,再查找messages.properties配置文件。最后,提示下,所有的配置文件必须放在classpath中,一般放在resources目录下

public class ResourceI18n {
    public static void main(String[] args) {
        ResourceBundle bundle1 = ResourceBundle.getBundle("message", new Locale("zh", "CN"));
        String value1 = bundle1.getString("test");
        System.out.println(value1);

        ResourceBundle bundle2 = ResourceBundle.getBundle("message", new Locale("en", "GB"));
        String value2 = bundle2.getString("test");
        System.out.println(value2);
    }
}

9.2Spring国际化

spring中国际化是通过MessageSource这个接口来支持的。

常见实现类:

  • ResourceBundleMessageSource这个是基于Java的ResourceBundle基础类实现,允许仅通过资源名加载国际化资源。 
  • ReloadableResourceBundleMessageSource这个功能和第一个类的功能类似,多了定时刷新功能,允许在不重启系统的情况下,更新资源的信息。
  • StaticMessageSource它允许通过编程的方式提供国际化信息,一会我们可以通过这个来实现db中存储国际化信息的功能。

test=china{0},{1}
public class ResourceI18n_spring {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
        //动态传数组
        Object[] objs=new Object[]{"小张",new Date().toString()};
        String value = context.getMessage("test", objs, Locale.CHINA);
        System.out.println(value);
    }
}

10.数据校验Validation

在Spring中有多种校验的方式

  • 第一种是实现org.springframework.validation.Validator接口,在代码中调用这个类。
  • 第二种是按照Bean Validation方式来进行校验,即通过注解的方式。
  • 第三种是基于方法实现校验。

除此之外,还可以实现自定义校验

方式一: Validator接口

1.引入依赖

        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>7.0.5.Final</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>jakarta.el</artifactId>
            <version>4.0.1</version>
        </dependency>

2.创建实体类

public class Person {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

3.创建类实现接口和方法

public class PersonValidator implements Validator {

    /**
     * 表示此校验用在哪个类型上
     *
     * @param clazz
     * @return
     */
    @Override
    public boolean supports(Class<?> clazz) {
        return Person.class.equals(clazz);
    }

    /**
     * 校验规则
     * 设置校验逻辑地址,其中ValidationUtils,是spring封装的校验工具类,帮助快速实现校验
     *
     * @param target
     * @param errors
     */
    @Override
    public void validate(Object target, Errors errors) {
        //name不能为空
        ValidationUtils.rejectIfEmpty(errors,"name","name.empty","name is null");

        //age不能小于零,不能大于200
        Person p1=(Person) target;
        if (p1.getAge()<0){
            errors.rejectValue("age","age.value.error","age<0");
        }else if(p1.getAge()>200){
            errors.rejectValue("age","age.value.error.old","age>200");
        }

    }
}

4.测试:

//校验测试
public class TestPerson {
    public static void main(String[] args) {
        //1.创建person对象
        Person p1=new Person();
        p1.setName("小张");
        p1.setAge(-18);

        //2.创建person对应dataBinder
        DataBinder binder=new DataBinder(p1);

        //3.设置校验器
        binder.setValidator(new PersonValidator());

        //4.调用方法执行校验
        binder.validate();

        //5.输出校验结果
        BindingResult result = binder.getBindingResult();
        System.out.println(result.getAllErrors());

    }
}

方式二:Bean Validation注解实现

1.创建配置类

@Configuration
@ComponentScan("com.xz.validator.two")
public class ValidationConfig {
    @Bean
    public LocalValidatorFactoryBean validator(){
        return new LocalValidatorFactoryBean();
    }
}

2.创建实体类

public class User {
    @NotNull
    private String name;

    @Min(0)
    @Max(120)
    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

属性上常用注解:

  • @NotNull 限制必须不为null
  • @NotEmpty 只作用于字符串类型,字符串不为空,并且长度不为0
  • @NotBlank 只作用于字符串类型,字符串不为空,并且trim()后不为空串
  • @DecimalMax(value) 限制必须为一个不大于指定值的数字
  • @DecimalMin(value) 限制必须为一个不小于指定值的数字
  • @Max(value) 限制必须为一个不大于指定值的数字
  • @Min(value) 限制必须为一个不小于指定值的数字
  • @Pattern(value) 限制必须符合指定的正则表达式
  • @Size(max,min) 限制字符长度必须在min到max之间
  • @Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

3.创建校验器

1.原生的

@Component
public class MyValidation1 {

    //原生的:import jakarta.validation.Validator;
    @Autowired
    private Validator validator;

    public boolean validatorByUser(User user){
        Set<ConstraintViolation<User>> validate = validator.validate(user);
        return validate.isEmpty();
    }
}

 2.spring中的

import org.springframework.validation.Validator;
@Component
public class MyValidation2 {

    @Autowired
    private Validator validator;

    public boolean validatorByUser2(User user){
        BindException bindException=new BindException(user,user.getName());
        validator.validate(user,bindException);
        List<ObjectError> errors = bindException.getAllErrors();
        for (ObjectError error:errors) {
            System.out.println(error);
        }
        return bindException.hasErrors();
    }
}

4.测试

public class TestUser {
    @Test
    public void test1() {
        ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);
        MyValidation1 validation1 = context.getBean("myValidation1", MyValidation1.class);
        User user=new User();
        user.setName("小张");
        user.setAge(19);
        boolean result = validation1.validatorByUser(user);
        System.out.println(result);
    }

    @Test
    public void test2(){
        ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);
        MyValidation2 validation2 = context.getBean("myValidation2", MyValidation2.class);
        User user=new User();
        user.setName("小张");
        user.setAge(-100);
        boolean result = validation2.validatorByUser2(user);
        System.out.println(result);

    }
}

方式三:基于方法实现校验

1.创建配置类

@Configuration
@ComponentScan("com.xz.validator.three")
public class ValidationConfig {

    @Bean
    public MethodValidationPostProcessor validationPostProcessor(){
        return new MethodValidationPostProcessor();
    }
}

2.创建实体类

public class User {
    @NotNull
    private String name;

    @Max(100)
    @Min(0)
    private int age;

    @NotBlank//不为空
    @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号")
    private String phone;


    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

3.创建校验器

@Component
@Validated
public class MyValidator {

    public String testMethod(@NotNull @Valid User user){
        return user.toString();
    }
}

4.测试

public class TestUser {
    @Test
    public void test(){
        ApplicationContext context=new AnnotationConfigApplicationContext(ValidationConfig.class);
        MyValidator validator = context.getBean("myValidator", MyValidator.class);
        User user= new User();
        user.setName("小张");
        user.setAge(18);
        user.setPhone("13462716990");
        String method = validator.testMethod(user);
        System.out.println(method);
    }
}

方式四:自定义注解校验

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {CannotBlankValidation.class})
public @interface CannotBlank {
    //默认提示错误信息
    String message() default "不能包含空格";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };


    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    @Retention(RUNTIME)
    @Documented
    @interface List {

        CannotBlank[] value();
    }
}
public class CannotBlankValidation implements ConstraintValidator<CannotBlank,String> {
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value!=null&&value.contains(" ")){
            //获取默认提示语
            String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate();
            System.out.println("default Message:"+defaultConstraintMessageTemplate);
            return false;
        }
        return false;
    }
}

10.提前编译AOT

简单来讲:

JIT即时编译指的是在程序的运行过程中,将字节码转换为可在硬件上直接运行的机器码,并部署至托管环境中的过程。

而AOT 编译指的则是,在程序运行之前,便将字节码转换为机器码的过程。

 Spring6支持的AOT技术,这个GraalVM就是底层的支持, Spring也对GraalVM本机映像提供了一流的支持。GraalVM是一种高性能JDK,旨在加速用Java和其他JVM语言编写的应用程序的执行,同时还为JavaScript、Python和许多其他流行语言提供运行时。GraalVM提供两种运行Java应用程序的方法:在HotSpotJVM上使用Graal即时UIT)编译器作为提前(AOT)编译的本机可执行文件。GraalVM的多语言能力使得在单个应用程序中混合多种编程语言成为可能,同时消除了外语调用成本。GraalVM向HotSpot Java虚拟机添加了一个用Java编写的高级即时UIT)优化编译器。

GraalVM 具有以下特性:

  • (1)一种高级优化编译器,它生成更快、更精简的代码,需要更少的计算资源
  • (2) AOT 本机图像编译提前将Java 应用程序编译为本机二进制文件,立即启动,无需预热即可实现最高性能
  • (3) Polyglot编程在单个应用程序中利用流行语言的最佳功能和库,无需额外开销
  • (4)高级工具在 Java 和多种语言中调试、监视、分析和优化资源消耗

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

会敲代码的小张

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

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

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

打赏作者

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

抵扣说明:

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

余额充值