SSM-Spring

141 阅读6分钟

Spring

图片.png

图片.png

一、Spring概述

1.1 web项目开发中的耦合度问题

  • 在Servlet中需要调用service中的方法,则需要在Servlet类中通过new关键字创建Service的实例
  • 如果使用new关键字创建对象
    • 失去了面向接口编程的灵活性
    • 代码的侵入性增强(增加了耦合度,降低了代码的灵活性)
    public interface ProductService{
    public List<Product> listProducts();
    }
    
    public class ProductServiceImpl1 implements ProductService{
    public List<Product> listProducts(){
    //查询热销商品
       }
    }
    
     public class ProductServiceImpl2 implements ProductService{
    public List<Product> listProducts(){
    //查询好评商品
       }
    }
    
    public class ProductListServlet extends HttpServlet{
    //在servlet中使用new关键字创建ProductServiceImpl1对象,增加了servlet和service的耦合度
    private ProductService productService = new  ProductServiceImpl1();
    protected void doGet(HttpServletRequest request,HttpServletResponse response){
        doPost(request,response);
        }
    protected void doPost(HttpServletRequest request,HttpServletResponse response){
        productService.listProducts();
        }
    }
    

1.2 面向接口编程

面向接口编程 图片.png 解决方案:在servlet中定义service接口的对象变量,不使用new关键字创建实现类对象,在servlet的实例化的时候,通过反射动态的给service对象变量赋值。
如何实现:Spring可以做到!!!

1.3 Spring介绍

spring是一个轻量级的控制反转和面向切向的容器框架,用来解决企业项目开发的复杂度问题-解耦

  • 轻量级:体积小,对代码没有侵入性
  • 控制反转:: loc (lnverse of Control) ,把创建对象的工作交由Spring完成,Spring在创建对象的时候同时可以完成对象属性赋值 (DI)
  • 面向切面: AOP (Aspect Oriented Programming) 面向切面编程,可以在不改变原有业务逻辑的情况下实现对业务的增强
  • 容器:实例的容器,管理创建的对象

1.4 Spring架构

官网:spring.io/

图片.png

图片.png

图片.png

图片.png

图片.png

图片.png

二、入门开发步骤

图片.png

2.1 引入依赖

pom.xml

<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"  
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
<modelVersion>4.0.0</modelVersion>  
<parent>  
<groupId>org.example</groupId>  
<artifactId>spring6</artifactId>  
<version>1.0-SNAPSHOT</version>  
</parent>  
  
<groupId>com.xie</groupId>  
<artifactId>spring-first</artifactId>  
  
<properties>  
<maven.compiler.source>17</maven.compiler.source>  
<maven.compiler.target>17</maven.compiler.target>  
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
</properties>  
<dependencies>  
<dependency>  
<groupId>org.springframework</groupId>  
<artifactId>spring-context</artifactId>  
<version>6.0.9</version>  
</dependency>  
<dependency>  
<groupId>org.junit.jupiter</groupId>  
<artifactId>junit-jupiter-api</artifactId>  
<version>5.7.1</version>  
<scope>test</scope>  
</dependency>  
</dependencies>  
  
</project>

2.2 创建类、定义方法属性

package com.xie.spring6;  
  
public class User {  
public void add(){  
System.out.println("add.....");  
}  
  
public static void main(String[] args) {  
User user=new User();  
user.add();  
}  
}

2.3 创建配置文件

图片.png

2.4 配置bean.xml

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">  
<!-- 完成user对象创建  
bean标签  
id属性:唯一标识  
class属性:要创建对象所在类的全路径(包名称+类名称)  
-->  
<bean id="user" class="com.xie.spring6.User"></bean>  
</beans>

2.5 测试

package com.xie.spring6;  
  
import org.junit.Test;  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class TestUser {  
@Test  
public void testUserObject(){  
// 加载spring配置文件,对象创建  
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");  
// 获取创建的对象  
User user = (User) context.getBean("user");  
System.out.println(user);  
// 使用对象调用方法进行测试  
user.add();  
}  
}

2.6 相关问题

  1. 之前创建对象,无参数构造执行?
    答:执行了

  2. 不用new方式,还可以如何创建对象:

    1. 反射
  3. 创建对象放到哪里?

  4. 如何使用返回创建的对象

图片.png

图片.png

三、日志

图片.png

3.1 引入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>

3.2 加入日志配置文件

在类的根路径下提供log4j2.xml配置文件(文件名固定为log4j2.xml,文件必须放在类根路径下)

<?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="d:/spring6_log/test.log" append="false">  
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>  
</File>  
  
<!-- 这个会打印出所有的信息,  
每次大小超过size,  
则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,  
作为存档-->  
<RollingFile name="RollingFile" fileName="d:/spring6_log/app.log"  
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.3 手动写日志

import org.junit.Test;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class TestUser {  
private Logger logger= LoggerFactory.getLogger(TestUser.class);  
@Test  
public void testUserObject(){  
// 加载spring配置文件,对象创建  
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");  
// 获取创建的对象  
User user = (User) context.getBean("user");  
System.out.println("2 "+user);  
// 使用对象调用方法进行测试  
user.add();  
// 手动写日志  
logger.info("###执行调用成功了...");  
}

图片.png

Spring-loC

图片.png

图片.png

图片.png

图片.png

一、依赖注入

图片.png

图片.png

1.1 基于XML管理bean

1.1.1 搭建子模块spring6-ioc-xml

  1. 搭建模块
    搭建方式如:spring-frist
  2. 引入配置文件
    引入spring-first模块配置文件:bean.xml\log4j.xml
  3. 添加依赖
  4. 创建类
public class User {  
private String name;  
private Integer age;  
public void run(){  
System.out.println("run.....");  
}  
}

1.1.2 实验一:获取bean

  1. 方式一:根据id获取
    由于id属性指定了bean的唯一标识,所以根据bean标签的id属性可以精确获取到一个组件对象。上个实验中我们使用的就是这种方式。
  2. 方式二:根据类型获取
  3. 根据id和类型获取bean
public class TestUser {  
public static void main(String[] args) {  
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");  
// 1. 根据id获取bean  
User user1 = (User) context.getBean("user");  
System.out.println("1. 根据id获取 "+user1);  
  
// 2.根据类型获取bean  
User user2 = context.getBean(User.class);  
System.out.println("2. 根据类型获取 "+user2);  
  
// 3.根据id和类型  
User user3 = context.getBean("user", User.class);  
System.out.println("3. 根据id和类型获取 "+user3);  
}  
}

注意
当根据类型获取bean时,要求IOC容器中指定类型的bean有且只有一个 当IOC容器中一共配置了两个,根据类型获取会抛出异常

  1. 扩展知识
    如果组件类实现了接口,根据接只类型可以获取bean吗?

可以,前提是bean唯一

如果一个接口有多个实现类,这些实现类都配置了bean,根据接口类型可以获取bean吗?

不行,因为bean不唯一

1.2 setter注入

  1. 类有属性,创建对象过程中,向属性设置值
    1. 第一种方式:基于set方法完成
    2. 第二种方式:基于构造器完成

1.2.1 定义属性,生成方法

package com.xie.spring6IocXml.di;  
  
public class Book {  
private String bname;  
private String author;  
// 生成set方法  
  
  //set注入
public Book() {  
System.out.println("无参数构造");  
this.bname = bname;  
this.author = author;  
}  

//构造器注入
public Book(String bname, String author) {  
System.out.println("有参数构造");  
this.bname = bname;  
this.author = author;  
}
  
public void setBname(String bname) {  
this.bname = bname;  
}  
  
public void setAuthor(String author) {  
this.author = author;  
}  
  
public static void main(String[] args) {  
// 1.set方法注入  
Book book=new Book();  
book.setBname("java");  
book.setAuthor("哈哈哈");  
  
// 2.通过构造器注入  
Book book1=new Book("c++","哈哈哈");  
  
}  
}

1.2.2在spring配置文件配置

<bean id="book" class="com.xie.spring6IocXml.di.Book">  
<property name="bname" value="Vue"></property>  
<property name="author" value="哈哈哈"></property>  
</bean>

1.2.3 测试类

  1. 先生产tostring方法
  2. 新建测试类
public class TestBook {  
@Test  
public void testSetter(){  
ApplicationContext context=new ClassPathXmlApplicationContext("bean-di.xml");  
Book book = context.getBean("book", Book.class);  
System.out.println(book);  
}  
}

1.3 基于构造器注入

1.3.1 创建类,定义属性,生成有参数构造方法

图片.png

代码如上setter注入

1.3.2 进行配置

<bean id="bookCon" class="com.xie.spring6IocXml.di.Book">  
<constructor-arg name="bname" value="javaweb"></constructor-arg>  
<constructor-arg name="author" value="哈哈哈"></constructor-arg>  
</bean>

1.4 特殊值处理

1.4.1 字面量赋值

例如:private String i="哈哈哈";
哈哈哈就是字面量。就是看到的值

1.4.2 null值

如果有一个值为空,则添加一个null标签

<bean id="book" class="com.xie.spring6IocXml.di.Book">  
<property name="bname" value="Vue"></property>  
<property name="author" value="哈哈哈"></property>  
<property name="others">  
<null/>  
</property>  
</bean>

1.4.3 xml实体

图片.png 如果带有这种符号的,会出错,应该转义处理

<bean id="book" class="com.xie.spring6IocXml.di.Book">  
<property name="bname" value="Vue"></property>  
<property name="author" value="哈哈哈"></property>  
<!-- <property name="others">-->  
<!-- <null/>-->  
<!-- </property>-->  
<property name="others" value="&lt;&gt;"></property>  
</bean>

1.4.4 CDATA节

图片.png

<bean id="book" class="com.xie.spring6IocXml.di.Book">  
<property name="bname" value="Vue"></property>  
<property name="author" value="哈哈哈"></property>  
<!-- <property name="others">-->  
<!-- <null/>-->  
<!-- </property>-->  
<!-- <property name="others" value="&lt;&gt;"></property>-->  
<property name="others">  
<value><![CDATA[a < b]]></value>  
</property>  
</bean>

a<b就是others的值

1.5 特殊类型属性注入

1.5.1 对象类型属性注入

图片.png

//部门类  
public class Dept {  
private String dname;  
  
public String getDname() {  
return dname;  
}  
  
public void setDname(String dname) {  
this.dname = dname;  
}  
  
public void info(){  
System.out.println("部门名称:"+dname);  
}  
}
//员工类  
public class Emp {  
// 对象属性类型:员工属于某个部门  
private Dept dept;  
private String ename;  
private Integer age;  
  
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;  
}  
  
public void work(){  
System.out.println(ename+"emp work...."+age+"岁");  
dept.info();  
}  
}
1.5.1.1 方法一:引用外部bean
<!--  
第一种方式:引入外部bean  
1 创建两个类对象: dept 和 emp  
2 在emp的bean标签里面,使用property引入dept的bean  
-->  
<bean id="dept" class="com.xie.spring6IocXml.ditest.Dept">  
<property name="dname" value="安保部"></property>  
</bean>  
<bean id="emp" class="com.xie.spring6IocXml.ditest.Emp">  
<!-- 对象属性类型注入-->  
<property name="dept" ref="dept"></property>  
  
