Spring
一、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 面向接口编程
面向接口编程
解决方案:在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/
二、入门开发步骤
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 创建配置文件
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 相关问题
-
之前创建对象,无参数构造执行?
答:执行了 -
不用new方式,还可以如何创建对象:
- 反射
-
创建对象放到哪里?
-
如何使用返回创建的对象
三、日志
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("###执行调用成功了...");
}
Spring-loC
一、依赖注入
1.1 基于XML管理bean
1.1.1 搭建子模块spring6-ioc-xml
- 搭建模块
搭建方式如:spring-frist - 引入配置文件
引入spring-first模块配置文件:bean.xml\log4j.xml - 添加依赖
- 创建类
public class User {
private String name;
private Integer age;
public void run(){
System.out.println("run.....");
}
}
1.1.2 实验一:获取bean
- 方式一:根据id获取
由于id属性指定了bean的唯一标识,所以根据bean标签的id属性可以精确获取到一个组件对象。上个实验中我们使用的就是这种方式。 - 方式二:根据类型获取
- 根据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容器中一共配置了两个,根据类型获取会抛出异常
- 扩展知识
如果组件类实现了接口,根据接只类型可以获取bean吗?
可以,前提是bean唯一
如果一个接口有多个实现类,这些实现类都配置了bean,根据接口类型可以获取bean吗?
不行,因为bean不唯一
1.2 setter注入
- 类有属性,创建对象过程中,向属性设置值
- 第一种方式:基于set方法完成
- 第二种方式:基于构造器完成
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 测试类
- 先生产tostring方法
- 新建测试类
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 创建类,定义属性,生成有参数构造方法
代码如上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实体
如果带有这种符号的,会出错,应该转义处理
<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="<>"></property>
</bean>
1.4.4 CDATA节
<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="<>"></property>-->
<property name="others">
<value><![CDATA[a < b]]></value>
</property>
</bean>
a<b就是others的值
1.5 特殊类型属性注入
1.5.1 对象类型属性注入
//部门类
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 引入外部属性文件
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的作用域
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生命周期
- 具体的生命周期过程
- bean对象创建 (调用无参构造器)
- 给bean对象设置属性
- bean的后置处理器 (初始化之前)
- bean对象初始化(需在配置bean时指定初始化方法)
- bean的后置处理器 (初始化之后)
- bean对象就绪可以使用
- bean对象销毁 (需在配置bean时指定销毁方法)
- IOC容器关闭
- 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>
1.5.10 FactoryBean
- 创建类
public class MyFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}
- 配置
<bean id="myFactoryBean" class="com.xie.spring6IocXml.factorybean.MyFactoryBean"></bean>
- 测试
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();
}
}
注意
也可以根据名称装配,如果是名称,则id要和类里的名字一样。
autowire="byName
1.6 基于注解管理Bean
从Java5开始,Java增加了对注解(Annotation)的支持,它是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在源代码中嵌入补充
信息。
Spring从2.5版本开始提供了对注解技术的全面支持,我们可以使用注解来实现自动装配,简化Spring的xml配置。
Spring通过注解实现自动装配的步骤如下:
- 引入依赖
- 开启组件扫描
- 使用注解定义Bean
- 依赖注入
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>
//@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 属性注入
- bean对象创建
- 定义相关属性,在属性上面添加注解
<!-- 开启组件扫描-->
<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注解联合
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 根据名称
1.6.4.2 根据属性名
1.6.4.3 根据类型
总结:
@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框架最核心的部分。
- 搭建子模块
- 准备测试需要的bean
- 添加依赖
三、代理设计模式
代理设计模式的优点:将通用性的工作都交给代理对象完成,被代理对象只需专注自己的核心业务
3.1 静态代理
静态代理:代理类只能够为特定的类生产代理对象,不能代理任意类(即只有实现的GeneralDao的类才可以被代理)
使用代理的好处
- 被代理类中只用关注核心业务的实现,将通用的管理型逻辑(事务管理、日志管理)和业务逻辑分离
- 将通用的代码放在代理类中实现,提供了代码的复用性
- 通过在代理类添加业务逻辑,实现对原有业务逻辑的扩展(增强)
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面向切面编程,是一种利用“横切"的技术(底层实现就是动态代理),对原有的业务逻辑进行拦截,并且可以在这个拦截的横切面上添加特定的业务逻辑,对原有的业务进行增强。基于动态代理实现在不改变原有业务的情况下对业务逻辑进行增强
1.2 Spring-AOP框架部署
1.2.1 创建Maven项目
1.2.2 添加依赖
- context
- aspects
<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开发步骤
- 创建切面类,在切面类定义切点方法
- 将切面类配置给Spring容器
- 声明切入点
- 配置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
1.3.5 AOP注解配置
-
@controller: controller控制器层(注入服务)
-
@service : service服务层(注入dao)
-
@repository : dao持久层(实现dao访问)
-
@component: 标注一个类为Spring容器的Bean,(把普通pojo实例化到spring容器中,相当于配置文件中的
-
@Aspect:作用是把当前类标识为一个切面供容器读取
-
@Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
-
@Around:环绕增强,相当于MethodInterceptor
-
@AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行
-
@Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有
-
@AfterThrowing:异常抛出增强,相当于ThrowsAdvice
-
@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&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事务管理器起效。
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();
}