赞
踩
整体架构 = 表示层 + 业务层 + 持久层
最原始的 = JSP + Servlet + Javabean Java JDBC(实现数据库访问)
SSH = Structs + Spring +Hibernate
SSM = SpringMVC + Spring + Mybatis
MVC设计 = M模型、V视图、C控制器
Spring Boot相关项目设计框架:
SSM = SpringMVC + Spring Boot + Mybatis
J2EE是什么
J2EE = Java2平台企业版(Java 2 Platform Enterprise Edition),是Java技术企业级应用开发的工业标准;
提供基于组件的方式来设计、开发、组装和部署企业应用。
J2EE平台组成
Java2 平台有三个版本:J2SE(Java2平台标准版)、J2EE(Java2平台企业版)、J2ME(Java2平台Micro版)。
J2EE平台由 一套服务(Services)、应用程序接口(APIs)和协议构成。
J2EE应用程序的组件
J2EE应用程序是由组件构成的。
J2EE组件是一个封装了功能的软件单元,能够与相关的类和文件一起组成J2EE应用程序。
J2EE分层设计模型
使用分层的设计模型,分别是:客户层、Web层、业务层、企业信息系统层(EIS)。
客户层 = 运行在客户端上的组件;
Web层 = 运行在J2EE服务器上的组件;
业务层 = 运行在J2EE服务器上的组件;
企业信息系统层(EIS) = 运行在EIS服务器上的软件系统;
客户层:
Web浏览器——Web客户端,也就是我们日常生活中使用的网页,它通过标准的格式来显示从服务器端传递过来的页面,服务器传递给浏览器的时候格式为HTML、XML格式;
Applet(小应用程序)——嵌套在浏览器中的一种轻量级客户端,当浏览器不能充分表现数据或者应用界面的时候,才会使用它,是web页面的替代手段,可以使用J2SE开发Applet。是小程序吗?
应用程序客户端——相对于Applet而言,是较重量级的客户端,可以使用大量的服务和API,运行在客户机上,能提供强大而灵活易用的用户界面,如使用Swing或AWT创建图形化的用户界面。类似于qq的客户端?可以访问业务层的Bean 或者 打开HTTP连接与Web层上的Servlet之间的通信。
J2EE应用层服务器
由组件和容器组成;
组件 = 能够显示在屏幕上的各种图形化的实体;
容器 = Component子类的任何一个对象;
容器可以添加其他组件形成复合组件。
两大容器 = Web容器 + EJB容器(表示层 + 业务逻辑层)
Web容器 = 管理所有的Servlet等Web组件的运行;
EJB容器 = 支持EJB组件的事务处理和生命周期管理,以及Bean的查找和其他服务,支持J2EE多层架构的基础结构。
两大组件 = Web组件 + EJB组件
Web组件 = 与基于Web的客户端进行交互。有三类Web组件:Servlet、JSP、JavaBean。
EJB组件 = 包含三种不同类型的EJB:会话Bean、消息驱动Bean、实体Bean。
存在的问题
层与层之间的耦合性严重,接口与具体的实现紧密耦合在一起;如果后期需要修改具体的实现则需要在修改对应的代码。
解决:程序中不需要手动new对象,通过第三方实现对象创建。
通用的事务功能、日志功能耦合在业务代码中;
解决:通过第三方提供需要的Bean对象的代理实现。
好处 | 说明 |
---|---|
轻量 | 轻量的,基本的版本大约2MB。 |
控制反转IoC | 通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。 |
面向切面的编程(AOP) | 支持面向切面的编程,并且把应用业务逻辑和系统服务分开。 |
容器 | 包含并管理应用中对象的生命周期和配置。 |
MVC框架 | Web框架是个精心设计的框架,是 Web框架的一个很好的替代品。 |
事务管理 | 提供一个持续的事务管理接口,可以扩展到 上至本地事务 下至全局事务(JTA)。 |
异常处理 | 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常 |
概念
框架 = 一些类 和 接口的集合,通过类、接口协调来完成一系列的程序实现。
概念
是为了解决企业级应用开发的复杂性而创建的,简化开发;
是分层的轻量级JavaEE框架;
以IOC和AOP为核心,使用基本的JavaBean来完成之前只能用EJB完成的工作。
EJB = Enterprise JavaBean,它是企业Java Beans。
理解 = 把编写的软件中要执行指定的任务的类,不放在客户端软件上,而是给他打包放到服务器上,并依靠RMI进行通信。
两个核心
IOC 和 AOP;
IOC = 控制反转,把创建对象的过程交给Spring进行管理;
AOP = 面向切面编程,不修改源代码进行功能增强。
// interface
package com.beanioc;
public interface UserService {
}
// interface implements
package com.Impl;
import com.beanioc.UserService;
public class UserServiceImpl implements UserService {
}
配置base.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">
<!-- 配置UserService对象-->
<bean id="userService" class="com.Impl.UserServiceImpl"></bean>
</beans>
测试代码;
package com.beanioc.test; import com.beanioc.UserService; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; public class BeanFactoryTest { public static void main(String[] args){ // 创建工厂对象 DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); // 创建一个读取器(xml文件) XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); // 读取配置文件给工厂 reader.loadBeanDefinitions("base.xml"); // 根据id获取bean实例对象 UserService userService = (UserService) beanFactory.getBean("userService"); System.out.println(userService); } }
概念:
将Bean1设置Bean2到自己内部的过程也交付给BeanFactory,需要通过配置文件实现。
实现过程:
要求:需要在UserServiceImpl中设置UserDao对象;
定义UserDao接口及具体实现UserDaoImpl;
修改UserServiceImpl实现类,加入setUserDao(UserDao userDao)方法接收UserDao对象;
修改Base.xml文件的配置文件,在UserDaoImpl的中嵌入 配置注入;视频中 我理解是在UserServiceImpl中的配置。
修改测试代码,获取UserService时,setUserDao方法就被框架调用执行了。
具体代码:
图示:
配置文件:
<?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"> xhj:核心内容是property属性。 <!-- 配置UserService对象--> <bean id="userService" class="com.Impl.UserServiceImpl"> <!-- name表示的是userService中setxxx方法中xxx首字母小写的名字--> <!-- ref 表示在容器中找Bean对象xxx--> <!-- 整体的含义是:从容器中找id名字为userDao的Bean对象设置到容器中id名字为WieuserService的Bean对象中名字为userDao的方法--> <property name="userDao" ref = "userDao"></property> </bean> <!-- 配置UserDao对象--> <bean id ="userDao" class="dao.impl.UserDaoImpl"></bean> </beans>
新建的UserDao接口与其实现类UserDaoImpl
接口:
package dao;
public interface UserDao {
}
实现类:
package dao.impl;
import dao.UserDao;
public class UserDaoImpl implements UserDao {
}
UserServiceImpl修改:
package com.Impl;
import com.beanioc.UserService;
import dao.UserDao;
public class UserServiceImpl implements UserService {
// BeanFactory 去调用该方法,从容器中获取userDao设置到此处
public void setUserDao(UserDao userDao){
System.out.println("BeanFactory 去调用该方法,获取userDao设置到此处"+userDao);
}
}
BeanFactory是核心接口,具体的实现是DafaultListableBeanFactory。
概念:
ApplicationContext被称为Springd的容器,内部封装了BeanFactory;
使用其作为进行开发时。xml配置文件通常名字较为ApplicationContext.xml;
测试代码:
package com.beanioc.test;
import com.beanioc.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ApplicationContextTest {
public static void main(String[] args) {
// 通过Application接口的实现类 ClassPathXmlApplicationContext创建对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("base.xml");
// 通过ApplicationContext对象的getBean方法来实现 对象的创建;
UserService userService = (UserService) applicationContext.getBean("userService");
System.out.println(userService);
}
}
ApplicationContext内部维护的BeanFactory的具体实现也是DefaultListableBeanFactory;
选中ApplicationContext 键盘输入ctrl + h 可以找到ApplicationContext的继承体系;
当只导入spring时,继承体系如下,只有三个可以使用的实现类;
只在Spring基础环境下三个实现类功能描述:
BeanFactory早期的接口,被称为Spring的Bean工厂;ApplicationContext是Spring的容器;
ApplicationContext除了有BeanFactory基础功能外,还有别功能(监听和国际化);BeanFactory的API更偏向底层、ApplicationContext的API主要是对那些底层API的封装;
BeanFactory创建Bean是在BeanFactory调用getBean时候创建的;而ApplicationContext是在加载xml文件时候就创建了;
BeanFactory是延迟加载,而ApplicationContext是立即加载。
两者关系:
BeanFactory中存在Bean的创建和功能;ApplicationContext继承了BeanFactory又维护BeanFactory的引用;同时存在继承与融合关系。
理解:
具体解释:
Bean实例化的方式:构造方法、工厂方式;
构造方法实例化:
1 = 创建对象通过new的形式创建
类名 对象名 = new 类名();
2 = xml配置
<bean id="bean的标识" class="类的全限定类名">
<!-- 如果是有参构造方法,则需要传入参数-->
<constructor-arg name="有参构造方法参数名字" value="具体的值"></constructor-arg>
</bean>
其中如果涉及有参构造方法,则需要在对应Bean配置文件中 添加constructor-arg标签
Bean工厂方式的实例化:三种方式
静态工厂方式、实例工厂方式、实现FactoryBean规范延迟Bean实例化。
静态工厂方式实例化Bean:通过fatory-method标签实现;
1 = 对象的创建采用类.静态方法的形式
类名 对象名 = 类名.静态方法();
2 = xml配置
<bean class="全限定类名" factory-method="静态方法名">
<!-- 如果是有参构造方法,则需要传入参数-->
<constructor-arg name="方法中的参数名" value="具体的值"></constructor-arg>
</bean>
该方式的作用在于:
原始创建Bean在之前或之后想执行别的操作无法实现,但是通过工厂方式可以实现。→直接在定义的工厂类MyFactory类中的静态类中书写即可;
第三方jar包中的Bean需要交由Spring容器管理。→ 通过静态工厂方式实现。
实例化工厂方式实例化Bean:
1 = 通过对象.普通方法得到对象
类1 对象1 = new 类1()
类2 对象2 = 对象1.方法()
2 = xml配置
<bean id="bean的标识" class="全限定类名-工厂对象" >
<!-- 如果是有参构造方法,则需要传入参数-->
<constructor-arg name="方法中的参数名" value="具体的值"></constructor-arg>
</bean>
<bean id="bean的标识" factory-bean="工厂对象的id" factory-method="工厂对象使用的方法名">
<!-- 如果是有参构造方法,则需要传入参数-->
<constructor-arg name="方法中的参数名" value="具体的值"></constructor-arg>
</bean>
该方式创建的工厂类中包含普通方法,需要先创建工厂对象,然后在调用普通类;
配置:先配置工厂对象;在配置方法;
该方式的作用在于:有些第三方jar包创建Bean是某些对象的非静态方法产生的,需要将这些交给Spring容器管理,在需要采用实例化工厂的形式去配置。
静态工厂方式和实例工厂方式创建Bean对象,其中的方法是含有参数的,如何配置?
就在对应的bean标签中,添加constructor-arg标签添加name 和 value来对对应的name=参数名,value=具体值 得到。
实现FactoryBean规范延迟Bean实例化
通过Bean的set注入:
普通变量注入;
对象引用注入;
通过构造Bean的方法注入:
普通变量注入;
对象引用注入;
注入集合数据类型:如果注入的是List、set,则在property便签下的子标签需要使用list或set。
如果List中的参数是Bean实例,配置方式有两种:
通过ref注入 <bean id="userService2" class="com.Impl.UserServiceImpl2"> <property name="stringList"> <list> <ref bean = "userDao1"></ref> <ref bean = "userDao2"></ref> <ref bean = "userDao3"></ref> </list> </property> </bean> <bean id="userDao1" class="dao.UserDaoImpl"></bean> <bean id="userDao2" class="dao.UserDaoImpl"></bean> <bean id="userDao3" class="dao.UserDaoImpl"></bean> 通过bean注入: <bean id="userService2" class="com.Impl.UserServiceImpl2"> <property name="stringList"> <list> <bean class= "dao.UserDaoImpl"></bean> <bean class= "dao.UserDaoImpl"></bean> <bean class= "dao.UserDaoImpl"></bean> </list> </property> </bean>
总结:
Bean注入有三种内容,分别是基本数据类型、对象引用、集合对象。
基本数据类型 <bean id="" class=""> -set方法引入 <property name="" vlue=""> </property> -构造方法引入 <constructor-arg name="" vlue=""> </constructor-arg> </bean> 对象引用 <bean id="" class=""> -set方法引入 <property name="" ref=""><property> -构造方法引入 <constructor-arg name="" ref=""></constructor-arg> </bean> 集合对象List <bean id="xxx" class=""> <property name=""> <list> <value></value> <ref></ref> </list> </property> </bean> 集合对象Set引入 <bean id="xxx" class=""> <property name=""> <set> <value></value> <ref></ref> </set> </property> </bean> 集合对象Map引入 <bean id="xxx" class=""> <property name=""> <map> -基础数据引入 <entry key="" value=""></entry> - 对象引用引入 <entry key-ref="" value-ref=""></entry> </map> </property> </bean> 集合对象properties引入 键值对 <bean id="xxx" class=""> <property name=""> <props> <prop key = "">value</prop> </props> </property> </bean>
可以实现将对象引用自动装配到对应的bean中;
<bean id="" class="" autowire="">
autowire参数两个:
byName = 通过setXxx与id=“xxx0”(或name=“xxx”)匹配,一致则自动装配
byType = 通过Bean类型从容器中匹配,但容易由于多个相同Bean类型,导致报错。
也即是多个bean的class都是相同的全限定类名。
标签分为:默认标签、自定义标签;
默认标签 = 不需要导入额外命名空间;
自定义标签 = 需要导入额外命名空间,通过前缀引用标签,例如context:property-placeholder/
spring的默认命名空间存在的标签有: beans、bean、import、alias
beans标签:
作用:作为根标签、嵌套在根标签中用于区分环境,通过profile属性切换开发环境。
根标签:
<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">
</beans>
区分环境:
<beans>
-测试环境
<beans profile="test"></beans>
-开发环境
<beans profile="dev"></beans>
</beans>
指定环境:
-代码指定
System.setProperty("spring.profiles.active","环境名")
-命令行指定
Dspring.profiles.active=环境名
import
导入其他配置文件;每个模块有需要有各自的配置文件,为了方便applicationContext导入配置文件,故将所有配置文件整合成一个文件。
<import resource="classpath:xxx"></import>
alias
给bean起别名。
<bean id="xxx" name="xxx1" class="com.beanioc.UserService"></bean>
<alias alias="xxx2" name="xxx"></alias>
则id为xxx的bean,有两个别名,即xxx1 xxx2
aliasmap中维护bean的别名。
spring自定义标签:
视频中描述在pom.xml中写入dependency被称为写入坐标,也即是jar包。
三种方式:
其中Class type需要通过反射得到class对象,即类.class 或者 对象.getClass 或者 Class.forName(“全限定类名”)
概念
将第三方jar包中的bean让spring管理 = spring配置非自定义的bean;
配置需要考虑:bean创建的形式是构造方式或工厂方式、是否需要注入依赖。
过程
1. 在pom.xml中引入druid依赖,有druid就得有mysql-connector-java
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
</dependenies>
3. 配置Druid数据源交由Spring管理;
就是在pom.xml文件中配置依赖,然后找到界面右侧maven刷新就可以得到jar包。
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- class全限定类名采用的是DruidDataSource类右键复制的-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/book"></property>
<property name="username" value="root"></property>
<property name="password" value="*****"></property>
</bean>
4. 测试代码
public class ApplicationContextTest {
public static void main(String[] args) {
// 通过Application接口的实现类 ClassPathXmlApplicationContext创建对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("base.xml");
Object dataSource = applicationContext.getBean("druidDataSource");
System.out.println(dataSource);
}
}
注意
shift+shift 实现查看某个类的源码;
数据源要想使用需要四个基本信息:数据源驱动setDriverClassName、连接setUrl、用户名称setUsername、用户密码setPassword。
IDEA快捷键
视频中代码出现问题:
概念
Connection产生是采用静态方法getconection创建的;
不交由spring管理,自己书写
- 加载驱动
Class.forName("com.mysql.jdbc.Driver");
- 创建connection对象
Connection connection = DeriverManager.getConnection("url","username","password");
分析:
两者都采用类.今天方法的形式,所以可以采用静态工厂方式构造;
交由spring管理,配置文件
<!-- 配置connection-->
<bean id="clazz" class="java.lang.Class" factory-method="forName">
<!-- 因为 public static Class<?> forName(String className) 的参数名是className,所以这里填写name="className"-->
<constructor-arg name="className" value="com.mysql.jdbc.Driver"/>
</bean>
<bean id ="connection " name ="connection" class="java.sql.DriverManager" factory-method="getConnection">
<constructor-arg name="url" value="jdbc:mysql://127.0.0.1:3306/books"/>
<constructor-arg name="user" value="root"/>
<constructor-arg name="password" value="123456"/>
</bean>
视频与实际操作不符合的部分
xml文档配置:
<bean id="simpleDateFormat" class="java.text.SimpleDateFormat">
<constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"></constructor-arg>
</bean>
<bean id="date" factory-bean="simpleDateFormat" factory-method="parse">
<constructor-arg name="source" value="2023-5-21 12:00:00"></constructor-arg>
</bean>
测试文档:
public class ApplicationContextTest {
public static void main(String[] args) {
// 通过Application接口的实现类 ClassPathXmlApplicationContext创建对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("base.xml");
Object date = applicationContext.getBean("date");
System.out.println(date);
// 原始日期类型创建形式。
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
simpleDateFormat.parse("2023-5-21 12:00:00")
}
}
理解:
在pom.xml中配置依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
不使用spring集中管理的代码文件:
base.xml配置文件
<bean id="in" class="org.apache.ibatis.io.Resources" factory-method="getResourceAsStream">
<constructor-arg name="resource" value="mybatis-config.xml"></constructor-arg>
</bean>
<bean id ="builder" class ="org.apache.ibatis.session.SqlSessionFactoryBuilder"></bean>
<bean id= "sqlSessionFactory" factory-bean="builder" factory-method="build">
<constructor-arg name="inputStream" ref="in"></constructor-arg>
</bean>
理解:
问题
原因在于mybatis-config.xml文件中多配置了mapper,所以出现报错。
概念
整合第三方命名空间的方式:1 不需要第三方命名空间,比如Mybatis;2 需要第三方命名空间,比如Dubbo;
原始使用Mybatis查询
1 创建数据表的对象,类中需要有属性表示数据表中的元素。
2 定义UserMapper接口,里面有查询表格数据方法findAll,返回列表,列表元素是User对象。
3 配置UserMapper.xml文件,从参考连接中选择这个已映射的 SQL 语句 复制粘贴。修改参数为下图所示。
该文件需要存储在resources路径下,且层级路径与UserMapper一样的。
4 在mybatis-config.xml文件中配置UserMapper.xml文件。
5 测试代码书写:
package com.beanioc.test; import Mapper.UserMapper; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import pojo.User; import java.io.IOException; import java.io.InputStream; import java.util.List; public class MybatisTest { public static void main(String[] args) throws Exception { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = builder.build(in); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> all = mapper.findAll(); for (User user : all) { System.out.println(user); } } }
所需步骤
pom.xml文件相关坐标导入
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
Mapper涉及文件
Mapper接口 package Mapper; import pojo.User; import java.util.List; public interface UserMapper { List<User> findAll(); } 返回对象User类 package pojo; public class User { @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", nation='" + nation + '\'' + '}'; } private Integer id; private String name; private String nation; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNation() { return nation; } public void setNation(String nation) { this.nation = nation; } }
Mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="Mapper.UserMapper">
<select id="findAll" resultType="pojo.User">
select * from author
</select>
</mapper>
<!-- 研究已配置-->
配置SqlSessionFactoryBean和MapperScannerConfigurer
<!-- 4/23 Spring整合Mybatis--> <!--数据源对象配置--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <!-- class全限定类名采用的是DruidDataSource类右键复制的--> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://127.0.0.1:3306/books"></property> <property name="username" value="root"></property> <property name="password" value="*****"></property> </bean> <!-- 配置SqlSessionFactoryBean,将sqlSessionFactory存储到spring容器中--> <bean class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- SqlSessionFactory底层是提供sqlSession的,而其在底层操作的时候需要connection,也就是数据源,需要一个数据源对象--> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置MapperScannerConfigurer,扫描指定的包,产生Mapper对象,存储在Spring容器中--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="Mapper"></property> </bean> <!-- 配置UserService对象--> <bean id="userService11" class="com.Impl.UserServiceImpl11"> <property name="userMapper" ref="userMapper"></property> </bean>
测试代码
// 4/23 Spring整合Mybatis 58
// 通过Application接口的实现类 ClassPathXmlApplicationContext创建对象
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("base.xml");
// 通过ApplicationContext对象的getBean方法来实现 对象的创建;
UserService1 userService11 = (UserService1) applicationContext.getBean("userService11");
userService11.show();
Spring整合MyBatis框架原理
需求:
数据源的信息配置往往采用键值对的形式,也就是properties文件实现;使用context命名空间的对应的标签。
过程:
1 在xml配置文件中配置命名空间;
2 案例书写数据源配置,数据源信息创建键值对文件;
3 xml配置数据源配置信息;
4 测试 其余代码设置均采用Spring整合MyBatis文件形式;
命名空间配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
">
键值对文件
jdbc.Driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/books
jdbc.username=root
jdbc.password=*****
xml配置信息
<!-- 加载properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- class全限定类名采用的是DruidDataSource类右键复制的-->
<property name="driverClassName" value="${jdbc.Driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
测试代码:
// 4/23 Spring整合Mybatis 58
// 通过Application接口的实现类 ClassPathXmlApplicationContext创建对象
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("base.xml");
// 通过ApplicationContext对象的getBean方法来实现 对象的创建;
UserService1 userService11 = (UserService1) applicationContext.getBean("userService11");
userService11.show();
Spring整合自定义框架context原理
63没有看,目前理解困难。
案例
haohaoannotation.xsd文件
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.itheima.com/haohao"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.itheima.com/haohao">
<xsd:element name="annotation-driven"></xsd:element>
</xsd:schema>
META-INF文件夹中的handlers和schemas文件
handlers文件内容
http\://www.itheima.com/haohao=com.handlers.HaohaoNamespaceHandler
#对应的是 命名空间字符串对应的命名空间处理器类对应的全限定类名
schema文件
http\://www.itheima.com/haohao/haohaoannotation.xsd=./com/itheima/config/haohao/haohaoannotation.xsd
# 配置schema映射地址字符串与当前文件的真实映射
命名空间处理器
package com.handlers;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class HaohaoNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// 初始化 一个命名空间下有多个标签,会在init方法中为每一个标签注册一个标签解析器
this.registerBeanDefinitionParser("annotation-driven", new HaohaoBeanDefinitionParser());
}
}
标签解析器
package com.handlers; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.w3c.dom.Element; public class HaohaoBeanDefinitionParser implements BeanDefinitionParser { @Override public BeanDefinition parse(Element element, ParserContext parserContext) { // 注入一个BeanPostProcessor BeanDefinition beanDefinition = new RootBeanDefinition(); beanDefinition.setBeanClassName("com.beanioc.HaohaoBeanPostProcessor"); parserContext.getRegistry().registerBeanDefinition("haohaoBeanPostProcessor", beanDefinition); return beanDefinition; } }
标签解析器功能所需Bean后处理器对象
package com.beanioc;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class HaohaoBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("HaohaoBeanPostProcessor执行...");
return bean;
}
}
概念:
IOC = inversion of control 控制反转。也就是Bean创建权的反转。
把创建对象的过程交给Spring进行管理,借助IOC容器实可以降低对象之间的耦合度;
作用
在于解耦,可以解除对象之间的耦合,让对象和对象之间完全没有联系,这样完成或修改一个对象时不需要考虑其他对象。
出现原因:
Java是面向对象的语言,应用程序需要通过一个个对象之间的关联和作用来完成功能。
对象之间紧密的咬合形成的系统会具有较高的耦合度,存在一个对象出现问题,整个系统无法工作的情况。
加入IOC容器,会使得对象之间的耦合度降低,修改一个对象不会对其他对象造成影响。
原理理解:
没有IOC容器的时候,对象A依赖对象B,A在运行到某一时刻的时候会去创建B的对象,这样A具有主动权,它控制了对象B的创建;
引入IOC容器以后对象A和对象B之间就没有直接的联系,当对象A运行的时候由IOC容器创建B对象在适当的时候注入到对象A中,这样对象B的控制权就由A对象转移到了IOC容器。
概念:
强调Bean之间的关系。
场景:
现在有两个Bean,其中Bean1中需要Bean2才可以达到想要的效果;
原始做法,通过第三方创建出Bean1和Bean2,然后通过代码设置把Bean2设置给Bean1;
现在做法,将Bean2设置给Bean1动作也交给第三方,但第三方创建Bean1时内部就创建了Bean2,当程序调用Bean1的时候内部就有Bean2的引用。
关系 | 控制反转 | 依赖注入 |
---|---|---|
名称 | IOC ( Inversion of Control) | DI (Dependency injection) |
概念 | 将创建对象的权利交给Spring | 原始:对象A执行过程中需要创建对象B 现在:IOC容器将创建好的对象B注入到对象A中 |
描述角度不同 | 调用者角度: 原始:对象A 需要对象B 直接通过new创建就好 现在:对象B的创建由IOC容器完成 控制反转 = 对于依赖对象的控制权 由调用者 转移到 IOC容器上 | Spring容器角度: spring容器负责将需要的对象注入到调用者的成员变量。 |
备注2022/8/17
注入依赖的本质 = 创建应用程序对象之间的协作关系的行为 = 装配(wiring)。
例子:创建的userServiceImpl类中,有UserDao类的对象
控制反转 =
将创建UserDao对象的权利 从userServiceImpl对象手中 交给 IOC容器,强调的是一种思想。
依赖注入 =
IOC容器将 userDao对象,注入到 userServiceImpl对象,强调的是一个过程和实现。
Spring通过依赖注入(DI)实现IOC(控制反转)。
常用的注入方式主要有:构造方法注入、setter注入、基于注解的注入。
注意
构造方法注入、setter注入都是通过Java的反射机制实现的。
构造方法注入
定义:
类中必须提供构造方法,属性的set方法不需要;
实现:
在Spring-config.xml文件中配置;标签是constructor-arg
前提:
package com.bean;
public class Car{
private String brand;
private float price;
public Car(){}
public Car(String brand,float price){
this.brand = brand;
this.price = price;
}
}
①通过构造方法形参名字注入;
<bean id="car" class="com.bean.Car"> <!-- Car类的全限定类名 -->
<constructor-arg name="brand" value = "奇瑞"></constructor-arg>
<constructor-arg name="price" value = "10000"></constructor-arg>
</bean>
②通过构造方法形参的索引位置注入。
<bean id="car" class="com.bean.Car">
<constructor-arg name="0" value = "奇瑞"></constructor-arg>
<constructor-arg name="1" value = "10000"></constructor-arg>
</bean>
setter注入
定义:
通过set方法完成注入,类中必须给属性设定set方法;
实现:
在Spring-config.xml文件中配置;标签是property
前提:
package com.bean;
public class Car{
private String brand;
private float price;
public Car(){}
public void setBrand(String brand){this.brand = brand;}
}
配置文件:
<bean id="car" class="com.bean.Car">
<property name="brand" value = "奇瑞"></property>
</bean>
前两者的测试文件都是:
public static void main(String[] args){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("Spring-config.xml");
Person person = (Person)applicationContext.getBean("car");
}
基于注释的注入
定义:
通过@Autowired注释方式,可以实现自动装配,只要在对应的属性上面添加该注解即可 = 在类创建的时候,在对应的属性前面加@Autowired。
但@Autowired注解是按照byte类型来注入的。
实现:
类文件:
package com.bean;
public class Person{
String name;//姓名
@Autowired
private Car car;
public String getName(){return name;}
public void setName(String name){this.name = name;}
public void getCarInfo(){输出;}
}
Spring-config.xml文件
<context:component-scan base-package = "com.bean"></context:component-scan>
<!-- 表示在com.bean包下扫描Person类上面的@Autowired注解,将Spring容器中的car汽车对象注入到Person类的car属性中-->
个人理解Spring-config.xml文件应该是:不确定对不对?
<bean id="car" class="com.bean.Car"> <!-- Car类的全限定类名 -->
<constructor-arg name="brand" value = "奇瑞"></constructor-arg>
<constructor-arg name="price" value = "10000"></constructor-arg>
</bean>
<bean id="person" class="com.bean.Person"> <!-- Person类的全限定类名 -->
<property name="brand" value = "奇瑞"></property> <!-- 通过setter注入 -->
<context:component-scan base-package = "com.bean"></context:component-scan>
<!-- 表示在com.bean包下扫描Person类上面的@Autowired注解,将Spring容器中的car汽车对象注入到Person类的car属性中-->
</bean>
Bean实例化 = 从xml文件中的bean标签→进入容器中→再通过getBean得到Bean的过程。
图解:
断点执行:
bean标签封装:BeanDefinitionMap
SingletonObjects:
概念:
Bean的创建过程是标签封装成BeanDefinition对象,存储在BeanDefinitionMap中,然后Spring遍历Map集合通过反射创建Bean对象存储在SingletonObjects中。
Spring后处理器 表示可以介入到Bean对象创建过程中,包括动态注册BeanDefiniton、修改BeanDefinitionMap、以及动态修改Bean。
实现方式:
BeanFactoryPostProcessor:Bean工厂后处理器,在BeanDefinitonMap填充之后,Bean实例化之前 执行;
BeanPostProcessor:Bean后处理器,Bean实例化之后,填充到单例池SingletonObjects之前 执行。
Bean实例化流程
BeanFactoryPostProcessor使用
1 创建类实现BeanFactoryPostProcessor接口,并重写其中方法
package com.beanioc;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
System.out.println("BeanDedifintion创建之后,Bean实例化对象之前调用");
}
}
2 将bean配置到容器中
<!-- BeanFactoryPostProcessor入门-->
<bean class="com.beanioc.MyBeanFactoryPostProcessor"></bean>
作用1:修改BeanDefinition
package com.beanioc; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { System.out.println("BeanDedifintion创建之后,Bean实例化对象之前调用"); // 修改某个BeanDefinition BeanDefinition userService = beanFactory.getBeanDefinition("userService"); userService.setBeanClassName("dao.impl.UserDaoImpl"); } }
前后结果为:
作用2:动态注册Bean 封装BeanDefinition
1 创建新的接口PersonDao 以及其实现类
// 接口
package dao;
public interface PersonDao {
}
//实现类
package dao.impl;
import dao.PersonDao;
public class personDaoImpl implements PersonDao {
}
2 在实现BeanFactoryPostProcessor的实现类中的postProcessorBeanFactory中书写 package com.beanioc; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { System.out.println("BeanDedifintion创建之后,Bean实例化对象之前调用"); // 修改某个BeanDefinition BeanDefinition userService = beanFactory.getBeanDefinition("userService"); userService.setBeanClassName("dao.impl.UserDaoImpl"); // 动态注册Bean BeanDefinition 扔到BeanDefinitionMap中 // 注册BeanDefinition 它是一个接口 需要通过它的实现来得到 setBeanClassName方法 BeanDefinition beanDefinition = new RootBeanDefinition(); beanDefinition.setBeanClassName("dao.impl.personDaoImpl"); // beanFactory本质是:DefaultListableBeanFactory // beanFactory强制转换为DefaultListableFactory DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory; defaultListableBeanFactory.registerBeanDefinition("PersonDao", beanDefinition); } }
BeanDefinitionRegistryPostProcessor
它是BeanFactoryPostProcessor的子接口;内部的方法postProcessBeanDefinitionRegistry参数是BeanDefinitionRegistry,该对象有 registerBeanDefinition方法实现注册Bean,得到BeanDefinition。
1= BeanDefinitionRegistryPostProcessor接口的实现类 package com.beanioc; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; public class MyBeanDefinitionPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { System.out.println("MyBeanDefinitionRegistryPostProcessor的postProcessorBeanDefinitionRegistry方法"); // 向容器当中注册BeanDefinition BeanDefinition beanDefinition = new RootBeanDefinition(); beanDefinition.setBeanClassName("dao.impl.personDaoImpl"); beanDefinitionRegistry.registerBeanDefinition("personDao", beanDefinition); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { System.out.println("MyBeanDefinitionRegistryPostProcessor的postProcessBeanFactory方法"); } }
2 = 配置文件
<!-- BeanDefinitionRegistryPostProcessor-->
<bean class="com.beanioc.MyBeanDefinitionPostProcessor"></bean>
加入BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor的Bean创建过程
案例:Spring的BeanFactoryPostProcessor扩展点完成自定义注解扫描
要求
具体实现过程:
1 创建类配置自定义标签@Component
2 配置自定义标签Component注解文件
3 添加扫描文件BaseClassScanUtils
4 设置实现BeanDefinitionRegistryPostProcessor接口的实现类
1 otherBean类
ackage com.otherBean;
import com.anno.MyComponent;
@MyComponent("otherBean")
// 字符串中的内容是字符串bean的名字
public class otherBean {
}
2 Component注解文件
package com.anno;
// 配置MyComponent注解文件
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//确定在哪个上面使用,ElementType.TYPE在类上使用
@Target(ElementType.TYPE)
//注解的存活范围 RetentionPolicy.RUNTIME 运行期间可见
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent {
String value();
}
3 扫描文件BaseClassScanUtils
package com.utils; import com.anno.MyComponent; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.util.ClassUtils; import java.lang.annotation.Annotation; import java.util.HashMap; import java.util.List; import java.util.Map; public class BaseClassScanUtils { //设置资源规则 private static final String RESOURCE_PATTERN = "/**/*.class"; public static Map<String, Class> scanMyComponentAnnotation(String basePackage) { //创建容器存储使用了指定注解的Bean字节码对象 Map<String, Class> annotationClassMap = new HashMap<String, Class>(); //spring工具类,可以获取指定路径下的全部类 ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); try { String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(basePackage) + RESOURCE_PATTERN; Resource[] resources = resourcePatternResolver.getResources(pattern); //MetadataReader 的工厂类 MetadataReaderFactory refractory = new CachingMetadataReaderFactory(resourcePatternResolver); for (Resource resource : resources) { //用于读取类信息 MetadataReader reader = refractory.getMetadataReader(resource); //扫描到的class String classname = reader.getClassMetadata().getClassName(); Class<?> clazz = Class.forName(classname); //判断是否属于指定的注解类型 if(clazz.isAnnotationPresent(MyComponent.class)){ //获得注解对象 MyComponent annotation = clazz.getAnnotation(MyComponent.class); //获得属value属性值 String beanName = annotation.value(); //判断是否为"" if(beanName!=null&&!beanName.equals("")){ //存储到Map中去 annotationClassMap.put(beanName,clazz); continue; } //如果没有为"",那就把当前类的类名作为beanName annotationClassMap.put(clazz.getSimpleName(),clazz); } } } catch (Exception exception) { } return annotationClassMap; } public static void main(String[] args) { Map<String, Class> stringClassMap = scanMyComponentAnnotation("com.itheima"); System.out.println(stringClassMap); } }
4 实现BeanDefinitionRegistryPostProcessor接口实现类
package com.beanioc; import com.utils.BaseClassScanUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.RootBeanDefinition; import java.util.Map; public class MyComponentBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { // 通过扫描工具去扫描指定包以及其子包下的所有子类,收集使用@MyComponent的注解的类 // 其中string表示@注解括号中的名字,Class是获取的类的全限定名 Map<String, Class> myComponentAnnotation = BaseClassScanUtils.scanMyComponentAnnotation("com.otherBean"); // 遍历Map,组装BeanDefinition进行注册 Lamda表达式 myComponentAnnotation.forEach((beanName,clazz)->{ // 获取beanClassName 也就是类的全限定类名 String beanClassName = clazz.getName(); // 创建BeanDefinition BeanDefinition beanDefinition = new RootBeanDefinition(); // 注册BeanDefinition beanDefinition.setBeanClassName(beanClassName); // 写入BeanDefinitionMap中 beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition); }); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { } }
概念:
BeanPostProcessor是对Bean对象的操作,不是对Bean定义的操作。
是Bean被实例化之后,存储SingletonObjects单例池之后前,中间会经过Bean的初始化过程。例如属性的填充、初始方法init的执行等。
步骤:
创建类,实现BeanPostProcessor接口
package com.beanioc; import dao.impl.UserDaoImpl; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class MyBeanPostProcessor implements BeanPostProcessor { // BeanPostProcessor接口中的方法是静态方法 不需要必须重写; // 通过BeanPostProcessor接口实现类 来实现bean对象的设置 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { // 判断是否是UserDaoImpl对象,是的话 设置其属性Name的值 if(bean instanceof UserDaoImpl){ UserDaoImpl userDao = (UserDaoImpl) bean; userDao.setName("123"); } System.out.println(beanName + ":postProcessBeforeInitialization"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println(beanName+":postProcessBeforeInitialization"); return bean; } }
xml文件配置
<!-- Bean后处理器 BeanPostProcessor配置-->
<bean class="com.beanioc.MyBeanPostProcessor"></bean>
执行顺序
Bean实例化、初始化init、继承InitializingBean接口的afterPropertiesSet方法、以及postProcessBeforeInitialization和postProcessAfterInitialization的执行顺序
结果:
多个文件之间关系:
代码:
Dao接口的实现类
package dao.impl; import dao.UserDao; import org.springframework.beans.factory.InitializingBean; public class UserDaoImpl implements UserDao, InitializingBean { // 设置一个参数 private String name; // 如果需要值,则一般情况会需要在base中通过property配置;但也可以通过BeanPostProcessor实现类 来实现属性的输入‘ public void setName(String name) { this.name = name; } public UserDaoImpl() { System.out.println("Dao对象的创建"); } // init初始化方法 public void init(){ System.out.println("init方法执行"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("afterPropertiesSet方法执行"); } }
xml配置文件:
<!-- 配置UserDao对象-->
<bean id ="userDao" class="dao.impl.UserDaoImpl" init-method="init"></bean>
BeanPostProcessor实现类:
package com.beanioc; import dao.impl.UserDaoImpl; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class MyBeanPostProcessor implements BeanPostProcessor { // BeanPostProcessor接口中的方法是静态方法 不需要必须重写; // 通过BeanPostProcessor接口实现类 来实现bean对象的设置 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if(bean instanceof UserDaoImpl){ UserDaoImpl userDao = (UserDaoImpl) bean; userDao.setName("123"); } System.out.println(beanName + ":postProcessBeforeInitialization"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println(beanName+":postProcessBeforeInitialization"); return bean; } }
要求:
Bean的方法执行之前和之后控制台打印当前时间。
文件之间关系:
代码
UserDao接口及实现类
接口: package dao; public interface UserDao { void show(); } 实现类: import dao.UserDao; import org.springframework.beans.factory.InitializingBean; public class UserDaoImpl implements UserDao { @Override public void show() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("show........"); } }
继承BeanPostProcessor接口的实现类
package com.beanioc; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Date; // 创建类实现BeanPostProcessor接口 public class DateBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // 使用动态代理模式对目标bean进行增强,返回proxy对象,进而存储到单例池中SingletonObjects中 Object beanproxy = Proxy.newProxyInstance( bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), // 这里是InvocationHandler 对象 (proxy, method, args) -> { // 1 输出开始时间 System.out.println("方法" + method.getName() + "-开始时间" + new Date()); // 2 执行方法 Object result = method.invoke(bean, args); // 3 结束时间 System.out.println("方法" + method.getName() + "-结束时间" + new Date()); return result; } ); return beanproxy; } }
配置文件:
<!-- 配置UserDao对象-->
<bean id ="userDao" class="dao.impl.UserDaoImpl" ></bean>
<!-- Bean后处理器 BeanPostProcessor Date日期 前后处理案例-->
<bean class="com.beanioc.DateBeanPostProcessor"></bean>
概念
Bean通过反射实例化对象之后,形成一个完整的Bean对象,最终存储在单例池中的过程。
每一个Bean标签注入的属性都不会被存储在propertyValues中。
普通属性:String、int或者存储基本数据类型,采用set方法的反射;
单向对象引用属性:通过容器调用getBean获取 再通过set方法反射设置;
双向对象引用属性:采用循环引用实现;
对于单向对象引用属性来说,关键点在于看容器bean、属性bean以及属性注入三者的时机。
先创建容器bean、在创建属性bean、再执行属性注入;
双向对象引用属性:采用三级缓存解决该内容
三级缓存存储的是:完整的Bean实例和半成品Bean实例。
过程图
传统java bean
传统的Java应用中,bean的生命周期很简单,使用Java关键字 new 进行Bean 的实例化,然后该Bean 就能够使用了。一旦bean不再被使用,则由Java自动进行垃圾回收。
概念:是什么
循环引用-三级缓存对应存储内容理解
具体过程:
1 在一级缓存中找,看是否有Service对象,没有就实例化对象,进入2
2 将生成实例化对象Service的工厂存储到三级缓存中 进入3
3 完成Service对象的初始化过程,发现需要属性填充userDao 进入4
4 在三个缓存集合中找是否存在userDao对象 发现没有 进入5
5 实例化对象userDao,进入6
6 将生成实例化对象userDao的工厂存储到三级缓存中,进入7
7 初始化对象Dao 属性填充userService,进入8
8 查看三个缓存集合中是否有userService对象,发现有,进入9
9 将三级缓存中生成userService对象的工厂直接赋值,再将其userService存储到二级缓存中
循环引用图解
三级缓存源码解释
解释
没看懂。
概念
当我们在创建对象的时候 想要获取对象的属性,比如beanName、applicationContext、beanFactory、servletContext对象等,都需要实现对应的接口通过其内部方法实现对象的返回。
具体
注意
在实现ServletContextAware接口的时候,如果实现类代码爆红,则需要在pom.xml文件中进行配置。
javax.servlet-api和javax.servlet.jsp-api
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
</dependency>
结果:
定义:
是一个由Spring IOC容器实例化、组装和管理的对象;
并且构成了应用程序的主干;
理解:
Bean是一个对象,可以有一个或多个;
Bean由Spring IOC容器管理;
Bean是应用程序的主干,也就是程序由Bean组成;
规律:
凡是子类及带有方法或属性的类都要加上注册Bean到Spring IOC的注解 = Bean注解中的注册Bean。
@Bean 用在方法上,告诉Spring容器,可以从下面这个方法拿到一个Bean = Spring容器会自动完成对@Bean对象的实例化。
创建Bean
即把想要实例化的对象转化成一个Bean,放在IOC容器中,等到需要的时候,配合@Autowired 或 @Resource使用。
即@Component、@Service、@Controller、@Repository。
注解 | 说明 |
---|---|
@Component | 通用的注解,将当前类对象存入IOC容器中 |
@Controller | 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面 |
@ Service | 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层 |
@Repository | 对应持久层即 Dao 层,主要用于数据库相关操作 |
修改创建对象的注入数据的注释
@Autowired = 可以自动按照类型注入数据到bean中,按照byte类型注入;
IOC容器中有唯一的bean对象与要被注入的变量类型匹配,则正常执行;
IOC容器中没有对应的bean对象类型和要被注入的变量类型匹配,则会抛出异常;
IOC容器中多个bean对象和要被注入的变量类型匹配,则根据id匹配,也一样,则根据变量的名称匹配,还一样,抛出异常;
@Resource = 可以指定注入的bean id,单独使用,不需要和@Autowired一起使用,按照byName类型注入;
@Qualifier = 在@Autowired的基础之上,添加value属性值,指定注入bean id(需要和@Autowired一起)
@Value = 用于注入基本数据类型和String数据类型;
用于改变创建对象作用范围的注解
@Scope = 指定Bean的作用范围;
生命周期相关的注解
@PreDestroy = 销毁方法的注解
@PostConstruct = 初始化方法的注解
新注解
@Configuration = 用于指定当前类是一个 Spring 配置类,当创建容器时 会从该类上加载注解;
@ComponentScan = 用于指定 spring 在初始化容器时要扫描的包;
@Bean = 该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器;
@PropertySource = 用于加载 .properties 文件中的配置 ;
@Import = 为两个没有关系的配置类建立关系链接;
@Component 注解作用于类;@Bean注解作用于方法。
@Component通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中;@Bean 注解表示在我们标有该注解的方法中定义产生这个 bean;
@Bean 注解比 @Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册 bean
概念
Bean的作用域可以通过scope属性来指定。
具体值
singleton:默认值。当IOC容器一创建就会创建Bean的实例,而且是单例的,每次得到同一个。
prototype:原型的。当IOC容器创建时不会立即实例化该Bean,每次调用getBean方法时再实例化该Bean,而且每次调用都会返回一个新的实例。
request:每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境。
session:同一个HTTP Session 共享一个Bean,不同的 HTTP Session 使用不同的Bean。该作用域仅适用于WebApplicationContext环境。
概念
单例Bean 存在线程安全问题。
单例Bean的线程安全问题:
无状态Bean = 线程中的操作不会对Bean的成员执行查询以外的操作,故此单例Bean是线程安全的;
有状态Bean = 即有实例变量的对象,可以保存数据,是非线程安全的。
Spring默认的Bean是单例的,也没有进行封装处理。
现象
当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
解决
备注2022/8/18
Spring中Bean被定义为两种模式:prototype(多例)和singleton(单例)。
通过属性值 scope设置。
prototype(多例):对这个Bean的每次请求都会创建一个新的Bean实例,类似于new。多个变量指向不同的对象。
singleton(单例):只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例。多个变量指向同一个对象。
概念:
ApplicationContext,是Spring中的核心接口和容器,允许容器通过应用程序上下文环境创建、获取、管理Bean。
在构建容器的时候,创建对象采用的策略是立即加载的方式,即只要一读取完配置文件就立即创建配置文件中配置的对象。
BeanFactory采用的是延迟加载的方式,什么时候根据id获取对象了,什么时候才真正地创建对象。
实现类 | 说明 |
---|---|
ClassPath XmlApplicationContext | 可以加载类路径下的配置文件,要求配置文件必须在类路径之下。 |
FileSystem XmlApplicationContext | 可以加载磁盘中任意路径下的配置文件,要求具有访问权限。 |
AnnotationConfig ApplicationContext | 用于读取注解创建容器。 |
概念
AOP(Aspect-Oriented Programming)面向切面编程。功能的纵向抽取,实现方式主要是Proxy代理。
是指将那些与业务无关,却被多个业务模块所共同调用逻辑或责任(例如事务处理、日志管理、权限控制等),将其封装起来,便于减少系统的重复代码,降低模块间的耦合度,提升系统的可维护性。
出现原因
对OOP(Object-Oriented Programming)面向对象编程的补充和完善。
组成
使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。
业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。
横切关注点的特点是:它们经常发生在核心关注点的多处,而各处都基本相似。比如:权限认证、日志、事务处理。
作用
面向切面编程(AOP)提供另外一种角度来思考程序结构,通过这种方式弥补了面向对象编程(OOP)的不足。
重点在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来,降低业务逻辑的耦合性,提高程序的可重用性和可维护性。
两种方式:静态代理(AspectJ AOP)、动态代理(Spring AOP)(基于接口的JDK代理 、 基于继承的CGLIB动态代理)。
具体使用哪种方式生成由 AopProxyFactory 根据 AdvisedSupport 对象的配置来决定。默认的策略是如果目标类是接口,则使用 JDK 动态代理技术,否则使用 CGlib 来生成代理。
AspectJ AOP
使用静态代理方式,也称为编译时增强;
AOP框架会在编译阶段生成AOP代理类,并将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
Spring AOP动态代理形式
AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
JDK动态代理
通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口InvocationHandler。
JDK动态代理的核心是InvocationHandler接口和Proxy类。
Spring默认的动态代理方式就是JDK动态代理。
如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。
CGLIB(Code Generation Library)
概念:是一个代码生成的类库,可以在运行时动态的生成某个类的子类。
CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理。
JDK动态代理
CGLIB代理
AOP出现原因:缓解OOP面向对象编程存在的不足。
于是将业务处理分成核心关注点和横切关注点;
核心关注点 = 业务的主要处理内容;
横切关注点 = 与业务处理无关,但是被多个业务模块所调用的逻辑或方法,将其封装起来,降低模块间的耦合度,提升了代码的复用性和提高系统的可维护性。
被代理类Target→连接点join point→切入点 point cut→通知Advice→切面Aspect→织入weaning→代理Proxy
各基本概念之间的关系
概念
Advice = 通知,增强或增加的功能,即切面的什么、干什么;
具体通知类型
<aop:before method = "目标方法名" pointcut-ref="切入点名称">
<aop:pointcut id="名字" expression = "execution()">
<!-- 在环绕通知中必须手动的调用目标方法,否则目标方法不会执行。
这个手动调用是通过ProceedingJoinPoint来实现的,
可以在环绕通知中接收一个此类型的形参,spring容器会自动将该对象传入,这个参数必须处在环绕通知的第一个形参位置。-->
<aop:around method = "目标方法名" pointcut-ref="切入点名字">
<aop:after-returning method = "目标方法名" pointcut-ref="切入点名字">
<aop:after-throwing method = "目标方法名" pointcut-ref="切入点名字">
配置传入的JoinPoint获取目标对象、目标方法相关信息 处于参数列表的第一位,其余参数:throwing 异常通知接收到目标方法抛出的异常对象
<aop:after method = "目标方法名" pointcut-ref="切入点名字">
概念
事务是逻辑上的一组操作,要么都执行,要么都不执行;
目的
事务会把数据库从一种一致状态转换为另一种一致状态;
本质
Spring的事务是数据库对事务的支持,事务能否生效 取决于 数据库引擎是否支持事务;
例如:数据库MySQL默认支持事务的是innodb引擎,如果把数据库引擎变成myisam,则程序也不能再支持事务;
特性
原子性(Atomicity)(事务是一组逻辑上的操作,要么全部执行,要么全部不执行);
一致性(consistency)(事务执行前后,数据库的完整性没有被破坏);
隔离性(isolation)(并发访问,数据库允许多个并发事务同时对其数据进行读写和修改,可以防止多个事务并发执行时交叉执行导致数据不一致的情况);
持久性(durability)(一旦事务提交,对数据的修改将是永久的)。
概念:
按照给定的事务规则来执行提交或者回滚操作;
Spring并不直接管理事务,而是提供了多种事务管理器;通过该接口,为各个平台JDBC、hibernate、JPA提供了对应的事务管理器;
接口:
PlatformTransactionManager:平台事务管理器,Spring事务策略的核心;
TransactionDefinition:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则);
TransactionStatus:事务运行状态。
三者之间的关系:
PlatformTransactionManager = 事务上层的管理者;
其余两个接口是事务的描述。
PlatformTransactionManager 会根据 TransactionDefinition 的定义(事务超时时间、隔离级别、传播行为等)来进行事务管理;TransactionStatus提供了一些方法来获取事务相应的状态(是否新事务、是否可以回滚等)。
TransactionDefinition接口中定义了五个表示隔离级别的常量:
隔离级别 | 说明 |
---|---|
TransactionDefinition.lSOLATION_DEFAULT | 使用后端数据库默认的隔离级别, MySQL默认采用的REPEATABLE_READ隔离级别(可重复读), Oracle 默认采用的READ_COMMITTED隔离级别(读已提交) |
TransactionDefinition.ISOLATION_READ_UNCOMMITTED | 最低的隔离级别 允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读; |
TransactionDefinition.ISOLATION_READ_COMMITTED | 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生; |
TransactionDefinition.lSOLATION_REPEATABLE_READ | 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生; |
TransactionDefinition.ISOLATION_SERIALIZABLE | 最高的隔离级别,完全服从ACID的隔离级别。 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,该级别可以防止脏读、不可重复读以及幻读。 但是这将严重影响程序的性能,通常情况下也不会用到该级别。 |
作用
为了解决业务层方法之间互相调用的事务问题。
当一个事务方法(当前方法B)被另一个事务方法(调用者A)调用时,该方法B对另一个事务方法A的态度
在TransactionDefinition定义的传播行为常量
支持当前事务 | 说明 |
---|---|
TransactionDefinition.Propagation_required | 当前事务方法B 必须在事务中运行,事务可以是调用者A的 或者 自己新开启的事务 |
调用者是否有事务 | 调用者A有事务——当前方法B加入调用者A事务中 调用者A没有事务——当前方法B自己新开启一个事务运行 |
调用者调用完成,抛出业务异常,如何回滚 | 调用者A抛出异常——方法B和调用者A一起回滚 调用者A抛出异常且已执行的SQL不回滚——当前方法B不回滚 |
当前方法发生异常,调用者捕获当前方法的业务异常,如何回滚 | 调用者A提交事务抛出异常——调用者A和当前方法B在同一事务中,一起回滚 (不允许存在) 调用者以非事务形式运行——当前方法B回滚 |
当前方法出现异常,调用者不捕获当前方法异常,怎么回滚 | 调用者抛出当前方法异常——当前方法和调用者在同一个事务中一起回滚 调用者非事务形式运行,抛出当前方法异常,已执行的SQL不回滚——当前方法的事务回滚 |
TransactionDefinition.Propagation_supports | 当前方法B 不必在事务中运行 |
调用者是否有事务 | 调用者A有事务——当前方法B加入调用者A事务中 调用者A没有事务——当前方法B以非事务的形式运行 |
调用者调用完成,抛出业务异常,如何回滚 | 调用者A抛出异常——当前方法B和调用者A一起回滚 调用者A抛出异常且已执行的SQL不回滚——当前方法B已执行的SQL不回滚 |
当前方法发生异常,调用者捕获当前方法的业务异常,如何回滚 | 调用者提交事务抛出异常——调用者和方法B一起回滚 调用者A以非事务的形式运行——当前方法已执行的SQL不回滚 |
当前方法出现异常,调用者不捕获当前方法异常,怎么回滚 | 调用者抛出当前方法异常——调用者和当前方法一起回滚 调用者抛出当前方法异常且已执行的SQL不回滚——当前方法已执行的SQL不回滚 |
TransactionDefinition.Propagation_mandatory | 当前事务方法B 必须在调用者事务中运行 |
调用者是否有是否 | 调用者A有事务——将事务方法B加入调用者A事务中 调用者A没有事务——当前方法B抛出异常 |
调用者调用完成,抛出业务异常,如何回滚 | 调用者抛出异常——当前方法B和调用者A一起回滚 调用者调用当前方法抛出异常(不允许该场景发生) |
当前方法发生异常,调用者捕获当前方法的业务异常,如何回滚 | 调用者提交事务发生异常——调用者和当前方法B一起回滚(不允许存在) 调用者调用当前方法时抛出异常——调用者已运行的SQL不回滚 |
当前方法出现异常,调用者不捕获当前方法异常,怎么回滚 | 调用者抛出当前方法异常——调用者和当前方法一起回滚 调用者调用方法抛出异常且已执行的SQL不回滚(不允许该场景产生) |
非事务形式 | 非事务形式就是设置了自动提交,一个方法中有多个操作,每个操作都会在不同的事务中完成,不会保证他们的原子性。 |
不支持当前事务 | 说明 |
---|---|
TransactionDefinition.Propagation_required_new | 当前方法B必须在新事务中运行 |
调用者否是有事务 | 调用者A有事务需要挂起——当前方法B自己新开一个事务运行 调用者A没有事务——当前方法B自己开启一个新事务运行 |
调用者调用完成,抛出业务异常,如何回滚 | 调用者A抛出异常且回滚——当前方法B不回滚 调用者A抛出异常且已执行的SQL不回滚——当前B不回滚 |
当前方法发生异常,调用者捕获当前方法的业务异常,如何回滚 | 调用者A以事务的形式正常执行——当前方法B抛出异常且回滚 调用者A非事务形式运行——当前方法B抛出异常且回滚 |
当前方法出现异常,调用者不捕获当前方法异常,怎么回滚 | 调用者抛出异常且回滚——当前方法抛出异常且回滚 调用者抛出异常且已执行的SQL不回滚——当前方法抛出异常且回滚 |
TransactionDefinition.Propagation_not_supported | 当前方法B 不支持在事务中运行 |
调用者是否有事务 | 调用者A有事务方法执行期间挂起——当前方法B以非事务的形式运行 调用者A没有事务——当前方法B以非事务形式运行 |
调用者调用完成,抛出业务异常,如何回滚 | 调用者A抛出异常且回滚——当前方法B已执行的SQL不回滚 调用者A抛出异常且已执行SQL不回滚——当前方法B已执行的SQL不回滚 |
当前方法发生异常,调用者捕获当前方法的业务异常,如何回滚 | 调用者A正常执行——当前方法B抛出异常且已执行的SQL不回滚 调用者A正常执行——当前方法B抛出异常且已执行的SQL不回滚 |
当前方法出现异常,调用者不捕获当前方法异常,怎么回滚 | 调用者抛出异常且回滚——当前方法抛出异常且已执行的SQL不回滚 调用者抛出异常且已执行的SQL不回滚 当前方法抛出异常且已执行的SQL不回滚 |
TransactionDefinition.Propagation_never | 调用者必须以非事务的形式运行 |
调用者是否有事务 | 调用者A有事务——抛出异常 调用者A没有事务——当前方法B以非事务的形式运行 |
调用者调用完成,抛出业务异常,如何回滚 | 调用者调用当前方法抛出异常(不允许存在该场景) 调用者A抛出异常且已执行的SQL不回滚——当前方法B已执行的SQL不回滚 |
当前方法发生异常,调用者捕获当前方法的业务异常,如何回滚 | 调用者A抛出异常 该场景不存在 调用者A以非事务的形式正常执行——当前方法B已执行的SQL不回滚 |
当前方法出现异常,调用者不捕获当前方法异常,怎么回滚 | 调用者调用当前方法抛出异常(不允许存在该场景) 调用者抛出异常且已执行的SQL不回滚——当前方法抛出异常且已执行的SQL不回滚 |
其他情况 | 说明 |
---|---|
TransactionDefinition.Propagation_nested | 当前方法B 必须在自己的事务中运行 |
调用者是否有事务 | 调用者A有事务——当前方法B以“嵌套事务”的形式加入到调用者A的事务中 调用者A没有事务——当前方法B自己新开启一个事务运行 |
调用者调用完成,抛出业务异常,如何回滚 | 调用者A抛出异常——调用者和当前方法一起回滚 调用者抛出异常且已执行的SQL语句不回滚——当前方法B不回滚 |
当前方法发生异常,调用者捕获当前方法的业务异常,如何回滚 | 调用者A正常运行——当前方法B以嵌套事务回滚 调用者以非事务形式运行——当前方法的事务回滚 |
当前方法出现异常,调用者不捕获当前方法异常,怎么回滚 | 调用者抛出异常且回滚——当前方法嵌套事务回滚 调用者抛出当前方法异常且已执行的SQL不回滚 |
设计模式 | 说明 |
---|---|
工厂设计模式 | Spring使用工厂模式通过BeanFactory、ApplicationContext 创建bean对象 |
代理设计模式 | Spring AOP功能的实现 |
单例设计模式: | Spring中的Bean默认都是单例 |
模板方法模式 | Spring中jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类 |
观察者模式 | Spring 事件驱动模型就是观察者模式很经典的一个应用 |
适配器模式 | Spring AOP的增强或通知(Advice)的AdvisorAdapter、SpringMVC 中也是用到了HandlerAdapter适配Controller |
装饰器模式 | Spring中含有Wrapper和含有Decorator的类 |
策略模式 | 资源访问Resource接口 |
备注2022/8/19
待看
观察者模式:
Spring 中的 Event 和 Listener。前者spring 事件:ApplicationEvent,该抽象类继承了EventObject 类,JDK 建议所有的事件都应该继承自 EventObject。后者spring 事件监听器:ApplicationListener,该接口继承了 EventListener 接口,JDK 建议所有的事件监听器都应该继承EventListener 。
概念:
多个Bean之间相互依赖,形成了一个闭环。
默认的单例模式Bean中,属性相互引用的场景。
循环依赖是Spring容器注入时候出现的问题。
解决方式:
Spring中单例Bean的三级缓存;
第一级缓存:Map<String, Obiect>(也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象;
第二级缓存: Map<String, Obiect> earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整);
第三级缓存: Map<String, ObiectFactory<?>> singletonFactories,存放可以生成Bean的工厂;
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。