<!-- 普通属性注入-->  
<property name="ename" value="张三"></property>  
<property name="age" value="23"></property>  
</bean>

测试

public class TestEmp {  
public static void main(String[] args) {  
ApplicationContext context=new ClassPathXmlApplicationContext("bean-ditest.xml");  
// 员工对象  
Emp emp=context.getBean("emp",Emp.class);  
emp.work();  
}  
}
1.5.1.2 方法二:内部bean
<!--第二种方式:内部bean-->  
<bean id="emp2" class="com.xie.spring6IocXml.ditest.Emp">  
<!-- 普通属性注入-->  
<property name="ename" value="李四"></property>  
<property name="age" value="20"></property>  
<!-- 内部bean-->  
<property name="dept">  
<bean id="dept2" class="com.xie.spring6IocXml.ditest.Dept">  
<property name="dname" value="财务部"></property>  
</bean>  
</property>  
</bean>
1.5.1.3 方法三:级联属性赋值
<!-- 第三种方式-->  
<bean id="dept3" class="com.xie.spring6IocXml.ditest.Dept">  
<property name="dname" value="技术研发部"></property>  
</bean>  
<bean id="emp3" class="com.xie.spring6IocXml.ditest.Emp">  
<property name="ename" value="李华"></property>  
<property name="age" value="22"></property>  
<property name="dept" ref="dept3"></property>  
<property name="dept.dname" value="测试部"></property>  
</bean>

部门是测试部

1.5.2 数组类型属性赋值

package com.xie.spring6IocXml.ditest;  
  
import java.lang.reflect.Array;  
import java.util.Arrays;  
  
//员工类  
public class Emp {  
// 对象属性类型:员工属于某个部门  
private Dept dept;  
private String ename;  
private Integer age;  
private String[] loves;  
  
  
public void setLoves(String[] loves) {  
this.loves = loves;  
}  
  
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;  
}  
  
public void work(){  
System.out.println(ename+"emp work...."+age+"岁");  
dept.info();  
System.out.println(Arrays.toString(loves));  
}  
}
<!-- 数组类型属性-->  
<bean id="dept" class="com.xie.spring6IocXml.ditest.Dept">  
<property name="dname" value="安保部"></property>  
</bean>  


<bean id="emp" class="com.xie.spring6IocXml.ditest.Emp">  
<!-- 普通属性-->  
<property name="ename" value="李四"></property>  
<property name="age" value="20"></property>  
<!-- 对象类型属性 -->  
<property name="dept" ref="dept"></property>  
<!-- 数组类型属性-->  
<property name="loves">  
<array>  
<value>吃饭</value>  
<value>睡觉</value>  
<value>打豆豆</value>  
</array>  
</property>  
</bean>

1.5.3 集合类型属性赋值

//部门类  
public class Dept {  
private List<Emp> empList;  
private String dname;  
  
public String getDname() {  
return dname;  
}  
  
public void setDname(String dname) {  
this.dname = dname;  
}  
  
public List<Emp> getEmpList() {  
return empList;  
}  
  
public void setEmpList(List<Emp> empList) {  
this.empList = empList;  
}  
  
public void info(){  
System.out.println("部门名称:"+dname);  
for(Emp emp:empList){  
System.out.println(emp.getEname());  
}  
}  
}
<bean id="emp1" class="com.xie.spring6IocXml.ditest.Emp">  
<property name="ename" value="张三"></property>  
<property name="age" value="23"></property>  
</bean>  
<bean id="emp2" class="com.xie.spring6IocXml.ditest.Emp">  
<property name="ename" value="李四"></property>  
<property name="age" value="29"></property>  
</bean>  
  
<bean id="dept" class="com.xie.spring6IocXml.ditest.Dept">  
<property name="dname" value="财务部"></property>  
<property name="empList">  
<list>  
<ref bean="emp1"></ref>  
<ref bean="emp2"></ref>  
</list>  
</property>  
</bean>

测试:

public class TestDept {  
public static void main(String[] args) {  
ApplicationContext context=new ClassPathXmlApplicationContext("bean-dilist.xml");  
// 员工对象  
Dept dept=context.getBean("dept",Dept.class);  
dept.info();  
}  
}

1.5.4 Map类型属性

