Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架
官方文档
IOC介绍
什么是IOC(控制反转)
- 把对象创建和对象之间的调用过程,交给Spring进行管理。
- 使用IOC目的:为了降低耦合度
IOC本质
控制反转loC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现loC的一种方法,也有人认为DlI只是loC的另一种说法。没有loC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是loC容器,其实现方法是依赖注入(Dependency Injection,Dl)。
IOC底层
Spring提供的IOC容器实现的两种方式(两个接口)
- BeanFactory接口:IOC容器基本实现是Spring内部接口的使用接口,不提供给开发人员进行使用(加载配置文件时候不会创建对象,在获取对象时才会创建对象。)
- ApplicationContext接口:BeanFactory接口的子接口,提供更多更强大的功能,提供给开发人员使用(加载配置文件时候就会把在配置文件对象进行创建)推荐使用!
简单IOC例子
没用IOC思想的代码
假如有这些东西,
部分代码如下
public class UserDao1 implements UserDao {
@Override
public void getUser() {
System.out.println("张三");
}
}
public class UserServiceImpl implements UserService{
UserDao user= new UserDao1();
@Override
public void getUser() {
user.getUser();
}
}
下面属于用户调用了,用户调用不是指客户那种,就好比,我写一个模块,让其他人调用我写的模块。这里的main方法,就是调用的我写的模块。调用的人就相当于用户,因为可以不同的人调用我这个模块,不同的用户。
public class testUser {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
userService.getUser();
}
}
最终会输出UserDao1张三
。
这时候有个问题,用户想要输出UserDao2怎么办,UserDao3呢?
这不得去改源码吗,UserServiceImpl.java
里面UserDao user= new UserDao2();``UserDao user= new UserDao3();
每当调用的人,就是用户,有新需求了,就去改源码,不是一个好程序,需要解耦。这些东西要让用户自定义。
使用IOC思想的代码
我们这样修改源码,多一个set方法。
使用了set注入后,程序不在具有主动性,而是变成了被动的接受对象。
public class UserServiceImpl implements UserService{
UserDao user= null;
public void setUser(UserDao user) {
this.user = user;
}
@Override
public void getUser() {
user.getUser();
}
}
用户调用时,用set方法自己设置一个对象,我要用什么对象,我直接设置,这样就不用去修改源码了,用户用什么,自己传入什么。
main这里是用户调用,不属于源码范围了。我是程序员,我用别人写的Spring,我就是Spring的用户。
public class testUser {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
userService.setUser(new UserDao2());//李四
userService.getUser();
}
}
使用Spring IOC
Maven创建项目后,导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
这是测试的目录结构
package com.lyd.pojo;
/**
* @author liyadong
* @create 2022-03-31-14:31
*/
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Spring配置文件ApplicationContext.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
使用Spring来创建对象,在Spring中这些都称为Bean.
一个对象就相当于一个bean
id:变量名
class:对象类的全类名
-->
<bean id="user" class="com.lyd.pojo.User">
<property name="name" value="张三"></property>
<property name="age" value="23"></property>
</bean>
</beans>
bean标签(核心)
<!--
使用Spring来创建对象,在Spring中这些都称为Bean.
一个对象就相当于一个bean
id:变量名
class:对象类的全类名
name:取多个别名,各种常用的分隔符隔开,可以通过这些别名进行访问对象
-->
<bean id="user" class="com.lyd.pojo.User" name="u u1,u2">
<property name="name" value="张三"></property>
<property name="age" value="23"></property>
</bean>
alias标签
取个别名,可以通过别名获取对象
- name:被取别名的对象名
alias:别名
<alias name="user" alias="u"></alias>
import标签
用于团队合作的时候,引入其他的Spring配置文件,就可以用当前的配置文件访问其他文件里面的对象了
<import resource="beans1.xml"></import>
<import resource="beans2.xml"></import>
依赖注入(DI)
public class Student {
private String name;
private School school;
private String[] books;
private List<String> hobbys;
private Map<String,String> card;
private Set<String> games;
private Properties info;
private String wife;
//......get,set,toString省略
}
set注入
对象里面这些参数一定要有set方法才能注入,不然注入不了。 ```xml <?xml version=”1.0” encoding=”UTF-8”?>
红楼梦 西游记 水浒传 听歌 看电影 学习 王者荣耀 原神 yyyyyyy xxxxxxxx root 123456
<a name="EjZyW"></a>
### 根据构造器注入
可以通过注入构造器的参数进行创建对象。**默认是使用无参构造器**<br />当然构造器注入也是可以向set注入那样多种类型注入。
```xml
<bean id="user" class="com.lyd.pojo.User">
<!--
1.属性名
-->
<constructor-arg name="age" value="23"/>
<constructor-arg name="name" value="王武"/>
<!--
2.形参类型
通过type的方式可以用指定参数类型的方式,没有别名。
这种方式不推荐,要是多个类型一样的参数呢
-->
<!-- <constructor-arg type="int" value="24"/>-->
<!-- <constructor-arg type="java.lang.String" value="张三"/>-->
<!--
3.参数索引
-->
<!-- <constructor-arg index="0" value="王二"/>-->
<!-- <constructor-arg index="1" value="25"/>-->
</bean>
p命名和c命名空间注入
需要引入
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--p命名空间注入,可以直接注入属性值:property-->
<bean id="user" class="com.lyd.pojo.User" p:age="23" p:name="张三"/>
<!--c命名空间注入,通过构造器注入:construct-args-->
<bean id="user2" class="com.lyd.pojo.User" c:age="32" c:name="李思"/>
</beans>
使用对象
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
通过XML获取对象,还可以通过注解,文件等。
执行这句话时,就创建了ApplicationContext.xml里面的全部对象,默认执行的无参构造函数。
把对象都写在程序之外的地方,用户可以直接修改,想要什么对象就自己写什么对象,参数都可以自己配置。使用时只用传一个字符串就得到了。
public class testSpringIOC {
public static void main(String[] args) {
//获取Spring的上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
//全部对象都是spring中管理了,要使用就去Spring中取出来。给用户一个容器,要什么就去取什么
//User user = (User)context.getBean("user");
User user = context.getBean("user",User.class);
System.out.println(user.toString());
//结果:User{name='张三', age=23}
}
}
bean作用域
在bean标签的scope属性里面设置
- 单例模式(Spring默认机制)
只会创建一个实例,翻来覆去用这个实例。
<bean id="user" class="com.lyd.pojo.User" scope="singleton"/>
原型模式:每次从容器中get的时候,都会产生一个新的对象
<bean id="user" class="com.lyd.pojo.User" scope="prototype"/>
其余的request,session这些只能在web开发中使用
bean的自动装配
使用autoaire属性自动装配
这是手动装配bean,gei每个set都手动注入值
<bean id="cat" class="com.lyd.pojo.Cat"/>
<bean id="dog" class="com.lyd.pojo.Dog"/>
<bean id="people" class="com.lyd.pojo.People">
<property name="string" value="张三"/>
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
</bean>
使用自动装配
<bean id="cat" class="com.lyd.pojo.Cat"/>
<bean id="dog" class="com.lyd.pojo.Dog"/>
<!--
autowire属性:可以设置主动装配bean,自动在容器上下文中查找
byName:会自动在容器上下文中查询,和自己对象set方法后面的值对应的beanid
byType:会自动在容器山下文中查找,和自己对象属性类型相同的bean
-->
<bean id="people" class="com.lyd.pojo.People" autowire="byName">
<property name="string" value="张三"/>
</bean>
小结:
- byName的时候,需要保证所有的bean的id唯一,并且这个bean需要和自动注入的属性的set的方法的值一致
byType的时候,需要保证所有的bean的class唯一,并且这个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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--配置注解支持-->
<context:annotation-config/>
<!--xml里面什么都不配置,id和class还是要的-->
<bean id="cat" class="com.lyd.pojo.Cat"/>
<bean id="dog" class="com.lyd.pojo.Dog"/>
<bean id="people" class="com.lyd.pojo.People"/>
</beans>
@Autowired:
直接放在属性上就可以,也可以放在set方法上
- 使用Autowired注解就可以不用边写set方法了,注解都是用反射获取值得,提前是自动装配的属性在IOC容器中,且符合规范 ```java package com.lyd.pojo;
import org.springframework.beans.factory.annotation.Autowired;
/**
- @author liyadong
@create 2022-03-31-17:42 */ public class People { @Autowired private Cat cat; //如果显示的定义了Autowired的required属性为false,说明这个对象可以为null,否则不允许为空 @Autowired(required = false) private Dog dog; private String name;
public void setName(String name) {
this.name = name;
}
public Cat getCat() {
return cat;
} public Dog getDog() {
return dog;
} public String getString() {
return name;
}
@Override public String toString() {
return "People{" +
"cat=" + cat +
", dog=" + dog +
", name='" + name + '\'' +
'}';
} }
<a name="Lzc9r"></a>
#### @Qualifier
如果@Autowired自动装配的环境比较复杂,自动装配无法通过一个注解@Autowired完成的时候、我们可以使用@Qualifier(value="xxx")去配置@Autowired的使用,指定一个唯一的bean对象注入!<br />指定不同的beanid,但是类型不一样是要报错的。
```xml
<bean id="cat222" class="com.lyd.pojo.Cat"/>
@Autowired
@Qualifier(value = "cat222")
private Cat cat;
@Resource
这是java自带的注解,可以指定name,type进行名字,类型的指定装配
@Resource(name = "cat222",type = Cat.class)
private Cat cat;
@Resource(name="dog",type=Dog.class)
private Dog dog;
小结
@Resource和@Autowired的区别:
都是自动装配的,都可以放在属性字段上
@Autowired默认通过byType的方式实现,然后再去byName
@Resource默认通过byName的方式实现,然后再去byType
使用注解开发
使用注解需要导入aop包
使用注解需要导入context约束,增加注解的支持
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--
指定要扫描的包.
该包下所有带有@Component 注解的类都等价于bean标签,装在容器里面
-->
<context:component-scan base-package="com.lyd.pojo"/>
<!--配置注解支持-->
<context:annotation-config/>
</beans>
@Component和@Value
- 直接在类上面加上
@Component
注解,配合配置文件里面的指定扫描包<context:component-scan base-package="com.lyd.pojo"/>
,这个类就被装到了IOC容器里面。 - 在属性或者set方法上面,可以通过
@Value
注入值 ```java package com.lyd.pojo;
import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;
//@Component组件,等价于
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Value("23")
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
调用
```java
public class testSpringIOC {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
User user = context.getBean("user",User.class);
System.out.println(user.toString());
}
}
衍生注解
@Component有几个衍生注解,根据分层不同,会有不同的注解标记
- dao层:@Repository
- service层:@Service
- controller层:@Controller
这四个注解功能一样,不同层对应不同的注解,都是代表将某个类注册到Spring中,装配Bean
需要在配置文件扫描指定包改成base-package="com.lyd"
配置作用域@Scope
@Component
@Scope("singleton")//作用域单例模式
//@Scope("prototype")//原型模式
public class Dog {
}
JavaConfig实现配置
完全用java来配置,就不用xml了。
接着上面的步骤,单独建一个配置类
package com.lyd.config;
import com.lyd.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
//这个也会被Spring容器托管,注册到容器中,因为他本来就是一个@Component
//@Component代表这是一个配置类,就是和我们之前的ApplicationContext.xml一样的
@Configuration
//扫描包 <context:component-scan base-package="com.lyd.pojo"/>
@ComponentScan("com.lyd.pojo")
//导入其他的配置类,合并成一个配置类
@Import({MyConfig2.class,MyConfig3.class})
public class MyConfig {
//注册一个bean,就相当于之前写的bean标签
//1.方法的名字,就相当于bean标签的id属性
//2.方法的返回类型,就相当于bean标签中的class属性
//3.返回值就是要注入到bean的对象
@Bean
public User user(){
return new User();
}
}
然后通过注解的方式AnnotationConfigApplicationContext
获取上下文对象,来从IOC容器中取出对象
@Test
public void Test01(){
//如果完全使用了配置类方法取做,我们就只能通过AnnotationConfigApplicationContext上下文来获取容器,通过配置类的class对象加载
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
User user = (User)context.getBean("user");
System.out.println(user.toString());
}