spring项目初始化
博主采用父子maven工程,父maven统一管理版本,多个子工程讲解项目。
要点:
- spring6要求最低java版本为java17
- spring6需要按要求创建xml文件
- maven要求最低3.6
实操:
- 创建maven项目,jdk版本选择17,删除父工程src
- 父工程设置spring6和Junit测试api版本控制
引入版本控制,子工程未引入依赖前会爆红
引入spring-context等于引入spring相关的所有基础依赖,可以打开依赖面板查看
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spring.context.version>6.0.2</spring.context.version>
<junit.api.version>5.3.1</junit.api.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.context.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.api.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
- 创建子模块引入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
</dependencies>
Spring入门
我们尝试把对象的创建过程交给ioc完成
- 写个类,包含一个方法
package com.atguigu;
//一个add类,包含一个addSome方法
public class add {
void addSome(){
System.out.println("com.atguigu.add......");
}
public static void main(String[] args) {
add add = new add();
add.addSome();
}
}
- 在resource下创建一个spring的xml文件,bean.xml
- 学习第一个标签 bean标签 2条属性
- id 给bean一个唯一标识,让spring认识他
- class 路径,指向刚写的类
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="classAdd" class="com.atguigu.add"></bean>
</beans>
好的,我们完成了配置工作,我们测试一下。
package com.atguigu;
import org.junit.jupiter.api.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class testAddClass {
@Test
public void testAddClass1(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
add objAdd = (add)context.getBean("classAdd");
System.out.println(objAdd);
}
}
-
new一个ClassPathXMLApplicationContext对象,填入我们的xml文件名
- 为什么是bean.xml而不是bean标签?
- 因为一个xml文件可以有多个bean
- 为什么填入xml文件名?
- 因为我们的xml文件是直接在resource文件夹下的,所以直接填写文件名就行了,spring会默认去找该文件夹
解析:
spring到底做了什么?
- 通过指定的xml文件找到bean
- 根据bean找到类,并把它加载到IOC容器中
- 利用反射创建无参的对象
- 返回给我们
我们改下代码(加上无参构造器),看看spring到底有没有用无参构造器
package com.atguigu;
public class add {
public add(){
System.out.println("add无参构造器被调用.....");
}
void addSome(){
System.out.println("com.atguigu.add......");
}
public static void main(String[] args) {
add add = new add();
add.addSome();
}
}
测试后发现调用了,我们手写一下Spring使用反射的步骤
@Test
public void testAddClass2() throws Exception{
//抓取类的字节码文件
Class<?> aClass = Class.forName("com.atguigu.add");
//使用新方法获得公开的构造器,创建实例
add addObj = (add)aClass.getDeclaredConstructor().newInstance();
//调方法
addObj.addSome();
}
spring把对象存到了哪里?
-
idea双击shift,搜索DefaultListableBeanFactory(默认可集合化的bean工厂)
-
找到
private final Map<String, BeanDefinition> beanDefinitionMap;
- Spring把对象放到一个Map里面去了。
解析private final Map<String, BeanDefinition> beanDefinitionMap;
- 这个map的key就是我们给bean标签设置的id(唯一标识)
- valueBeanDefinition(bean定义)其实是spring对创建的对象进行了各种增强,添加各种强大功能。我们点开源码看一下
package org.springframework.beans.factory.config;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.core.AttributeAccessor;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
int ROLE_APPLICATION = 0;
int ROLE_SUPPORT = 1;
int ROLE_INFRASTRUCTURE = 2;
void setParentName(@Nullable String parentName);
@Nullable
String getParentName();
void setBeanClassName(@Nullable String beanClassName);
@Nullable
String getBeanClassName();
void setScope(@Nullable String scope);
@Nullable
String getScope();
void setLazyInit(boolean lazyInit);
boolean isLazyInit();
void setDependsOn(@Nullable String... dependsOn);
@Nullable
String[] getDependsOn();
void setAutowireCandidate(boolean autowireCandidate);
boolean isAutowireCandidate();
void setPrimary(boolean primary);
boolean isPrimary();
void setFactoryBeanName(@Nullable String factoryBeanName);
@Nullable
String getFactoryBeanName();
void setFactoryMethodName(@Nullable String factoryMethodName);
@Nullable
String getFactoryMethodName();
ConstructorArgumentValues getConstructorArgumentValues();
default boolean hasConstructorArgumentValues() {
return !this.getConstructorArgumentValues().isEmpty();
}
MutablePropertyValues getPropertyValues();
default boolean hasPropertyValues() {
return !this.getPropertyValues().isEmpty();
}
void setInitMethodName(@Nullable String initMethodName);
@Nullable
String getInitMethodName();
void setDestroyMethodName(@Nullable String destroyMethodName);
@Nullable
String getDestroyMethodName();
void setRole(int role);
int getRole();
void setDescription(@Nullable String description);
@Nullable
String getDescription();
ResolvableType getResolvableType();
boolean isSingleton();
boolean isPrototype();
boolean isAbstract();
@Nullable
String getResourceDescription();
@Nullable
BeanDefinition getOriginatingBeanDefinition();
}
里面非常详细的配置了各种属性和方法,包括作用域,是否是单例对象,是否懒加载......
详细了解IOC原理
所有Java对象的实例化和初始化,控制对象与对象之间的依赖关系。
引入log4j2
需要引入2个东西,
- log4j的核心
- log4j2 (对核心的实现)
<log4j.core.version>2.19.0</log4j.core.version>
<log4j2.imp.version>2.19.0</log4j2.imp.version>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.core.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>${log4j2.imp.version}</version>
</dependency>
配置log4j2
- resource下创建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>
跑起来看下日志
2023-09-22 15:23:58 045 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext -
//下面这一句,启动类加载器
Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@ebaa6cb
2023-09-22 15:23:58 248 [main] DEBUG
org.springframework.beans.factory.xml.XmlBeanDefinitionReader -
//加载了一个bean,从xml文件中
Loaded 1 bean definitions from class path resource [bean.xml]
2023-09-22 15:23:58 279 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory
//从DefaultListableBeanFactory这个map中创建对象
- Creating shared instance of singleton bean 'classAdd'
add无参构造器被调用.....
复习log4j2的使用
private Logger logger = LoggerFactory.getLogger(testAddClass.class);
logger.info("使用日志功能");
讲解 BeanDefinitionReader
什么是BeanDefinitionReader?
上面我们讲了BeanDefinition(加强后的Bean),所谓的reader就是读取者,在这里解释为读取器。
他的作用是把各种格式的bean(xml.....)读取过来,可以理解为一个面向下层的IO接口
它本身是一个接口,有多个实现类
从结构上了解IOC
在Spring当中BeanFactory是非常重要的一个类,他可以说是IOC容器的基本实现,但我们一般更多的使用ApplicationContext而非BeanFactory,因为他太底层,功能也太简陋,Spring通过层层接口继承与实现来增强底层的功能
我们打开BeanFactory的结构图看一下
-
该接口在beans依赖下面,org.springframework.beans.factory;
-
指到该接口 点击ctrl + h
ioc获取bean的方法
ClassPathXMLApplicationContext有多个参数可选,可以使用XXX.class来获取对象,可以指定接口或者对象,但要注意,多个实现类会造成歧义,导致报错
DI 依赖注入
使用setter方法注入
创建book类,给设置好setter方法
package com.atguigu;
public class book {
private String bookName;
private String author;
public void setBookName(String bookName) {
this.bookName = bookName;
}
public void setAuthor(String author) {
this.author = author;
}
}
学习第二个标签 properties标签 用来定义对象内部属性
1. name 指向对应的属性
2. value String类型的可以直接赋值
<bean id="book" class="com.atguigu.book">
<property name="author" value="jack"/>
<property name="bookName" value="啦啦啦"/>
</bean>
使用构造器方法注入
创建book类,给设置好有参构造器
package com.atguigu;
public class book {
private String bookName;
private String author;
public book(String bookName, String author) {
this.bookName = bookName;
this.author = author;
}
}
学习第二个标签 constructor-args标签 用来定义对象内部属性
1. name 指向对应的属性
2. value String类型的可以直接赋值
<bean id="book" class="com.atguigu.book">
<constructor-arg name="author" value="jack"/>
<constructor-arg name="bookName" value="啦啦啦"/>
</bean>
怎么注入特殊值?
- 怎么注入null值?
<constructor-arg name="bookName">
<null/>
</constructor-arg>
- 带有特殊符号怎么办
<bean id="book" class="com.atguigu.book">
<constructor-arg name="author" value="jack"/>
<constructor-arg name="bookName" value="a<>b"/>
</bean>
- 解决所有符号问题 Cdata区,ide快捷键CD
<constructor-arg name="bookName" >
<value><![CDATA[!@#$%%^&&&&]]></value>
</constructor-arg>
-
复杂数据类型的引用
-
对象引用
创建员工类和部门类
package com.atguigu; public class dept { private String deptName; public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this.deptName = deptName; } }
员工类
package com.atguigu; public class emp { private String name; private Integer age; private dept dept; public void setName(String name) { this.name = name; } public void setAge(Integer age) { this.age = age; } public void setDept(com.atguigu.dept dept) { this.dept = dept; } public void info() { System.out.println("deptName is " + dept.getDeptName()); } }
- 外部bean引用
在xml里面直接创建两个对象的bean,使用setter方法给属性赋值,这里学习第三种标签 ref标签 指向创建的bean即可,相当于在xml内部定义并直接引用了。上代码,不再赘述
<bean id="dept" class="com.atguigu.dept"> <property name="deptName" value="保安部"/> </bean> <bean id="emp" class="com.atguigu.emp"> <property name="name" value="jack"/> <property name="age" value="22"/> <property name="dept" ref="dept"/> </bean>
- 内部bean
顾名思义就是直接把别的bean对象定义在自己内部。
<bean id="emp" class="com.atguigu.emp"> <property name="name" value="jack"/> <property name="age" value="22"/> <property name="dept"> <bean id="dept" class="com.atguigu.dept"> <property name="deptName" value="生产部"/> </bean> </property> </bean>
- 级联复制
我们学习第四个知识点,我们可以在引入外部bean的基础上,引入外bean的属性,在class后面加上 .属性名 这个东西要求两个bean对象都得有getter方法,而且会有一定的歧义,注意,我在下面的代码中定义了dept3,而我在下面调用 .属性名 时使用的是emp内部给的新name。
<bean id="dept3" class="com.atguigu.dept"> <property name="deptName" value="保安部"/> </bean> <bean id="emp" class="com.atguigu.emp"> <property name="name" value="jack"/> <property name="age" value="22"/> <property name="dept" ref="dept3"/> <property name="dept.deptName" value="技术研发部"/> </bean>
-
数组怎么写
- 给员工类加一个String[] hobby属性;
- 上代码 学习第五个新标签 array标签(配合value使用)
<bean id="emp" class="com.atguigu.emp"> <property name="hobby"> <array> <value>吃饭</value> <value>睡觉</value> <value>打豆豆</value> </array> </property> <property name="name" value="jack"/> <property name="age" value="22"/> <property name="dept" ref="dept3"/> <property name="dept.deptName" value="技术研发部"/> </bean>
- List怎么写?
我们上面的代码里面,一直是给员工增加部门属性,我们现在尝试给部门增加一个 list<emp> 让部门独立出来,包含自己的员工。
上代码
- 学习第六个新标签 list标签(配合ref使用)
- ref成为标签,第七个标签。
- bean成为属性,新属性
<bean id="tom" class="com.atguigu.emp"> <property name="name" value="tom"/> </bean> <bean id="mary" class="com.atguigu.emp"> <property name="name" value="mary"/> </bean> <bean id="jack" class="com.atguigu.emp"> <property name="name" value="jack"/> </bean> <bean id="dept" class="com.atguigu.dept"> <property name="deptName" value="保安部"/> <property name="empList"> <list> <ref bean="tom"></ref> <ref bean="jack"></ref> <ref bean="mary"></ref> </list> </property> </bean>
-
其实现思路还是通过外部引用来实现的
- map 怎么写?
我们给dept 增加一个Map<String,emp>属性,用来演示map。这里的String是empId,当然,我们不需要给emp加id,随便写一个就行。
学习我们的第八个知识点 entry标签(entry的结构是固定的,看代码)
<bean id="dept" class="com.atguigu.dept">
<property name="deptName" value="保安部"/>
<property name="empMap">
<map>
<entry>
<key>
<value>tom</value>
</key>
<ref bean="tom"></ref>
</entry>
</map>
</property>
</bean>
改进map与list
上面的结构我们会发现,如果内容过多,则我们的单个bean中代码会变得非常冗余,而Spring为了解决这个事情,给我们提供了一个全新的标签 util标签 但该标签并非默认存在,需要我们引入新的约束
- 我们需要改一下约束就可以使用util提供的标签了,上代码
<?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/beans/spring-util.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
改进后的list
<bean id="tom" class="com.atguigu.emp">
<property name="name" value="tom"/>
</bean>
<bean id="mary" class="com.atguigu.emp">
<property name="name" value="mary"/>
</bean>
<bean id="jack" class="com.atguigu.emp">
<property name="name" value="jack"/>
</bean>
<util:list id="emps">
<ref bean="tom"></ref>
<ref bean="jack"></ref>
<ref bean="mary"></ref>
</util:list>
<bean id="dept" class="com.atguigu.dept">
<property name="deptName" value="保安部"/>
<property name="empList" ref="emps"/>
</bean>
同理,对于map的简化
<util:map id="empEntrys">
<entry>
<key>
<value>tom</value>
</key>
<ref bean="tom"></ref>
</entry>
</util:map>
p命名空间实现注入
同样需要我们修改约束来实现,他允许我们将标签变得更加简洁。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
添加下面这条命名空间
xmlns:p="http://www.springframework.org/schema/p"
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">
<bean id="tom" class="com.atguigu.emp">
<property name="name" value="tom"/>
</bean>
<bean id="mary" class="com.atguigu.emp">
<property name="name" value="mary"/>
</bean>
<bean id="jack" class="com.atguigu.emp">
<property name="name" value="jack"/>
</bean>
<util:list id="emps">
<ref bean="tom"></ref>
<ref bean="jack"></ref>
<ref bean="mary"></ref>
</util:list>
更简洁的写法
<bean id="dept2" class="com.atguigu.dept" p:deptName="jack" p:empList-ref="emps"></bean>
</beans>
spring引入外部属性文件
如何使用外部属性文件方式连接数据库
我们的目标是将Druid的配置交给Spring来做,而且是通过外部文件来设置数据库的配置信息。这样的好处是,我们不需要手动去配Druid,而且通过修改外部文件即可修改数据库相关的配置,而不需要对代码进行修改。
- 先导入mysql跟Druid依赖
<mysql.connector.java.version>8.0.30</mysql.connector.java.version>
<druid.version>1.0.31</druid.version>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.connector.java.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
- 写一个外部的Properties
jdbc.user=root
jdbc.password=密码
jdbc.url=jdbc:mysql://ip地址:端口/spring6?serverTimezone=Asia/Shanghai
jdbc.driver=com.mysql.cj.jdbc.Driver
- 看一下我们的常规方法(手动配置Druid,其实就是创建import com.alibaba.druid.pool.DruidDataSource;对象,并设置属性值)
package com.atguigu.jdbc;
import com.alibaba.druid.pool.DruidDataSource;
import org.junit.jupiter.api.Test;
public class testDriver {
@Test
public void t1(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl("jdbc:mysql://不给你看/spring6?serverTimezone=Asia/Shanghai");
druidDataSource.setUsername("root");
druidDataSource.setPassword("不给你看");
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
}
}
- 配置命名空间引入外部Properties文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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"/>
</beans>
在上面的代码中我们引入了context命名空间,并引入了指定路径下的配置文件。此时,配置文件中的数据我们已经可以随便调用了。
- 我们直接通过xml给指定的对象赋值。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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"/>
<bean id="druid" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
</bean>
</beans>
- 后面不再演示。
Bean的作用域
其实就是bean对象是否为单例,当指定scope为单例时会警告,因为默认为单例。
学习新属性 Scope= singleton OR prototype
他们的区别在于,单例在容器初始化时就被创建,而多例则是在获取时才创建
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="scope" class="com.atguigu.scope.Oder" scope="singleton">
<property name="orderId" value="10010"/>
</bean>
</beans>
package com.atguigu;
import com.atguigu.scope.Oder;
import org.junit.jupiter.api.Test;
import org.springframework.context.support.BeanDefinitionDsl;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@SuppressWarnings({"all"})
public class testScope {
@Test
public void t1(){
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("scope.xml");
Oder oder = (Oder)classPathXmlApplicationContext.getBean("scope");
System.out.println(oder);
Oder oder1 = (Oder)classPathXmlApplicationContext.getBean("scope");
System.out.println(oder1);
}
}
D:\jdk-17.0.8\bin\java.exe -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\IDEA2023\IntelliJ IDEA 2023.2.2\lib\idea_rt.jar=61702:D:\IDEA2023\IntelliJ IDEA 2023.2.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Users\18329\.m2\repository\org\junit\platform\junit-platform-launcher\1.3.1\junit-platform-launcher-1.3.1.jar;C:\Users\18329\.m2\repository\org\apiguardian\apiguardian-api\1.0.0\apiguardian-api-1.0.0.jar;C:\Users\18329\.m2\repository\org\junit\platform\junit-platform-engine\1.3.1\junit-platform-engine-1.3.1.jar;C:\Users\18329\.m2\repository\org\junit\platform\junit-platform-commons\1.3.1\junit-platform-commons-1.3.1.jar;C:\Users\18329\.m2\repository\org\opentest4j\opentest4j\1.1.1\opentest4j-1.1.1.jar;C:\Users\18329\.m2\repository\org\junit\jupiter\junit-jupiter-engine\5.3.1\junit-jupiter-engine-5.3.1.jar;C:\Users\18329\.m2\repository\org\junit\jupiter\junit-jupiter-api\5.3.1\junit-jupiter-api-5.3.1.jar;D:\IDEA2023\IntelliJ IDEA 2023.2.2\lib\idea_rt.jar;D:\IDEA2023\IntelliJ IDEA 2023.2.2\plugins\junit\lib\junit5-rt.jar;D:\IDEA2023\IntelliJ IDEA 2023.2.2\plugins\junit\lib\junit-rt.jar;D:\Project\Praent\pro-01\target\test-classes;D:\Project\Praent\pro-01\target\classes;D:\libs\apache-maven-3.6.3\maven-repo\org\springframework\spring-context\6.0.2\spring-context-6.0.2.jar;D:\libs\apache-maven-3.6.3\maven-repo\org\springframework\spring-aop\6.0.2\spring-aop-6.0.2.jar;D:\libs\apache-maven-3.6.3\maven-repo\org\springframework\spring-beans\6.0.2\spring-beans-6.0.2.jar;D:\libs\apache-maven-3.6.3\maven-repo\org\springframework\spring-core\6.0.2\spring-core-6.0.2.jar;D:\libs\apache-maven-3.6.3\maven-repo\org\springframework\spring-jcl\6.0.2\spring-jcl-6.0.2.jar;D:\libs\apache-maven-3.6.3\maven-repo\org\springframework\spring-expression\6.0.2\spring-expression-6.0.2.jar;D:\libs\apache-maven-3.6.3\maven-repo\org\junit\jupiter\junit-jupiter-api\5.3.1\junit-jupiter-api-5.3.1.jar;D:\libs\apache-maven-3.6.3\maven-repo\org\apiguardian\apiguardian-api\1.0.0\apiguardian-api-1.0.0.jar;D:\libs\apache-maven-3.6.3\maven-repo\org\opentest4j\opentest4j\1.1.1\opentest4j-1.1.1.jar;D:\libs\apache-maven-3.6.3\maven-repo\org\junit\platform\junit-platform-commons\1.3.1\junit-platform-commons-1.3.1.jar;D:\libs\apache-maven-3.6.3\maven-repo\org\apache\logging\log4j\log4j-core\2.19.0\log4j-core-2.19.0.jar;D:\libs\apache-maven-3.6.3\maven-repo\org\apache\logging\log4j\log4j-api\2.19.0\log4j-api-2.19.0.jar;D:\libs\apache-maven-3.6.3\maven-repo\org\apache\logging\log4j\log4j-slf4j2-impl\2.19.0\log4j-slf4j2-impl-2.19.0.jar;D:\libs\apache-maven-3.6.3\maven-repo\org\slf4j\slf4j-api\2.0.0\slf4j-api-2.0.0.jar;D:\libs\apache-maven-3.6.3\maven-repo\mysql\mysql-connector-java\8.0.30\mysql-connector-java-8.0.30.jar;D:\libs\apache-maven-3.6.3\maven-repo\com\google\protobuf\protobuf-java\3.19.4\protobuf-java-3.19.4.jar;D:\libs\apache-maven-3.6.3\maven-repo\com\alibaba\druid\1.0.31\druid-1.0.31.jar" com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit5 com.atguigu.testScope,t1
2023-09-24 13:27:48 668 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@49c6c24f
2023-09-24 13:27:48 837 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 1 bean definitions from class path resource [scope.xml]
2023-09-24 13:27:48 858 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'scope'
com.atguigu.scope.Oder@7de0c6ae
com.atguigu.scope.Oder@7de0c6ae
Bean 的生命周期
Spring中Bean的生命周期主要分为8个阶段
- 调用无参构造器创建空对象
- 使用setter方法给对象赋值
- 调用后置处理器方法(初始化之前的)
- 初始化Bean对象
- 调用后置处理器方法(初始化之后的)
- bean对象交给开发者使用
- 调用销毁方法
- IOC容器关闭
下面我们通过代码来演示主要过程。
xml中的bean标签允许我们通过指定的标签来指定与其对应的方法
- init-method标签
- destroy-method标签
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.atguigu.life.User" init-method="initMethod" destroy-method="destroyMethod">
<property name="name" value="jack"/>
</bean>
</beans>
我们将几个方法设置好,并将各个步骤打印出来
package com.atguigu.life;
public class User {
private String name;
public User(){
System.out.println("1.无参构造器被调用");
}
public void initMethod(){
System.out.println("4.调用初始化方法");
}
public void destroyMethod(){
System.out.println("7.调用销毁方法");
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("2 给属性设置值");
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
'}';
}
}
下面是打印结果:
1.无参构造器被调用
2 给属性设置值
4.调用初始化方法
2023-09-24 14:04:00 616 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Closing org.springframework.context.support.ClassPathXmlApplicationContext@6fe1b4fb, started on Sun Sep 24 14:04:00 CST 2023
7.调用销毁方法
在上面的演示中,我们并没有展示2个后置处理器的执行。这是因为他们的配置比较特殊,我们需要指定一个类,来继承BeanPOSTProcessor接口,并重写该接口提供的2个方法(后置处理器Before方法和After方法),并将该类以Bean标签的形式生命在Xml文件内。
该方法其实并没有破坏最小入侵性原则,也没有改动原有代码,而是通过增加新的类来完成方法注入,
但该操作有一个问题,就是他会给容器内的所有对象都加上处理器!!!!!
package com.atguigu.life;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之前执行");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之后执行");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
xml声明该类
<bean id="myPostProcessor" class="com.atguigu.life.MyPostProcessor"/>
执行结果:
1.无参构造器被调用
2 给属性设置值
初始化之前执行
4.调用初始化方法
初始化之后执行
2023-09-24 14:29:32 261 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Closing org.springframework.context.support.ClassPathXmlApplicationContext@6fe1b4fb, started on Sun Sep 24 14:29:32 CST 2023
7.调用销毁方法
FactoryBean
-
什么是FactoryBean?
相当于Spring提供的一个外部接口,该接口提供1个方法,会返回一个由使用者自己指定的对象。
-
他的实用价值? 他可以帮助第三方来将复杂的代码进行封装,只对外提供一个简洁的对象获取方法,因为基于Spring平台,AOP相关的强大功能都可以使用。
代码演示
package com.atguigu.factoryBean;
import com.atguigu.life.User;
import org.springframework.beans.factory.FactoryBean;
public class MyFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return null;
}
}
<bean id="myFactoryBean" class="com.atguigu.factoryBean.MyFactoryBean"/>
类型断言
@Test
public void t3(){
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("life.xml");
Object user = classPathXmlApplicationContext.getBean("myFactoryBean");
Assert.isInstanceOf(User.class,user);
}
基于XML的自动装配
我们先看一组代码
package com.atguigu.autoWire.Service;
import com.atguigu.autoWire.DAO.userDao;
public class addServiceImpl implements addService{
private userDao user;
public void setUser(userDao user) {
this.user = user;
}
@Override
public void add() {
System.out.println("addService 被调用~");
user.add();
}
}
我们会发现,在Controller中,我们定义了自己的一个复杂数据类型的属性:addService,然后我们在方法中调用了该对象内部的方法add()
我们先思考一下如何给复杂类型addService赋值,这似乎并不难做到。
创建一个addService的bean,把它装配到addController的Bean中就行了。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="addService" class="com.atguigu.autoWire.Service.addServiceImpl"/>
<bean id="addController" class="com.atguigu.autoWire.Controller.addController">
<property name="addService" ref="addService"/>
</bean>
</beans>
那么,我们想要在AddService中调用基础的DAO怎么办呢?是的,如法炮制即可,配置私有的复杂数据类型,设置他的Setter方法,Bean装配,ref引入。就像下面这样,我们就完成了对MVC的装配!他们之间的关系一目了然,只要有了对象,我们就可以一层一层的去调用他们的方法!
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.atguigu.autoWire.DAO.userDaoImpl"/>
<bean id="addService" class="com.atguigu.autoWire.Service.addServiceImpl">
<property name="user" ref="user"/>
</bean>
<bean id="addController" class="com.atguigu.autoWire.Controller.addController">
<property name="addService" ref="addService"/>
</bean>
</beans>
先别激动,Spring为我们提供了更加方便的装配方法,被称为自动装配。
我们学习新的属性: autowire= byType(默认) or byName or constructor
新代码:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.atguigu.autoWire.DAO.userDaoImpl"/>
<bean id="addService" class="com.atguigu.autoWire.Service.addServiceImpl" autowire="byType"/>
<bean id="addController" class="com.atguigu.autoWire.Controller.addController" autowire="byType">
<property name="addService" ref="addService"/>
</bean>
</beans>
注解方式Bean管理
开启注解扫描
Spring当中是默认不开启注解的,而如果想使用注解。则需要开启注解扫描,并指定对应的区域,让Spring进行管理。
- 需要使用我们的Context命名空间,通过component-scan标签,指定对应的包
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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:component-scan base-package="com.atguigu"/>
</beans>
有针对性的注解扫描(过滤)
Spring我么我们提供了2个新标签(在component-scan标签内使用):
- context:exclude-filter 指定需要排除在外的注解
- context:include-filter 除指定注解外全部排除
在这两个标签的基础上又提供了2个属性
- type 选项可以使annotation(指定注解) 或者 assignable(指定对应的包)
- expression 属性根据type指定注解类或者包位置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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:component-scan base-package="com.atguigu">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="assignable" expression="com.atguigu.User"/>
</context:component-scan>
</beans>
Spring提供的注解
Spring对于Bean,为我们提供了4个注解
- @Repository(DAO层)
- @Controller(Controller层)
- @Service(Service层)
- @Component(父类)
package com.atguigu;
import org.springframework.stereotype.Repository;
@Repository
public class User {
private String name;
public String getName() {
return name;
}
public void sayHi(){
System.out.println("hello word!");
}
public void setName(String name) {
this.name = name;
}
}
测试
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = context.getBean(User.class);
user.sayHi();
@AutoWired 注解
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
@Target注解
注明了该注解可以用在哪些位置
- CONSTRUCTOR 构造器上
- METHOD 方法上
- PARAMETER 参数上
- FIELD 字段上
- ANNOTATION_TYPE 注解上
boolean required() default true;
表示该注解必须写入的内容必须存在,否则报错。
@Autowired实现MVC
代码过多,只展示Service,用 @Component代替Bean声明,@AutoWired实现自动注入
package com.atguigu.autoWire.Service;
import com.atguigu.autoWire.DAO.orderImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class orderServiceImpl implements orderService {
@Autowired
private orderImpl order;
@Override
public void orderServiceRun() {
System.out.println("orderService is run");
order.printOrderId();
}
}
@AutoWired的一些古怪写法
@AutoWired可以写在本类的有参构造器上, 也可以写在本类有参构造器的参数上,或者写在属性的setter方法上。
最后一种奇怪的注入方法,不需要写@AutoWrited,只需要类内部只有一个有参构造器(如果同时有无参构造器,则会报错!),以后看见别懵就行
package com.atguigu.autoWire.Controller;
import com.atguigu.autoWire.Service.orderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class orderController {
private orderService orderService;
public orderController(com.atguigu.autoWire.Service.orderService orderService) {
this.orderService = orderService;
}
public void orderControllerRun(){
System.out.println("orderController is run");
orderService.orderServiceRun();
}
}
@Qualifiar注解
我们的@AutoWired注解有一个缺陷,就是它只会按照类型进行注入,问题就在于,当我们有多个对象满足条件时,他就会乱套。
@Qualifiar注解解决了这个问题,他跟@AutoWired注解一起使用。他需要填入value值,值为对应对象的类名。
假设DAO层多了一个来自Redis的实现类
package com.atguigu.autoWire.DAO;
import org.springframework.stereotype.Repository;
@Repository
public class orderRedisDao implements orderDao{
@Override
public void printOrderId() {
System.out.println("redisDao~~~");
}
}
注入写法为:
@Autowired
@Qualifier(value = "orderRedisDao")
private orderDao order;
@Resource注解
- @Resource是java自己的注解,
- 在java拓展包中,java8自带该注解。
- 如果版本高于Java11或低于Java8,则需要导入如下的依赖才能使用
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
-
该注解可以写在属性上或者setter方法上。
-
情况1:如果我们指定了对象名,则他会根据对象名进行查找。
@Resource(name = "myOrderService")
private com.atguigu.resource.Service.orderService orderService;
@Service("myOrderService")
public class orderServiceImpl implements orderService {
.................
- 如果不指定对象名,他则会根据属性的名字。
@Resource
private orderDao order;
@Repository("order")
public class orderImpl implements orderDao {
- 如果两者都不存在,他就会按照类型去找。
如何避类名重复
当我们有多个同名的类同时出现在被扫描的范围内时,就会发生对象冲突,解决的办法就是给Bean对象指定唯一索引
Spring全注解开发
我们上面介绍的开发模式还有一点点小缺陷,我们还是需要一个xml文件,如何省略他呢?
我们需要创建一个类,使用@Configuration来表示我们是一个配置类,然后使用@ComponentScan注解指定对应的包。
@Configuration
@ComponentScan("com.atguigu")
public class Config {
}
新的上下文获取方式
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
复习Java反射
一页代码复习Java反射
package com.atguigu.clazz;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@SuppressWarnings({"all"})
public class reviewClazz {
@Test
public void t1() throws Exception {
/**
* 获取字节码文件的3种方法
*/
//类.class
Class carClass = Car.class;
//对象.getClass
Class aClass = new Car().getClass();
//Class.forName("全类名")
Class aClass1 = Class.forName("com.atguigu.clazz.Car");
/**
* 实例化方法
*/
Car car = (Car)carClass.getDeclaredConstructor().newInstance();
/**
* 抓取构造方法
*
*/
Constructor[] constructors = carClass.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("类名"+constructor.getName()+"属性个数"+constructor.getParameterCount());
}
//获取所有构造方法(包括私有)
for (Constructor c : carClass.getDeclaredConstructors()) {
System.out.println("类名"+c.getName()+"属性个数"+c.getParameterCount());
}
}
@Test
public void t2() throws Exception {
//类.class
Class carClass = Car.class;
/**
* 获取指定的有参构造器 输入参数类型的class
*/
Constructor constructor = carClass.getConstructor(String.class, int.class, String.class);
Car car1 = (Car) constructor.newInstance("宾利", 100, "red");
System.out.println(car1);
/**
* 如果是私有的就用 Declared获取
* 需要爆破!!!
*/
Constructor constructor2 = carClass.getDeclaredConstructor(String.class, int.class, String.class);
//需要爆破!!!
constructor2.setAccessible(true);
Car car2 = (Car) constructor2.newInstance("马自达", 100, "red");
System.out.println(car2);
}
@Test
public void t3() throws Exception{
/**
* 属性操作,获取属性名,设置值
* 因为属性大多是私有,所以我们直接选择Declared
*/
Class carClass = Car.class;
Car car1 = (Car)carClass.getDeclaredConstructor().newInstance();
Field[] declaredFields = carClass.getDeclaredFields();
for (Field field : declaredFields) {
if (field.getName().equals("name")){
field.setAccessible(true);
field.set(car1,"保时捷");
}
}
System.out.println(car1);
/**
* 对方法的操作
*/
Method[] declaredMethods = carClass.getDeclaredMethods();
for (Method method : declaredMethods) {
if (method.getName().equals("run")){
method.setAccessible(true);
method.invoke(car1);
}
}
}
}
手写IOC并完成依赖注入
直接贴代码了,作者自己写的,跟老师区别很大。建议看老师的,思路相通
public class myApplicationContext {
private int subStarPoint;
private ArrayList<String> classPaths = new ArrayList<>();
//根据指定路径获取所有全类名;
private void setClassPaths(File file) {
if (file.isDirectory()) {
for (File listFile : Objects.requireNonNull(file.listFiles())) {
setClassPaths(listFile);
}
return;
}
if (file.getName().contains(".class")) {
String PerfectPath = file.getAbsolutePath()
.substring(subStarPoint, file.getAbsolutePath().length() - 6)
.replaceAll("\\", "\.");
classPaths.add(PerfectPath);
}
}
//根据basePackage路径获取所有全类名;
private void setClassPathsInBasePackage(String basePackage) {
String newPath = basePackage.replaceAll("\.", "\\");
try {
Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(newPath);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
//指定包的绝对路径
String AbsolutePath = URLDecoder.decode(url.getFile(), "utf-8");
//从绝对路径开始到basePackage前一个字母的长度。
subStarPoint = AbsolutePath.length() - basePackage.length() - 1;
setClassPaths(new File(AbsolutePath));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public myApplicationContext(String basePackage) {
setClassPathsInBasePackage(basePackage);
classPaths.forEach(classPath -> {
try {
Class aClass = Class.forName(classPath);
if (aClass.isAnnotationPresent(Bean.class)) {
Class key = aClass.getInterfaces().length != 0 ? key = aClass.getInterfaces()[0] : aClass;
Beans.put(key, aClass.getDeclaredConstructor().newInstance());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
});
classPaths.forEach(classPath -> {
try {
Class aClass = Class.forName(classPath);
for (Field declaredField : aClass.getDeclaredFields()) {
declaredField.setAccessible(true);
for (Annotation annotation : declaredField.getAnnotations()) {
if (annotation.annotationType().equals(Di.class)) {
Class objKey = aClass;
if (objKey.getInterfaces().length>0)objKey=objKey.getInterfaces()[0];
Object bean = getBean(declaredField.getType());
Object bean1 = getBean(objKey);
declaredField.set(bean1,bean);
}
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
private Map Beans = new HashMap<Class, Object>();
public Object getBean(Class cs) {
return Beans.get(cs);
}
}
这笔记写的太长卡的不行,决定分篇了,下篇正在写。