public class Student {  
private Map<String,Teacher> teacherMap;  
private String sid;  
private String sname;  
  
public void study(){  
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 String getSid() {  
return sid;  
}  
  
public void setSid(String sid) {  
this.sid = sid;  
}  
  
public String getSname() {  
return sname;  
}  
  
public void setSname(String sname) {  
this.sname = sname;  
}  
}
public class Teacher {  
private String teacherId;  
private String teacherName;  
  
public String getTeacherId() {  
return teacherId;  
}  
  
public void setTeacherId(String teacherId) {  
this.teacherId = teacherId;  
}  
  
public String getTeacherName() {  
return teacherName;  
}  
  
public void setTeacherName(String teacherName) {  
this.teacherName = teacherName;  
}  
  
@Override  
public String toString() {  
return "Teacher{" +  
"teacherId='" + teacherId + '\'' +  
", teacherName='" + teacherName + '\'' +  
'}';  
}  
}
<!--  
1. 创建两个对象  
2. 注入普通类型属性  
3. 在学生bean注入map集合类型属性  
-->  
<bean id="teacher1" class="com.xie.spring6IocXml.dimap.Teacher">  
<!-- 注入普通·类型属性-->  
<property name="teacherId" value="100"></property>  
<property name="teacherName" value="大学教授"></property>  
</bean>  
<bean id="teacher2" class="com.xie.spring6IocXml.dimap.Teacher">  
<!-- 注入普通·类型属性-->  
<property name="teacherId" value="101"></property>  
<property name="teacherName" value="高中老师"></property>  
</bean>  
  
<bean id="student" class="com.xie.spring6IocXml.dimap.Student">  
<!-- 注入普通类型属性-->  
<property name="sid" value="2021"></property>  
<property name="sname" value="张三"></property>  
<!-- 在学生bean注入map集合类型属性-->  
<property name="teacherMap">  
<map>  
<entry>  
<key>  
<value>10010</value>  
</key>  
<ref bean="teacher1"></ref>  
</entry>  
<entry>  
<key>  
<value>10011</value>  
</key>  
<ref bean="teacher2"></ref>  
</entry>  
</map>  
</property>  
</bean>

测试

public class TestEmp {  
public static void main(String[] args) {  
ApplicationContext context=new ClassPathXmlApplicationContext("bean-diarray.xml");  
// 员工对象  
Emp emp=context.getBean("emp",Emp.class);  
emp.work();  
}  
}

1.5.5 引入集合bean

使用util:list、util:map标签必须引入相应的命名空间,可以通过idea的提示功能选择

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
xmlns:util="http://www.springframework.org/schema/util"  
xsi:schemaLocation="http://www.springframework.org/schema/util  
http://www.springframework.org/schema/util/spring-util.xsd  
http://www.springframework.org/schema/beans  
http://www.springframework.org/schema/beans/spring-beans.xsd">
public class Lesson {  
private String lessonName;  
  
public String getLessonName() {  
return lessonName;  
}  
  
public void setLessonName(String lessonName) {  
this.lessonName = lessonName;  
}  
  
@Override  
public String toString() {  
return "Lesson{" +  
"lessonName='" + lessonName + '\'' +  
'}';  
}  
}
public class Student {  
private List<Lesson> lessonList;  
private Map<String,Teacher> teacherMap;  
private String sid;  
private String sname;  
  
public void study(){  
System.out.println("学生编号:"+sid+"学生姓名:"+sname);  
System.out.println(teacherMap);  
System.out.println(lessonList);  
}  
  
public List<Lesson> getLessonList() {  
return lessonList;  
}  
  
public void setLessonList(List<Lesson> lessonList) {  
this.lessonList = lessonList;  
}  
  
public Map<String, Teacher> getTeacherMap() {  
return teacherMap;  
}  
  
public void setTeacherMap(Map<String, Teacher> teacherMap) {  
this.teacherMap = teacherMap;  
}  
  
public String getSid() {  
return sid;  
}  
  
public void setSid(String sid) {  
this.sid = sid;  
}  
  
public String getSname() {  
return sname;  
}  
  
public void setSname(String sname) {  
this.sname = sname;  
}  
}
<bean id="lesson1" class="com.xie.spring6IocXml.dimap.Lesson">  
<property name="lessonName" value="java开发"></property>  
</bean>  
<bean id="lesson2" class="com.xie.spring6IocXml.dimap.Lesson">  
<property name="lessonName" value="小程序开发"></property>  
</bean>  
  
<bean id="teacher1" class="com.xie.spring6IocXml.dimap.Teacher">  
<property name="teacherId" value="100"></property>  
<property name="teacherName" value="张三"></property>  
</bean>  
<bean id="teacher2" class="com.xie.spring6IocXml.dimap.Teacher">  
<property name="teacherId" value="101"></property>  
<property name="teacherName" value="李四"></property>  
</bean>  
  
<bean id="student" class="com.xie.spring6IocXml.dimap.Student">  
<property name="sid" value="10000"></property>  
<property name="sname" value="小萝卜"></property>  
<!-- 注入list、map类型属性-->  
<property name="lessonList" ref="lessonList"></property>  
<property name="teacherMap" ref="teacherMap"></property>  
</bean>  
<util:list id="lessonList">  
<ref bean="lesson1"></ref>  
<ref bean="lesson2"></ref>  
</util:list>  
<util:map id="teacherMap">  
<entry>  
<key>  
<value>10001</value>  
</key>  
<ref bean="teacher1"></ref>  
</entry>  
<entry>  
<key>  
<value>10002</value>  
</key>  
<ref bean="teacher2"></ref>  
</entry>  
</util:map>

测试

public class TestDiMap {  
@Test  
public void testStu(){  
ApplicationContext context=new ClassPathXmlApplicationContext("bean-diref.xml");  
Student student = context.getBean("student", Student.class);  
student.study();  
}  
}

1.5.6 p命名空间

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
xmlns:util="http://www.springframework.org/schema/util"  
xmlns:p="http://www.springframework.org/schema/p"  
xsi:schemaLocation="http://www.springframework.org/schema/util  
http://www.springframework.org/schema/util/spring-util.xsd  
http://www.springframework.org/schema/beans  
http://www.springframework.org/schema/beans/spring-beans.xsd">  
<!-- p命名空间注入-->  
<bean id="studentp" class="com.xie.spring6IocXml.dimap.Student"  
p:sid="100" p:sname="mary" p:lessonList-ref="lessonList" p:teacherMap-ref="teacherMap">  
</bean>  
  
<!--  
1. 创建三个对象  
2. 注入普通类型属性  
3. 使用util:类型属性  
4. 在学生bean引入util:类型定义bean,完成list、map类型属性注入  
-->  
<bean id="lesson1" class="com.xie.spring6IocXml.dimap.Lesson">  
<property name="lessonName" value="java开发"></property>  
</bean>  
<bean id="lesson2" class="com.xie.spring6IocXml.dimap.Lesson">  
<property name="lessonName" value="小程序开发"></property>  
</bean>  
  
<bean id="teacher1" class="com.xie.spring6IocXml.dimap.Teacher">  
<property name="teacherId" value="100"></property>  
<property name="teacherName" value="张三"></property>  
</bean>  
<bean id="teacher2" class="com.xie.spring6IocXml.dimap.Teacher">  
<property name="teacherId" value="101"></property>  
<property name="teacherName" value="李四"></property>  
</bean>  
  
<bean id="student" class="com.xie.spring6IocXml.dimap.Student">  
<property name="sid" value="10000"></property>  
<property name="sname" value="小萝卜"></property>  
<!-- 注入list、map类型属性-->  
<property name="lessonList" ref="lessonList"></property>  
<property name="teacherMap" ref="teacherMap"></property>  
</bean>  
  
  
<util:list id="lessonList">  
<ref bean="lesson1"></ref>  
<ref bean="lesson2"></ref>  
</util:list>  
<util:map id="teacherMap">  
<entry>  
<key>  
<value>10001</value>  
</key>  
<ref bean="teacher1"></ref>  
</entry>  
<entry>  
<key>  
<value>10002</value>  
</key>  
<ref bean="teacher2"></ref>  
</entry>  
</util:map>  
</beans>

测试

public class TestDiMap {  
@Test  
public void testStu(){  
ApplicationContext context=new ClassPathXmlApplicationContext("bean-diref.xml");  
Student student = context.getBean("studentp", Student.class);  
student.study();  
}  
}

1.5.7 引入外部属性文件

图片.png

1.5.7.1 加入依赖
<dependencies>  
<dependency>  
<groupId>mysql</groupId>  
<artifactId>mysql-connector-java</artifactId>  
<version>8.0.31</version>  
</dependency>  
<dependency>  
<groupId>com.alibaba</groupId>  
<artifactId>druid</artifactId>  
<version>1.2.16</version>  
</dependency>  
</dependencies>
1.5.7.2 创建外部属性文件
jdbc.user=root  
jdbc.password=root  
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC  
jdbc.driver=com.mysql.cj.jdbc.Driver
1.5.7.3创建spring配置文件
<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
xmlns:context="http://www.springframework.org/schema/context"  
xsi:schemaLocation="http://www.springframework.org/schema/context  
http://www.springframework.org/schema/context/spring-context.xsd  
http://www.springframework.org/schema/beans  
http://www.springframework.org/schema/beans/spring-beans.xsd">  
<!-- 引入外部属性文件-->  
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>  
<!-- 完成数据库信息注入-->  
<bean id="druidDataSource" 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>  
</beans>
1.5.7.4 测试
public class TestJdbc {  
@Test  
public void demo1(){  
DruidDataSource dataSource=new DruidDataSource();  
dataSource.setUrl("jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC");  
dataSource.setUsername("root");  
dataSource.setPassword("123456");  
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");  
}  
  
@Test  
public void demo2(){  
ApplicationContext context=new ClassPathXmlApplicationContext("bean-jdbc.xml");  
DruidDataSource dataSource = context.getBean(DruidDataSource.class);  
System.out.println(dataSource.getUrl());  
}  
}

1.5.8 bean的作用域

图片.png

public class Orders {  
}
<!-- 通过scope属性配置单实例还是多实例-->  
<bean id="orders" class="com.xie.spring6IocXml.scope.Orders" scope="prototype">  
</bean>

测试

public class TestOrders {  
public static void main(String[] args) {  
ApplicationContext context=new ClassPathXmlApplicationContext("bean-scope.xml");  
Orders orders = context.getBean("orders", Orders.class);  
System.out.println(orders);  
Orders orders1 = context.getBean("orders", Orders.class);  
System.out.println(orders1);  
}  
}

1.5.9 bean生命周期

