Spring-01
1.Spring简介
Spring是一个开源框架,它由[Rod Johnson](https://baike.baidu.com/item/Rod Johnson)创建。它是为了解决企业应用开发的复杂性而创建的。
目前是JavaEE开发的灵魂框架。他可以简化JavaEE开发,可以非常方便整合其他框架,无侵入的进行功能增强。
Spring的核心就是 控制反转(IoC)和面向切面(AOP) 。
2.IOC控制反转
2.1 概念
控制(对象的控制权,由spring容器帮助创建)反转,之前对象的控制权在类手上,现在反转后到了Spring手上。
2.1.1 IOC流程
1. 无spring的问题
2. 简易版自制IOC
大致文件介绍:
public interface UserDao {
String getUserNameById(Integer id);
}
创建容器对象
通过传入路径,解析xml文件。
获取到全类名(有全类名便可以通过反射创建对应类的对象)和id值。
通过反射创建全类名对应的对象
存入map集合中
容器里getBean方法
在测试里就可以拿到UserDao对象
这样使得我们完全与UserDao解耦了。我们只需要将xml配置文件里的改成我们想运行的就可以了
2.2 入门案例
①导入依赖
导入SpringIOC相关依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
②编写配置文件
在resources目录下创建applicationContext.xml文件,文件名可以任意取。但是建议叫applicationContext。
内容如下:
<?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">
<!--
classs:配置类的全类名
id:配置一个唯一标识
-->
<bean class="com.sangeng.dao.impl.StudentDaoImpl" id="studentDao" >
</bean>
</beans>
③创建容器从容器中获取对象并测试
public static void main(String[] args) {
// 1.获取StudentDaoImpl对象
//创建Spring容器,指定要读取的配置文件路径
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
//从容器中获取对象,通过id=studentDao
StudentDao studentDao = (StudentDao) app.getBean("studentDao");
//调用对象的方法进行测试
System.out.println(studentDao.getStudentById(1));
}
2.3 Bean的常用属性配置
2.3.1 id
bean的唯一标识,同一个Spring容器中不允许重复
2.3.2 class
全类名,用于反射创建对象
2.3.3 scope
scope主要有两个值:singleton和prototype
如果设置为singleton则一个容器中只会有这个一个bean对象。默认容器创建的时候就会创建该对象。
并且是容器一创建,对象就创建
如果设置为prototype则一个容器中会有多个该bean对象。每次调用getBean方法获取时都会创建一个新对象。
而在多例时,则是在构造对象的时候创建的
3.DI依赖注入
依赖注入可以理解成IoC的一种应用场景,反转的是对象间依赖关系维护权。
在我们用之前的方式来通过反射得到类对象,最后调用方法,却爆出空指针异常。
此处是show方法出现了问题,我们调用了userDao属性,而作为UserServiceImpl的一个属性没给他赋值,其值一定是null。
public class UserServiceImpl implements UserService {
private UserDao userDao;
private int num;
private String str;
public UserServiceImpl(UserDao userDao, int num, String str) {
this.userDao = userDao;
this.num = num;
this.str = str;
}
// public void setNum(int num) {
// this.num = num;
// }
public UserServiceImpl() {
}
public UserDao getUserDao() {
return userDao;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void show() {
userDao.show();
}
}
UserServiceImpl功能的完成依赖里面的属性,DI依赖注入是spring帮助我们完成以来的赋值过程
3.1 set方法注入
在要注入属性的bean标签中进行配置。前提是该类有提供属性对应的set方法。
package com.sangeng.domain;
public class Student {
private String name;
private int id;
private int age;
private Dog dog;
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
public Student() {
}
public Student(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
<bean class="com.sangeng.domain.Dog" id="dog">
<property name="name" value="小白"></property>
<property name="age" value="6"></property>
</bean>
<bean class="com.sangeng.domain.Student" id="student" >
<!--
name属性用来指定要设置哪个属性
value属性用来设置要设置的值
ref属性用来给引用类型的属性设置值,可以写上Spring容器中bean的id
-->
<property name="name" value="东南枝"></property>
<property name="age" value="20"></property>
<property name="id" value="1"></property>
<property name="dog" ref="dog"></property>
</bean>
注意:ref属性需要把对应的引用对象放入spring容器当中,其对应id填入ref中,就可以把对象引入进来,
3.2 有参构造注入
在要注入属性的bean标签中进行配置。前提是该类有提供对应的有参构造。
public class Student {
private String name;
private int id;
private int age;
private Dog dog;
public Student(String name, int id, int age, Dog dog) {
this.name = name;
this.id = id;
this.age = age;
this.dog = dog;
}
//.....省略其他
}
直接写bean标签,通过反射默认调用空参构造来创建对象
arg是argument的缩写–构造参数
<!--使用有参构造进行注入-->
<bean class="com.sangeng.domain.Student" id="student2" >
<constructor-arg name="name" value="自挂东南枝"></constructor-arg>
<constructor-arg name="age" value="20"></constructor-arg>
<constructor-arg name="id" value="30"></constructor-arg>
<constructor-arg name="dog" ref="dog"></constructor-arg>
</bean>
3.3 复杂类型属性注入
实体类如下:
注释为Lombok
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int age;
private String name;
private Phone phone;
private List<String> list;
private List<Phone> phones;
private Set<String> set;
private Map<String, Phone> map;
private int[] arr;
private Properties properties;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Phone {
private double price;
private String name;
private String password;
private String path;
}
下面的内容不用记忆。自己找找就可以。没有找到合适的标签或者是标签里面合适的属性,就把标签结束掉,然后看看里面是否有合适的子标签,或者直接将值写在标签体中
配置如下:
<?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 class="com.sangeng.domain.Phone" id="phone">
<property name="price" value="3999"></property>
<property name="name" value="黑米"></property>
<property name="password" value="123"></property>
<property name="path" value="qqqq"></property>
</bean>
<bean class="com.sangeng.domain.User" id="user">
<property name="age" value="10"></property>
<property name="name" value="大队长"></property>
<property name="phone" ref="phone"></property>
<property name="list">
<list>
<value>三更</value>
<value>西施</value>
</list>
</property>
<property name="phones">
<list>
<ref bean="phone"></ref> //因为bean是引用这里选择了这个
</list>
</property>
<property name="set">
<set>
<value>setEle1</value>
<value>setEle2</value>
</set>
</property>
<property name="map">
<map>
<entry key="k1" value-ref="phone"></entry> //每一个键值对是一个entry对象
<entry key="k2" value-ref="phone"></entry>
</map>
</property>
<property name="arr">
<array>
<value>10</value>
<value>11</value>
</array>
</property>
<property name="properties"> //该类主要用于读取Java的配置文件
<props>
<prop key="k1">v1</prop>
<prop key="k2">v2</prop>
</props>
</property>
</bean>
</beans>
4.Lombok
①导入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
②增加注解
@Data //根据属性生成set,get方法
@NoArgsConstructor //生成空参构造
@AllArgsConstructor //生成全参构造
public class Phone {
private double price;
private String name;
private String password;
private String path;
}
这里使用alt+f7可以查看方法
5.SPEL
让让我们在配置文件中可以写一些简单的计算,bean的id
我们可以再配置文件中使用SPEL表达式。写法如下:
<property name="age" value="#{20+8}"/>
<property name="car" value="#{car}"/>
注意:SPEL需要写到value属性中,不能写到ref属性。
6.配置文件
6.1 读取properties文件
我们可以让Spring读取properties文件中的key/value,然后使用其中的值。
①设置读取properties
在Spring配置文件中加入如下标签:指定要读取的文件的路径。
<context:property-placeholder location="classpath:filename.properties">
其中的classpath表示类加载路径下。
而那行语句也会让我们resources里面的东西被搬运到classes目录下。这个classes就是类加载的目录,类加载器会在这里面加载.class文件
我们也会用到如下写法:classpath:**.properties 其中的* * 表示文件名任意。
注意:context命名空间的引入是否正确
②使用配置文件中的值
在我们需要使用的时候可以使用${key}来表示具体的值。注意要再value属性中使用才可以。例如:
<property name="propertyName" value="${key}"/>
最终拿到配置文件的值,完成读取。
6.2 引入Spring配置文件
起因:项目过大,一个配置文件显得臃肿,转为多个配置文件,一个主配置文件。而在主配置文件中就需要引入其它的配置文件
我们可以在主的配置文件中通过import标签的resource属性,引入其他的xml配置文件
<import resource="classpath:applicationContext-book.xml"/>
7. 低频知识点
7.1 bean的配置
7.1.1 name属性
我们可以用name属性来给bean取名。例如:
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource" name="dataSource2,dataSource3">
<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>
获取的时候就可以使用这个名字来获取了
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
DruidDataSource dataSource = (DruidDataSource) app.getBean("dataSource3");
System.out.println(dataSource);
}
7.1.2 lazy-init
(单例设计模式)是懒汉式,不得不用的时候才加载
可以控制bean的创建时间,如果设置为true就是在第一次获取该对象的时候才去创建。
<bean class="com.alibaba.druid.pool.DruidDataSource" lazy-init="true" id="dataSource" name="dataSource2,dataSource3">
<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>
7.1.3 init-method
可以用来设置初始化方法,设置完后容器创建完对象就会自动帮我们调用对应的方法。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private String name;
private int id;
private int age;
//初始化方法
public void init(){
System.out.println("对学生对象进行初始化操作");
}
}
<bean class="com.sangeng.domain.Student" id="student" init-method="init"></bean>
注意:配置的初始化方法只能是空参的。
7.1.4 destroy-method
可以用来设置对象销毁之前调用的方法,设置完后容器销毁对象前就会自动帮我们调用对应的方法。
如:对象销毁之前调用,用于释放资源
<bean class="com.sangeng.domain.Student" id="student" destroy-method="close"></bean>
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private String name;
private int id;
private int age;
public void init(){
System.out.println("对学生对象进行初始化操作");
}
public void close(){
System.out.println("对象销毁之前调用,用于释放资源");
}
}
注意:配置的方法只能是空参的。
7.1.5 factory-bean&factory-method
使用场景:
当我们需要让Spring容器使用工厂类来创建对象放入Spring容器的时候可以使用factory-bean和factory-method属性。
具体解释:
之前对象的创建都是很简单的,调用构造方法+设置几个属性就行,但某些场景下显然不会这样,还需要调用一系列的方法来完成初始化的一些操作才能正确的使用这个对象。
如果我们不用spring方式来创建汽车对象,并将测试方法的调用完:
完成汽车的创建和初始化。
不过这样显然比较复杂(创建的方式就是这一套),所以我们将这部分代码分离出来。我们使用工厂设计模式。
这样我们得到了一样的结果。(此处为实例工厂,因为我们的方法是成员方法。)
我们也可以创建静态工厂,只需要在方法前加static
我们希望用了spring,当我们需要car的时候,spring自己去找相应的工厂,调用相应的方法,获取car对象,放入spring容器当中。而这就是这两个属性的应用场景
7.1.5.1 配置实例工厂创建对象
配置文件中进行配置
<!--创建实例工厂,对应CarFactory carFactory = new CarFactory();对应CarFactory carFactory = new CarFactory();-->
<bean class="com.sangeng.factory.CarFactory" id="carFactory"></bean>
<!--使用实例工厂创建Car放入容器-->
<!--factory-bean 用来指定使用哪个工厂对象-->
<!--factory-method 用来指定使用哪个工厂方法-->
<bean factory-bean="carFactory" factory-method="getCar" id="car"></bean> //最终会把方法的返回值放入容器当中,并使用指定的id作为key
创建容器获取对象测试
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取car对象
Car c = (Car) app.getBean("car");
System.out.println(c);
7.1.5.2 配置静态工厂创建对象
直接用类名调用,意味着不需要创建工厂对象了。
配置文件中进行配置
<!--使用静态工厂创建Car放入容器-->
<bean class="com.sangeng.factory.CarStaticFactory" factory-method="getCar" id="car2"></bean>
创建容器获取对象测试
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取car对象
Car c = (Car) app.getBean("car2");
System.out.println(c);
Spring-02
1.注解开发
为了简化配置,Spring支持使用注解代替xml配置。(大势所趋)
之前我想将phone放到spring容器里面,需要在xml文件完成配置
但如果是注解,只要一个。显然更方便
2.Spring常用注解
2.0 注解开发准备工作
如果要使用注解开发必须要开启组件扫描,这样加了注解的类才会被识别出来。Spring才能去解析其中的注解。
<!--启动组件扫描,指定对应扫描的包路径,该包及其子包下所有的类都会被扫描,加载包含指定注解的类-->
<context:component-scan base-package="com.sangeng"/>
2.1 IOC相关注解
2.1.1 @Component,@Controller,@Service ,@Repository
上述4个注解都是加到类上的。
他们都可以起到类似bean标签的作用。可以把加了该注解类的对象放入Spring容器中。
实际再使用时选择任意一个都可以。但是后3个注解是语义化注解。
如果是Service类要求使用@Service。
如果是Dao类要求使用@Repository (跟数据访问层相关的)
如果是Controllerl类(SpringMVC中会学习到)要求使用@Controller
如果是其他类可以使用@Component
例如:
配置文件如下:
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<!--启动组件扫描,指定对应扫描的包路径,该包及其子包下所有的类都会被扫描,加载包含指定注解的类-->
<context:component-scan base-package="com.sangeng"></context:component-scan>
</beans>
类如下:
@Repository("userDao")
public class UserDaoImpl implements UserDao {
public void show() {
System.out.println("查询数据库,展示查询到的数据");
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Component("phone")
public class Phone {
private double price;
private String name;
private String password;
private String path;
}
@Service("userService")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserServiceImpl implements UserService {
private UserDao userDao;
private int num;
private String str;
public void show() {
userDao.show();
}
}
测试类如下:
public class Demo {
public static void main(String[] args) {
//创建容器
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取对象
UserDao userDao = (UserDao) app.getBean("userDao");
Phone phone = (Phone) app.getBean("phone");
UserService userService = (UserService) app.getBean("userService"); //父类引用指向子类对象
System.out.println(phone);
System.out.println(userService);
System.out.println(userDao);
}
}
2.2 DI相关注解
如果一个bean已经放入Spring容器中了。那么我们可以使用下列注解实现属性注入,让Spring容器帮我们完成属性的赋值。
2.2.1 @Value
记得是spring的注解,而不是Lombok的
主要用于String,Integer等可以直接赋值的属性注入。不依赖setter方法,支持SpEL表达式。
例如:
@Service("userService")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserServiceImpl implements UserService {
private UserDao userDao;
@Value("199")
private int num;
@Value("三更草堂")
private String str;
@Value("#{19+3}") //spEL
private Integer age;
public void show() {
userDao.show();
}
}
2.2.2 @AutoWired
userDao的数据类型是UserDao接口类型,我们希望从spring容器当中获取到对应类型的对象,完成赋值。而完成这个的就是@AutoWired
Spring会给加了该注解的属性自动注入数据类型相同的对象。
前提:类和对象都要在spring容器里面
例如:
@Service("userService")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Value("199")
private int num;
@Value("三更草堂")
private String str;
@Value("#{19+3}")
private Integer age;
public void show() {
userDao.show();
}
}
这些注解能生效的前提:
主键扫描到相应的类
属性所属的类要在容器当中
属性对象对应的类型,也要在容器当中
required属性代表这个属性是否是必须的,默认值为true。如果是true的话Spring容器中如果找不到相同类型的对象完成属性注入就会出现异常。
2.2.3 @Qualifier
如果相同类型的bean在容器中有多个时(大概率不会有,一般就是一个)
单独使用@AutoWired就不能满足要求,这时候可以再加上@Qualifier来指定bean的名字从容器中获取bean注入。
例如:
@Autowired
@Qualifier("userDao2")
private UserDao userDao;
注意:该直接不能单独使用。单独使用没有作用
2.3 xml配置文件相关注解
@Configuration
标注在类上,表示当前类是一个配置类。我们可以用注解类来完全替换掉xml配置文件。(xml文件可以删掉了)
注意:如果使用配置类替换了xml配置,spring容器要使用:AnnotationConfigApplicationContext
例如:
@Configuration
public class ApplicationConfig {
}
@ComponentScan
可以用来代替context:component-scan标签来配置组件扫描。
basePackages属性来指定要扫描的包。
注意要加在配置类上。
例如:
@Configuration
@ComponentScan(basePackages = "com.sangeng")//指定要扫描的包
public class ApplicationConfig {
}
使用这两个注解后即可测试
@Bean
可以用来代替bean标签(上面提到的@component等等也是同样),主要用于第三方类的注入。比如之前写到的
之前的注解是找到对应的类,在类的上面添加注解。但此时这个类是别人的,无法修改
使用:定义一个方法,在方法中创建对应的对象并且作为返回值返回。然后在方法上加上@Bean注解,注解的value属性来设置bean的名称。
例如:
@Configuration
@ComponentScan(basePackages = "com.sangeng")
public class ApplicationConfig {
@Bean("dataSource")
public DruidDataSource getDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUsername("root");
druidDataSource.setUrl("jdbc:mysql://localhost:3306/mybatis_db");
druidDataSource.setPassword("root");
return druidDataSource;
}
}
对应测试方法和结果
注意事项:如果同一种类型的对象在容器中只有一个,我们可以不设置bean的名称。
具体写法如下:
@Configuration
@ComponentScan(basePackages = "com.sangeng")
public class ApplicationConfig {
@Bean
public DruidDataSource getDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUsername("root");
druidDataSource.setUrl("jdbc:mysql://localhost:3306/mybatis_db");
druidDataSource.setPassword("root");
return druidDataSource;
}
}
获取方式如下:
public static void main(String[] args) {
//创建注解容器
AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(ApplicationConfig.class);
//根据对应类的字节码对象获取
DataSource bean = app.getBean(DataSource.class); //DruidDataSource也可以,因为DataSource为接口,前者为其实现类
System.out.println(userService);
}
@PropertySource
可以用来代替context:property-placeholder,让Spring读取指定的properties文件。然后可以使用@Value来获取读取到的值。
使用:在配置类上加@PropertySource注解,注解的value属性来设置properties文件的路径。
然后在配置类中定义成员变量。在成员变量上使用@Value注解来获取读到的值并给对应的成员变量赋值。
例如:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_db
jdbc.username=root
jdbc.password=root
读取文件并且获取值—把key对应的value赋值给成员变量
@Configuration
@ComponentScan(basePackages = "com.sangeng")
@PropertySource("jdbc.properties")
public class ApplicationConfig {
@Value("${jdbc.driver}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DruidDataSource getDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUsername(username);
druidDataSource.setUrl(url);
druidDataSource.setPassword(password);
return druidDataSource;
}
}
注意事项:使用@Value获取读到的properties文件中的值时使用的是${key},而不是#{key}。
3.如何选择XML还是注解
①SSM
自己项目中的类的IOC和DI都使用注解,对第三方jar包中的类,配置组件扫描时使用xml进行配置。
②SpringBoot
纯注解开发