  1. 具体的生命周期过程
    • bean对象创建 (调用无参构造器)
    • 给bean对象设置属性
    • bean的后置处理器 (初始化之前)
    • bean对象初始化(需在配置bean时指定初始化方法)
    • bean的后置处理器 (初始化之后)
    • bean对象就绪可以使用
    • bean对象销毁 (需在配置bean时指定销毁方法)
    • IOC容器关闭
  2. bean的后置处理器
public class MyBeanPost implements BeanPostProcessor {  
@Override  
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {  
System.out.println("3 bean后置处理器,初始化之前执行");  
System.out.println(beanName+"::"+bean);  
return bean;  
}  
  
@Override  
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {  
System.out.println("5 bean后置处理器,初始化之后执行");  
System.out.println(beanName+"::"+bean);  
return bean;  
}  
}

配置

<bean id="myBeanPost" class="com.xie.spring6IocXml.life.MyBeanPost"></bean>

图片.png

1.5.10 FactoryBean

  1. 创建类
public class MyFactoryBean implements FactoryBean<User> {  
  
@Override  
public User getObject() throws Exception {  
return new User();  
}  
  
@Override  
public Class<?> getObjectType() {  
return User.class;  
}  
}
  1. 配置
<bean id="myFactoryBean" class="com.xie.spring6IocXml.factorybean.MyFactoryBean"></bean>
  1. 测试
public class TestUser {  
public static void main(String[] args) {  
ApplicationContext context=new ClassPathXmlApplicationContext("bean-factory.xml");  
User user = (User) context.getBean("myFactoryBean");  
System.out.println(user);  
}  
}

1.5.11 基于xml自动装配

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

public class UserController {  
private UserService userService;  
  
public void setUserService(UserService userService) {  
this.userService = userService;  
}  
  
public void addUser(){  
System.out.println("controller方法执行了。。。。");  
// 调用service方法  
userService.addUserService();  
}  
}
public interface UserDao {  
public void addUserDao();  
}
@Override  
public void addUserDao() {  
System.out.println("userDao方法执行");  
}  
}
public interface UserService {  
public void addUserService();  
}
public class UserServiceImpl implements UserService{  
private UserDao userDao;  
  
public void setUserDao(UserDao userDao) {  
this.userDao = userDao;  
}  
  
@Override  
public void addUserService() {  
System.out.println("userService执行了");  
userDao.addUserDao();  
// UserDao userDao=new UserDaoImpl();  
// userDao.addUserDao();  
}  
}
<!--根据类型自动装配-->
<bean id="userController" class="com.xie.spring6IocXml.auto.controller.UserController" autowire="byType">  
</bean>  
<bean id="userService" class="com.xie.spring6IocXml.auto.service.UserServiceImpl" autowire="byType"></bean>  
<bean id="userDao" class="com.xie.spring6IocXml.auto.dao.UserDaoImpl"></bean>

测试

public class TestUser {  
public static void main(String[] args) {  
ApplicationContext context=new ClassPathXmlApplicationContext("bean-aotu.xml");  
UserController userController = context.getBean("userController", UserController.class);  
userController.addUser();  
}  
}

注意

图片.png

也可以根据名称装配,如果是名称,则id要和类里的名字一样。autowire="byName

1.6 基于注解管理Bean

从Java5开始,Java增加了对注解(Annotation)的支持,它是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在源代码中嵌入补充 信息。
Spring从2.5版本开始提供了对注解技术的全面支持,我们可以使用注解来实现自动装配,简化Spring的xml配置。

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

  1. 引入依赖
  2. 开启组件扫描
  3. 使用注解定义Bean
  4. 依赖注入

图片.png

1.6.1 搭建子模块

1.6.2 开启组件扫描

Spring默认不使用注解装配Bean,因此我们需要在Spring的XML配置中,通过context:component-scan元素开启Spring Beans的自动扫描功能。开启此功能后,Spring会自动从扫描指定的包(base-package属性设置)及其子包下的所有类,如果类上使用了@Component注解,就将该类装配到容器中。

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

图片.png

图片.png

图片.png

//@Component(value = "user")//<bean id="user"...  
//@Repository(value = "user")  
//@Service  
@Controller  
public class User {  
}
<context:component-scan base-package="com.xie.spring6"></context:component-scan>

测试

public class TestUser {  
public static void main(String[] args) {  
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");  
User user = context.getBean(User.class);  
System.out.println(user);  
}  
}

1.6.3 @Autowired注入

根据类型进行装配 源码中有两处需要注意:

  • 第一处:该注解可以标注在哪里?
    • 构造方法上
    • 方法上
    • 形参上
    • 属性上
    • 注解上
  • 第二处:该注解有一个required属性,默认值是true,表示在注入的时候要求被注入的Bean必须是存在的,如果不存在则报错。如果required属性设置为false,表示注入的Bean存在或者不存在都没关系,存在的话就注入,不存在的话,也不报错。
1.6.3.1 属性注入
  1. bean对象创建
  2. 定义相关属性,在属性上面添加注解
<!-- 开启组件扫描-->  
<context:component-scan base-package="com.xie.spring6"></context:component-scan>

controller

@Controller  
public class UserController {  
// 注入service  
// 第一种方式:属性注入  
@Autowired //根据类型找到对应对象,完成注入  
private UserService userService;  
  
public void add(){  
System.out.println("controller....");  
userService.add();  
}  
  
}

service

public interface UserService {  
public void add();  
}
@Service  
public class UserServiceImpl implements UserService{  
// 注入Dao  
// 第一种方式:属性注入  
@Autowired //根据类型找到对应对象,完成注入  
private UserDao userDao;  
  
@Override  
public void add() {  
System.out.println("service....");  
userDao.add();  
}  
}

dao

public interface UserDao {  
public void add();  
}
@Repository  
public class UserDaoImpl implements UserDao{  
@Override  
public void add() {  
System.out.println("dao.....");  
}  
}

测试

public class TestUser {  
public static void main(String[] args) {  
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");  
UserController controller = context.getBean(UserController.class);  
controller.add();  
}  
}
1.6.3.2 set注入

controller

@Controller  
public class UserController {  
// 注入service  
// 第一种方式:属性注入  
// @Autowired //根据类型找到对应对象,完成注入  
// private UserService userService;  
  
// 第二种方式:set注入  
private UserService userService;  
@Autowired  
public void setUserService(UserService userService) {  
this.userService = userService;  
}  
  
public void add(){  
System.out.println("controller....");  
userService.add();  
}  
  
}

service

@Service  
public class UserServiceImpl implements UserService{  
// 注入Dao  
// 第一种方式:属性注入  
// @Autowired //根据类型找到对应对象,完成注入  
// private UserDao userDao;  
  
// 第二种方式:set注入  
private UserDao userDao;  
@Autowired  
public void setUserDao(UserDao userDao) {  
this.userDao = userDao;  
}  
  
@Override  
public void add() {  
System.out.println("service....");  
userDao.add();  
}  
}
1.6.3.3 构造方法注入

controller

// 第三种方式:构造方法注入  
private UserService userService;  
@Autowired  
public UserController(UserService userService) {  
this.userService = userService;  
}  
  
public void add(){  
System.out.println("controller....");  
userService.add();  
}

service

// 第三种方式:构造方法注入  
private UserDao userDao;  
@Autowired  
public UserServiceImpl(UserDao userDao) {  
this.userDao = userDao;  
}  
  
@Override  
public void add() {  
System.out.println("service....");  
userDao.add();  
}
1.6.3.4 形参上注入
private UserService userService;  
public UserController(@Autowired UserService userService) {  
this.userService = userService;  
}
private UserDao userDao;  
public UserServiceImpl(@Autowired UserDao userDao) {  
this.userDao = userDao;  
}
1.6.3.5 只有一个构造函数,无注解

当有参数的构造方法只有一个时@Autowired注解可以省略。
说明:有多个构造方法时呢?大家可以测试(再添加一个无参构造函数),测试报错

private UserDao userDao;  
  
public UserServiceImpl(UserDao userDao) {  
this.userDao = userDao;  
}

只有一个构造函数是时,不用注解也行,但要是加了其他的构造函数,则必须有注解

1.6.3.6 @Autowired注解和@Qualifier注解联合

图片.png Dao有两个实现类

// 最后一种方式:两个注解,根据名称注解  
@Autowired  
@Qualifier(value = "userDaoImpl2")  
private UserDao userDao;  
  
@Override  
public void add() {  
System.out.println("service....");  
userDao.add();  
}

1.6.4 @Resource注入

@Resource注解也可以完成属性注入。那它和@Autowired汪解有什么区别?

  • @Resource注解是DK扩展包中的,也就是说属于刊DK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)
  • @Autowired注解是Spring框架自己的。
  • @Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType:装配。
  • @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用。
  • @Resource注解用在属性上、setter方法上。
  • @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。

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

<dependency>  
<groupId>jakarta.annotation</groupId>  
<artifactId>jakarta.annotation-api</artifactId>  
<version>2.1.1</version>  
</dependency>  
1.6.4.1 根据名称

图片.png

1.6.4.2 根据属性名

图片.png

1.6.4.3 根据类型

图片.png

总结:
@Resource注解:默认byName注入,没有指定name时把属性名当做nam根据name找不到时,才会byType注入。byType注入时,某种类型的Bean只能有一个

1.6.5 spring全注解开发

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

新建配置类

@Configuration //配置类  
@ComponentScan("com.xie.spring6") //开启组件扫描,替代了配置文件  
public class SpringConfig {  
}

测试

public class TestUserControllerAnno {  
public static void main(String[] args) {  
// 加载配置类  
ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);  
UserController controller = context.getBean(UserController.class);  
controller.add();  
}  
}

二、手写IOC

2.1 回顾Java反射

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

要想解剖一个类,必须先要获取到该类的Clss对象。而剖析一个类或用反射解决具体的问题就是使用相关API
(1)java.lang.Class   (2)java.lang.reflect,   所以,Classi对象是反射的根源。

public class TestCar {  
// 1.获取class对象多种方式  
@Test  
public void test1() throws Exception {  
// 1.类名.class  
Class clazz1= Car.class;  
// 2. 对象.getClass()  
Class clazz2=new Car().getClass();  
// 3. Class.forName("全路径")  
Class clazz3 = Class.forName("com.xie.reflect.Car");  
// 实例化  
Car car =(Car) clazz3.getConstructor().newInstance();  
System.out.println(car);  
}  
// 2.获取构造方法  
@Test  
public void test2() throws Exception {  
Class clazz=Car.class;  
// 获取所有的构造  
  
Constructor[] constructors = clazz.getConstructors();//getConstructors():获取所有public方法  
//getDeclaredConstructors():获取所有的构造方法 public,private  
Constructor[] declaredConstructors = 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("奔驰", 10, "白色");  
System.out.println(car1);  
// 2.构造private  
Constructor c2 = clazz.getDeclaredConstructor(String.class, int.class, String.class);  
c2.setAccessible(true);  
Car car2 = (Car) c2.newInstance("捷达", 10, "红色");  
System.out.println(car2);  
}  
// 3.获取属性  
@Test  
public void test3()throws Exception{  
Class<Car> clazz=Car.class;  
Car car=(Car)clazz.getDeclaredConstructor().newInstance();  
// 获取所有的public属性  
Field[] fields1=clazz.getFields();  
// 获取所有属性(包含私有属性)  
Field[] fields2=clazz.getDeclaredFields();  
for(Field field:fields2){  
if(field.getName().equals("name")){  
// 设置允许访问  
field.setAccessible(true);  
field.set(car,"五菱宏光");  
}  
System.out.println(field.getName());  
System.out.println(car);  
}  
}  
// 4. 获取方法  
@Test  
public void test4()throws Exception{  
Car car = new Car("奔驰",10,"黑色");  
Class clazz = car.getClass();  
// 1.public方法  
Method[] methods = clazz.getMethods();  
for(Method m1:methods){  
// System.out.println(m1.getName());  
// 执行方法(比如toString)  
if(m1.getName().equals("toString")){  
String invoke =(String) m1.invoke(car);  
System.out.println("toString方法执行了"+invoke);  
}  
}  
// 2.private方法  
Method[] methodsAll = clazz.getDeclaredMethods();  
for(Method m2:methodsAll){  
// 执行方法run  
if(m2.getName().equals("run")){  
m2.setAccessible(true);  
m2.invoke(car);  
}  
}  
}  
}

2.2 实现Spring的IoC

我们知道,IoC(控制反转)和D1(依赖注入)是Spring里面核心的东西,那么,我们如何自己手写出这样的代码呢?下面我们就一步一步写出Spring框架最核心的部分。

图片.png

  1. 搭建子模块
  2. 准备测试需要的bean
    1. 添加依赖

三、代理设计模式

图片.png 代理设计模式的优点:将通用性的工作都交给代理对象完成,被代理对象只需专注自己的核心业务

3.1 静态代理

静态代理:代理类只能够为特定的类生产代理对象,不能代理任意类(即只有实现的GeneralDao的类才可以被代理)

图片.png 使用代理的好处

  1. 被代理类中只用关注核心业务的实现,将通用的管理型逻辑(事务管理、日志管理)和业务逻辑分离
  2. 将通用的代码放在代理类中实现,提供了代码的复用性
  3. 通过在代理类添加业务逻辑,实现对原有业务逻辑的扩展(增强)

3.2 动态代理

动态代理,几乎可以为所有的类产生代理对象

动态代理的实现方式有2种:

  • JDK动态代理
  • CGLib动态代理
  • JDK动态代理类实现
/**  
*  
* JDK动态代理:是通过被代理对象实现的接口产生其代理对象的  
* 1.创建一个类,实现InvocationHandler接口,重写invoke方法  
* 2.在类种定义一个0 biect类型的变量,并提供这个变量的有参构造器,用于将被代理对象传递进来  
* 3.定义getProxy方法,用于创建并返回代理对象  
* */  
public class JDKMyDynamicProxy implements InvocationHandler{  
// 被代理对象  
private Object obj;  
  
public JDKMyDynamicProxy(Object obj) {  
this.obj = obj;  
}  
  
// 产生代理对象,返回代理对象  
public Object getProxy(){  
// 1.获取被代理对象的类加载器  
ClassLoader classLoader = obj.getClass().getClassLoader();  
// 2.获取被代理对象的类实现的接口  
Class<?>[] interfaces = obj.getClass().getInterfaces();  
// 3. 产生代理对象(通过被代理对象的类加载器及实现的接口)  
// 第一个参数:被代理对象的类加载器  
// 第二个参数:被代理对象实现的接口  
// 第三个参数:使用产生代理对象调用方法时,用于拦截方法执行的处理器  
Object proxy = Proxy.newProxyInstance(classLoader, interfaces,this);  
return proxy;  
}  
  
@Override  
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
begin();  
Object returnValue = method.invoke(obj);//执行method方法  
commit();  
return returnValue;  
}  
public void begin(){  
System.out.println(".....开启事务");  
}  
public void commit(){  
System.out.println("....提交事务");  
}  
}

测试

public class TestDynamicProxy {  
public static void main(String[] args) {  
// 创建被代理对象  
BookDaoImpl bookDao=new BookDaoImpl();  
StudentDaoImpl studentDao=new StudentDaoImpl();  
// 创建动态代理类对象,并将被代理对象传递到代理类中赋值给obj  
JDKMyDynamicProxy jdkMyDynamicProxy=new JDKMyDynamicProxy(studentDao);  
// proxy就是产生的代理对象,产生的代理对象可以强转成被代理对象实现的接口类型  
GenaralDao proxy=(GenaralDao)jdkMyDynamicProxy.getProxy();  
// 代理对象调用方法,并不会调用方法,而是进入到创建代理对象指定的InvocationHandler类中的invoke方法  
// 调用的方法作为一个Method参数,传递给了invoke方法  
proxy.delete();  
  
}  
}

3.3 CGLib代理

由于JDK动态代理是通过被代理类实现的接口来创建代理对象的,因此JDK动态代理只能代理实现了接口的类的对象。如果一个类没有实现任何接口,该如何产生代理对象呢?
CGLb动态代理,是通过创建被代理类的子类来创建代理对象的,因此即使没有实现任何接口的类也可以通过CGLib产生代理对象
CGLib动态代理不能为final创建代理对象

/**  
* 1.添cgLib依赖  
* 2.创建个类,实MethodInterceptor接口,同时实现接口中的intercept方法  
* 3.在类中定义一个Object类型的变量,并提供这个变量的有参构造器,用于传递被代理付象  
* 4.定义getProxy方法创建并返回代理对象(代理对象是通过创建被代理类的子类来创建的)  
*  
* */  
public class CGLibDynamicProxy implements MethodInterceptor {  
  
private Object obj;  
  
public CGLibDynamicProxy(Object obj) {  
this.obj = obj;  
}  
public Object getProxy(){  
Enhancer enhancer=new Enhancer();  
enhancer.setSuperclass(obj.getClass());  
enhancer.setCallback(this);  
Object proxy= enhancer.create();  
return proxy;  
}  
  
@Override  
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {  
begin();  
Object returnValue = method.invoke(obj,objects);//通过反射调用被代理类的方法  
commit();  
return returnValue;  
}  
public void begin(){  
System.out.println(".....开启事务");  
}  
public void commit(){  
System.out.println("....提交事务");  
}  
}

测试

public class TestDynamicProxy2 {  
public static void main(String[] args) {  
// 创建被代理对象  
BookDaoImpl bookDao=new BookDaoImpl();  
StudentDaoImpl studentDao=new StudentDaoImpl();  
// 通过cglib动态代理创建代理对象  
CGLibDynamicProxy cgLibDynamicProxy = new CGLibDynamicProxy(bookDao);  
// 代理对象实际上是被代理对象的子类,因此代理对象可以强转为被代理类型  
BookDaoImpl proxy = (BookDaoImpl) cgLibDynamicProxy.getProxy();  
// 使用对象调用方法,实际上并没有执行这个方法,而是执行了代理类中的intercept方法,将当前调用的方法以及方法中的参数传递到intercept  
proxy.update();  
}  
}

Sping-AOP

一、AOP

1.1 AOP概念

Aspect Oriented Programming面向切面编程,是一种利用“横切"的技术(底层实现就是动态代理),对原有的业务逻辑进行拦截,并且可以在这个拦截的横切面上添加特定的业务逻辑,对原有的业务进行增强。基于动态代理实现在不改变原有业务的情况下对业务逻辑进行增强

图片.png

1.2 Spring-AOP框架部署

1.2.1 创建Maven项目

1.2.2 添加依赖

  • context
  • aspects 图片.png
<dependency>  
<groupId>org.springframework</groupId>  
<artifactId>spring-aspects</artifactId>  
<version>5.2.13.RELEASE</version>  
</dependency>
<dependency>  
<groupId>org.springframework</groupId>  
<artifactId>spring-context</artifactId>  
<version>6.0.9</version>  
</dependency>

1.2.3 创建Spring配置文件

  • 需要引入aop的命名空间
<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
xmlns:aop="http://www.springframework.org/schema/aop"  
xsi:schemaLocation="http://www.springframework.org/schema/aop  
http://www.springframework.org/schema/aop/spring-aop.xsd  
http://www.springframework.org/schema/beans  
http://www.springframework.org/schema/beans/spring-beans.xsd"  
>  
  
</beans>

1.3 AOP配置--基于xml

测试

public class Test3 {  
public static void main(String[] args) {  
// 通过spring容器获取BookDaoImpl的对象,并调用方法  
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");  
BookDaoImpl bookDao = (BookDaoImpl) context.getBean("bookDao");  
bookDao.update();  
}  
}

在DAO方法添加开启事务和提交事务的逻辑

1.3.1 创建一个类

定义要添加的业务逻辑

public class TxManage {  
public void begin(){  
System.out.println("。。。。。。开启事务");  
}  
public void commit(){  
System.out.println("。。。。。。。提交事务");  
}  
}

1.3.2 配置aop

<bean id="bookDao" class="com.xie.dao.BookDaoImpl"></bean>  
<bean id="studentDao" class="com.xie.dao.StudentDaoImpl"></bean>  
  
<bean id="txManager" class="com.xie.utils.TxManage"></bean>  
<aop:config>  
<!-- 声明切入点-->  
<aop:pointcut id="all-way" expression="execution(* com.xie.dao.*.*(..))"/>  
<!-- 声明xmManager为切面类-->  
<aop:aspect ref="txManager">  
<!-- 通知-->  
<aop:before method="begin" pointcut-ref="all-way"></aop:before>  
<aop:after method="commit" pointcut-ref="all-way"></aop:after>  
</aop:aspect>  
</aop:config>  

测试

public class Test3 {  
public static void main(String[] args) {  
// 通过spring容器获取BookDaoImpl的对象,并调用方法  
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");  
BookDaoImpl bookDao = (BookDaoImpl) context.getBean("bookDao");  
StudentDaoImpl studentDao = (StudentDaoImpl) context.getBean("studentDao");  
studentDao.insert();  
    }  
}

AOP开发步骤

  1. 创建切面类,在切面类定义切点方法
  2. 将切面类配置给Spring容器
  3. 声明切入点
  4. 配置AOP的通知策略

1.3.3 切入点声明

1.3.3.1 各种切入点方式
<bean id="bookDao" class="com.xie.dao.BookDaoImpl"></bean>  
<bean id="studentDao" class="com.xie.dao.StudentDaoImpl"></bean>  
<bean id="bookServiceImpl" class="com.xie.Service.BookServiceImpl"></bean>  
  
<bean id="logManager" class="com.xie.utils.LogManager"></bean>  
<bean id="txManager" class="com.xie.utils.TxManage"></bean>  
<aop:config>  
    
<!-- 声明切入点-->  
  
<!-- dao包下所有类中的方法 -->  
<aop:pointcut id="all-way" expression="execution(* com.xie.dao.*.*(..))"/>  
<!-- BookDaoImpl类中所有无返回值的方法 -->  
<aop:pointcut id="insertW" expression="execution(void com.xie.dao.BookDaoImpl.insert())"/>  
<!-- BookDaoImpl类中无参数,无返回值的方法 -->  
<aop:pointcut id="vWay" expression="execution(void com.xie.dao.BookDaoImpl.*())"/>  
<!-- BookDaoImpl类中所有无返回值的方法 -->  
<aop:pointcut id="vWay2" expression="execution(void com.xie.dao.BookDaoImpl.*(..))"/>  
<!-- BookDaoImpl类中所有无参数的方法 -->  
<aop:pointcut id="vWay3" expression="execution(* com.xie.dao.BookDaoImpl.*(..))"/>  
<!-- BookDaoImpl类中所有无返回值的方法 -->  
<aop:pointcut id="vWay4" expression="execution(void com.xie.dao.BookDaoImpl.*(..))"/>  
<!-- dao包下所有类中的insert方法 -->  
<aop:pointcut id="vWay5" expression="execution(* com.xie.dao.*.insert(..))"/>  
    
    
<!-- 通知策略-->  
<!-- 声明xmManager为切面类-->  
<aop:aspect ref="txManager">
<aop:before method="begin" pointcut-ref="all-way"></aop:before>  
<aop:after method="commit" pointcut-ref="all-way"></aop:after>  
</aop:aspect>  
<aop:aspect ref="logManager">  
<aop:before method="printLog" pointcut-ref="all-way"></aop:before>  
</aop:aspect>  
</aop:config>
1.3.3.2 注意事项
  • 如果要使/spring aop面向切面编程,调用切入点方法的对象必颈通/Spring容器获取
  • 如果一个类中的方法被声明为切入点之后,通过Spring容器获取该类对象,实则获取到的是一个代理对象
  • 如果一个类中的方法没有被声明为切入点,通过Spring容器获取的就是这个类真实创建的对象

1.3.4 AOP通知策略

AOP通知策略:就是声明将切面类中的切点方法如何织入到切入点

  • before
  • after
  • after-throwing
  • after-returnning
  • around

图片.png 图片.png

1.3.5 AOP注解配置

  1. @controller: controller控制器层(注入服务)

  2. @service : service服务层(注入dao)

  3. @repository : dao持久层(实现dao访问)

  4. @component: 标注一个类为Spring容器的Bean,(把普通pojo实例化到spring容器中,相当于配置文件中的

  5. @Aspect:作用是把当前类标识为一个切面供容器读取

  6. @Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。

  7. @Around:环绕增强,相当于MethodInterceptor

  8. @AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行

  9. @Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有

  10. @AfterThrowing:异常抛出增强,相当于ThrowsAdvice

  11. @After: final增强,不管是抛出异常或者正常退出都会执行

1.3.5.1 Spring AOP注解配置框架部署
  • 创建Maven工程
  • 添加Spring依赖
    • context
    • aspect
  • Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
xmlns:context="http://www.springframework.org/schema/context"  
xmlns:aop="http://www.springframework.org/schema/aop"  
xsi:schemaLocation=" http://www.springframework.org/schema/context  
http://www.springframework.org/schema/context/spring-context.xsd  
http://www.springframework.org/schema/beans  
http://www.springframework.org/schema/beans/spring-beans.xsd  
http://www.springframework.org/schema/aop  
http://www.springframework.org/schema/aop/spring-aop.xsd">  
<context:annotation-config></context:annotation-config>  
<context:component-scan base-package="com.xie"></context:component-scan>  
  
<!-- 基于注解的AOP代理-->  
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>  
  
</beans>
  • dao
@Component  
public class UserDaoImpl {  
public void insert(){  
System.out.println("user......insert");  
}  
}
  • utils
@Component  
@Aspect  
public class TransactionManager {  
@Pointcut("execution(* com.xie.dao.*.*())")  
public void pc1(){  
  
}  
@Before("pc1()")  
public void begin(){  
System.out.println("开启事务");  
}  
@After("pc1()")  
public void commit(){  
System.out.println("提交事务");  
}  
@Around("pc1()")  
public Object printExecuteTime(ProceedingJoinPoint point) throws Throwable {  
long time1=System.currentTimeMillis();  
Object v = point.proceed();  
long time2=System.currentTimeMillis();  
System.out.println("......time"+(time2-time1));  
return v;  
}  
}
  • 测试
public class Test {  
public static void main(String[] args) {  
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");  
UserDaoImpl userDao = (UserDaoImpl) context.getBean("userDaoImpl");  
userDao.insert();  
}  
}

注意:注解使用虽然方便,但是只能在源码上添加注解,因此我们的自定义类提倡使用注解配置:但如果如果使用到第三方提供的类则需要通过xm配置形式完成配置。

二、Spring-整合MyBatis

Springi两大核心思想:
IoC和AOP

  • loC:控制反转,Spring容器可以完成对象的创建、属性注入、对象管理等工作
  • AOP:面向切面,在不修改原有业务逻辑的情况下,实现原有业务的增强

2.1 Spring可以对MyBatis提供哪些支持

  • SpringIoC

    • 需要创建数据源DataSource
    • 需要创建SqlSessionFactory对象
    • 需要创建SqlSession对象
    • 需要创建DAO对象(Mapper)

    SpringloC可以为MyBatis3完成DataSource、SqlSessionFactory、.SqlSession.以及DAo对象的创建

  • Spring AOP

    AOP支持使用Spring提供的事务管理切面类完成对MyBatis数据库操作中的事务管理

2.2 spring整合Mybatis准备工作

2.2.1 创建Maven工程

2.2.2 部署mybatis框架

  • 导入依赖
<dependencies>  
<dependency>  
<groupId>mysql</groupId>  
<artifactId>mysql-connector-java</artifactId>  
<version>8.0.31</version>  
</dependency>  
<dependency>  
<groupId>org.mybatis</groupId>  
<artifactId>mybatis</artifactId>  
<version>3.5.5</version>  
</dependency>  
</dependencies>

2.2.3 创建Mybatis配置文件

  • 创建配置文件之后无需进行任何配置

2.2.4 部署Spring框架

  • 添加依赖
    • context
    • aspects
  • 创建Spring配置文件:applicatinContext.xml
<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
xmlns:context="http://www.springframework.org/schema/context"  
xmlns:aop="http://www.springframework.org/schema/aop"  
xsi:schemaLocation=" http://www.springframework.org/schema/context  
http://www.springframework.org/schema/context/spring-context.xsd  
http://www.springframework.org/schema/beans  
http://www.springframework.org/schema/beans/spring-beans.xsd  
http://www.springframework.org/schema/aop  
http://www.springframework.org/schema/aop/spring-aop.xsd">  

</beans>

2.2.5 添加Spring整合Mybatis的依赖

  • mybatis-spring
</dependency>  
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->  
<dependency>  
<groupId>org.mybatis</groupId>  
<artifactId>mybatis-spring</artifactId>  
<version>3.0.2</version>  
</dependency>
  • mybatis-jdbc
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->  
<dependency>  
<groupId>org.springframework</groupId>  
<artifactId>spring-jdbc</artifactId>  
<version>6.0.9</version>  
</dependency>

2.2.6 整合Druid连接池

  • 添加druid依赖
<dependency>  
<groupId>com.alibaba</groupId>  
<artifactId>druid</artifactId>  
<version>1.2.16</version>  
</dependency>
  • 创建druid.properties属性文件
druid.driver=com.mysql.cj.jdbc.Driver  
druid.url=jdbc:mysql://localhost:3306/SSM?characterEncoding=utf-8&amp;useSSL=false  
druid.username=tiii  
druid.password=1234  

##连接池参数  
druid.pool.init=1  
druid.pool.minIdle=3  
druid.pool.maxActive=20  
druid.pool.timeout=30000
  • 在applicationContext.xml中配置DruidDataSource
<!-- 加载druid.properties属性文件-->  
<context:property-placeholder location="classpath:druid.properties"></context:property-placeholder>  
<!-- 依赖Spring容器完成数据源DataSource的创建-->  
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">  
<property name="driverClassName" value="${druid.driver}"></property> 
<property name="url" value="${druid.url}"></property>  
<property name="username" value="${druid.username}"></property>  
<property name="password" value="${druid.password}"></property>  
<property name="minIdle" value="${druid.pool.minIdle}"></property>  
<property name="maxActive" value="${druid.pool.maxActive}"></property>  
<property name="maxWait" value="${druid.pool.timeout}"></property>  
</bean>
  • 整合mybatis--创建SqlSessionFactory

依赖Spring容器创建Mybatis的SqlSessionFactory对象

    <!-- 依赖spring容器完成mybatis的sqlSessionFactory对象创建-->  
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" >  
<!-- 配置数据源-->  
<property name="dataSource" ref="dataSource"></property>  
<!-- 配置mapper文件的路径-->  
<property name="mapperLocations" value="classpath:mappers/*Mapper.xml"/>  
<!-- 配置需要定义别名的实体类的包-->  
<property name="typeAliasesPackage" value="com.xie.pojo"/>  
<!-- 可选,配置Mybatis的主配置文件-->  
<property name="configLocation" value="classpath:mybatis-config.xml"/>  
</bean>

2.2.7 整合MyBatis创建Mapper

<!-- 加载dao包中的所有DAO接口,通过sqlSessionFactory获取SqlSession,然后创建所有的DAO接口对象,存储spring容器-->  
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>  
<property name="basePackage" value="com.xie.dao"/>  
</bean>
  • dao
public interface UserDao {  
public List<User> queryUsers();  
}
  • mappers
<mapper namespace="com.xie.dao.UserDao">  
  
<resultMap id="userMap" type="User">  
<id column="user_id" property="userId"/>  
<result column="user_name" property="userName"/>  
<result column="user_pwd" property="userPwd"/>  
<result column="user_realname" property="realName"/>  
<result column="user_img" property="userImg"/>  
</resultMap>  
<select id="queryUsers" resultMap="userMap">  
select user_id,user_name,user_pwd,user_realname,user_img  
from users  
</select>
  • pojo
package com.xie.pojo;  
  
public class User {  
private int userId;  
private String userName;  
private String userPwd;  
private String userImg;  
private String realName;  
public User() {  
  
}  
  
public User(int userId, String userName, String userPwd, String userImg, String realName) {  
this.userId = userId;  
this.userName = userName;  
this.userPwd = userPwd;  
this.userImg = userImg;  
this.realName = realName;  
}  
  
@Override  
public String toString() {  
return "User{" +  
"userId=" + userId +  
", userName='" + userName + '\'' +  
", userPwd='" + userPwd + '\'' +  
", userImg='" + userImg + '\'' +  
", realName='" + realName + '\'' +  
'}';  
}  
  
public int getUserId() {  
return userId;  
}  
  
public String getRealName() {  
return realName;  
}  
  
public void setRealName(String realName) {  
this.realName = realName;  
}  
  
public void setUserId(int userId) {  
this.userId = userId;  
}  
  
public String getUserName() {  
return userName;  
}  
  
public void setUserName(String userName) {  
this.userName = userName;  
}  
  
public String getUserPwd() {  
return userPwd;  
}  
  
public void setUserPwd(String userPwd) {  
this.userPwd = userPwd;  
}  
  
public String getUserImg() {  
return userImg;  
}  
  
public void setUserImg(String userImg) {  
this.userImg = userImg;  
}  
}
  • service
public interface UserService {  
public List<User> lisetUsers();  
}
@Service  
public class UserServiceImpl implements UserService{  
@Resource  
private UserDao userDao;  
@Override  
public List<User> lisetUsers() {  
return userDao.queryUsers();  
}  
}

测试:获取DAO

public class UserTest {  
@Test  
public void queryUsers(){  
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");  
UserDao userDao =(UserDao) context.getBean("userDao");  
System.out.println(userDao);  
}  
}

2.3 整合AOP配置(spring,MyBatis)

使用Spring提供的事务管理切面类完成DAO中增删改操作的事务 一、配置单个方法:

<tx:method name="transfer" isolation="DEFAULT"
propagation="REQUIRED" timeout="-1" read-only="false"/>

二、tx:method的name属性也支持通配符的写法

1、让所有方法支持事务:

<tx:method name="*" propagation="REQUIRED"/>

2、让查询方法只读(假设查询方法都是标准的find开头,比如findAll、findOne)

<tx:method name="find*" read-only="true"/>

三、优先级:精准匹配>部分匹配>完全模糊匹配

让查询方法只读,其他方法则支持读写:

<tx:method name="*" read-only="false"/>

<tx:method name="find*" read-only="true"/>

2.3.1 可以设置isolation下面这些枚举值:

  • DEFAULT:采用数据库默认隔离级别
  • SERIALIZABLE:最严格的级别,事务串行执行,资源消耗最大;
  • REPEATABLE_READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
  • READ_COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。
  • READ_UNCOMMITTED:保证了读取过程中不会读取到非法数据。隔离级别在于处理多事务的并发问题。

2.3.2 关于propagation属性的7个传播行为

  • REQUIRED:指定当前方法必需在事务环境中运行,如果当前有事务环境就加入当前正在执行的事务环境,如果当前没有事务,就新建一个事务。这是默认值。
  • SUPPORTS:指定当前方法加入当前事务环境,如果当前没有事务,就以非事务方式执行。
  • MANDATORY:指定当前方法必须加入当前事务环境,如果当前没有事务,就抛出异常。
  • REQUIRES_NEW:指定当前方法总是会为自己发起一个新的事务,如果发现当前方法已运行在一个事务中,则原有事务被挂起,我自己创建一个属于自己的事务,直我自己这个方法commit结束,原先的事务才会恢复执行。
  • NOT_SUPPORTED:指定当前方法以非事务方式执行操作,如果当前存在事务,就把当前事务挂起,等我以非事务的状态运行完,再继续原来的事务。
  • NEVER:指定当前方法绝对不能在事务范围内执行,如果方法在某个事务范围内执行,容器就抛异常,只有没关联到事务,才正常执行。
  • NESTED:指定当前方法执行时,如果已经有一个事务存在,则运行在这个嵌套的事务中.如果当前环境没有运行的事务 ,就新建一个事务,并与父事务相互独立,这个事务拥有多个可以回滚的保证点。就是指我自己内部事务回滚不会对外部事务造成影响,只对DataSourceTransactionManager事务管理器起效。

图片.png

2.3.3 配置spring

<!-- 1.将Spring提供的事务管理配置给Spring容器-->  
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
<property name="dataSource" ref="dataSource"/>  
</bean>  
<!-- 2.通过spring-jdbc提供的tx标签,声明事务管理策略 -->  
<tx:advice id="txAdvice" transaction-manager="transactionManager">  
<tx:attributes>  
<!--  
isolation 设置事务隔离级别:READ_UNCOMMITTED,READ_COMMITTED,REPEATABLE_READ,SERIALIZABLE  
  
-->  
<tx:method name="insert*" isolation="REPEATABLE_READ" propagation="REQUIRED"/>  
<tx:method name="delete*" isolation="REPEATABLE_READ" propagation="REQUIRED"/>  
<tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED"/>  
<tx:method name="query*" isolation="REPEATABLE_READ" propagation="SUPPORTS"/>  
</tx:attributes>  
</tx:advice>  
<!-- 3.将事务管理策略以AOP配置,应用于DAO操作方法--> 
<aop:config>  
<aop:pointcut id="crud" expression="execution(* com.xie.service.*.*(..))"/>  
<aop:advisor advice-ref="txAdvice" pointcut-ref="crud"/>  
</aop:config>

2.4 AOP事务管理配置-注解配置

  • 将Spring提供的事务管理配置给Spring容器
<!-- 1.将Spring提供的事务管理配置给Spring容器-->  
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
<property name="dataSource" ref="dataSource"/>  
</bean>  
<!-- 声明使用注解完成事务配置-->  
<tx:annotation-driven transaction-manager="transactionManager"/>
  • 在需要Spring进行事务管理的方法上添加@Transactional注解
@Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.SUPPORTS)  
@Override  
public List<User> lisetUsers() {  
return userDao.queryUsers();  
}