IOC理论推导
在这里我会通过一个简单的例子来引出IOC,首先我们简单的新建一个maven项目,里面主要有dao层和service层,具体的代码如下:
//userDao层接口
public interface UserDao {
void getUserData();
}
//获取mysql数据的实现
public class UserMysqlDaoImpl implements UserDao{
public void getUserData() {
System.out.println("获取Mysql数据!!!");
}
}
//获取orancle数据的实现
public class UserOrancleDaoImpl implements UserDao{
public void getUserData() {
System.out.println("获取Orancle数据!!!");
}
}
//service层:获取数据
public interface UserService {
void getUser();
}
现在我们正常的情况下要获取数据:
public class UserServiceImpl implements UserService {
//实例化获取mysql数据的接口
UserDao userDao = new UserMysqlDaoImpl();
//获取数据
public void getUser() {
userDao.getUserData();
}
}
此时我们调用service层的方法进行测试:
public class MyTest {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
userService.getUser(); //控制台显示:获取Mysql数据!!!
}
}
这时我们如果想要获取Orancle的数据,我们只能这样做,
public class UserServiceImpl implements UserService {
//实例化获取mysql数据的接口
UserDao userDao = new UserOrancleDaoImpl();
//获取数据
public void getUser() {
userDao.getUserData();
}
}
我们需要修改实例化的对象,把它修改成UserOrancleDaoImpl
,这种方式我们修改了原有的代码,这就造成了每次由于业务的请求,写代码的人需要做出大量的修改,如果代码成千上万行,难道我们都要一一去修改吗,这时我们应该思考是不是这种方式不可取?,是不是还有其它的方式可以来实现?
我们可以用set动态的实现值的注入
,代码如下:
public class UserServiceImpl implements UserService {
private UserDao userDao;
//我们可以利用set动态的实现值的注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
//获取数据
public void getUser() {
userDao.getUserData();
}
}
因此我们在测试的时候可以以动态的注入Dao层的实现,仅仅只要修改初始化的实现对象:
public class MyTest {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
((UserServiceImpl)userService).setUserDao(new UserOrancleDaoImpl());//输出:Orancele
((UserServiceImpl)userService).setUserDao(new UserMysqlDaoImpl());//输出:Mysql
userService.getUser();
}
}
总结:
在之前没有使用set注入的业务中,用户的需求可能会影响我们原先的代码,破坏了程序的完整性,如果代码量很大,那么修改一次的成本很高。
我们使用set注入
的方式,这种思想从本质上解决了这个问题,我们就不需要再去管理对象的创建了,系统的耦合性大大的降低了,就更加专注在业务的实现上了,从而引出spring的低层就是使用的这种机制,及这就是IOC的原型。
IOC的本质
控制反转IOC,是一种设计思想,DI(依赖注入)是实现IOC的一种方法,在没有IOC的程序中,我们使用面向对象编程,对象的创建和对象的依赖关系完全在我们的程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方。
Hellow Spring
接下来我会通过一个实例来体现IOC容器:
下图显示了Spring的工作原理的高级视图。您的应用程序类与配置元数据结合在一起,因此,在ApplicationContext
创建和初始化后,您将拥有一个完全配置且可执行的系统或应用程序。
首先我们编写一个Hellow
实体类:
public class Hellow {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "hellow{" +
"name='" + name + '\'' +
'}';
}
}
然后编写spring
文件,这里命名为beans.xml
,以下示例显示了基于XML的配置元数据的基本结构:
bean == 对象
id == 变量名
class == new 的对象
<?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="hellow" class="com.twy.pojo.Hellow">
<property name="name" value="testSpring"></property>
</bean>
</beans>
最后我们实例化容器:
public static void main(String[] args) {
//获取spring的上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//对象都在spring中管理了,因此直接取出来就可以使用了
Hellow hellow = (Hellow) context.getBean("hellow");
System.out.println(hellow.toString());
}
思考问题?
(1)hellow是怎么创建的?
答:hellow对象是由spring创建的
(2)hellow对象的属性是怎么设置的?
答:hellow对象的属性是由spring容器设置的
上述的过程就叫控制反转
:
控制
:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用spring后,对象是由spring创建的
反转
:程序本身不创建对象,而变成被动的接收对象
依赖注入
:利用set方法来进行注入
IOC创建对象的方式
- 使用无参构造函数创建对象,默认!
- 使用有参构造创建对象:
(1)下标赋值:
(2)通过类型创建,不建议使用:<bean id="user" class="com.twy.pojo.User"> <constructor-arg index="0" value="下标赋值"/> </bean>
(3)直接通过参数名来赋值:<bean id="user" class="com.twy.pojo.User"> <constructor-arg type="java.lang.String" value="类型赋值"/> </bean>
<bean id="user" class="com.twy.pojo.User"> <constructor-arg name="name" value="参数名赋值"/> </bean>
依赖注入
- 构造器注入
见IOC创建对象的方式,及就是构造器注入。 - Set方式注入
(1)新建一个实体类Student.java
(2)beans.xmlpublic class Student { private String name; private Address address; private String[] books; private List<String> hobbys; private Map<String,String> card; private Set<String> games; private String wife; private Properties info; public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public String[] getBooks() { return books; } public void setBooks(String[] books) { this.books = books; } public List<String> getHobbys() { return hobbys; } public void setHobbys(List<String> hobbys) { this.hobbys = hobbys; } public Map<String, String> getCard() { return card; } public void setCard(Map<String, String> card) { this.card = card; } public Set<String> getGames() { return games; } public void setGames(Set<String> games) { this.games = games; } public String getWife() { return wife; } public void setWife(String wife) { this.wife = wife; } public Properties getInfo() { return info; } public void setInfo(Properties info) { this.info = info; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", address=" + address + ", books=" + Arrays.toString(books) + ", hobbys=" + hobbys + ", card=" + card + ", games=" + games + ", wife='" + wife + '\'' + ", info=" + info + '}'; } }
<?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="address" class="com.twy.pojo.Address"> <property name="address" value="江苏"/> </bean> <bean id="student" class="com.twy.pojo.Student"> <!-- 第一种:普通值注入--> <property name="name" value="xiaoming"/> <!-- 第二种:bean注入--> <property name="address" ref="address"/> <!-- 第三种:数组注入--> <property name="books"> <array> <value>01</value> <value>02</value> <value>03</value> </array> </property> <!-- 第四种:list注入--> <property name="hobbys"> <list> <value>01</value> <value>02</value> <value>03</value> </list> </property> <!-- 第五种:map注入--> <property name="card"> <map> <entry key="01" value="0101"/> </map> </property> <!-- 第六种:set注入--> <property name="games"> <set> <value>01</value> </set> </property> <!-- 第七种:null注入--> <property name="wife"> <null/> </property> <!-- 第八种:Properties注入--> <property name="info"> <props> <prop key="学号">0102</prop> </props> </property> </bean> </beans>
bean的作用域
(1)单例模式(Spring默认机制)
仅管理一个singleton bean
的一个共享实例
,并且所有对具有ID或与该bean定义相匹配的ID的bean的请求都将导致该特定的bean实例由Spring容器返回。
换句话说,当您定义一个bean定义并且其作用域为单例时,Spring IoC容器将为该bean定义所定义的对象创建一个实例。该单个实例存储在此类单例bean的高速缓存中,并且对该命名bean的所有后续请求和引用都返回该高速缓存的对象。下图显示了单例作用域的工作方式:
如果您在单个Spring容器中为特定类定义一个bean,则Spring容器将创建该bean定义所定义的类的一个且只有一个实例。单例作用域是Spring中的默认作用域。要将bean定义为XML中的单例,可以定义bean,如以下示例所示:
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
(2)原型模式
每次对特定bean提出请求时,bean部署的非单一原型范围都会导致创建一个新bean实例
。也就是说,该Bean被注入到另一个Bean中,或者您可以通过getBean()容器上的方法调用来请求它。通常,应将原型作用域用于所有有状态Bean,将单例作用域用于无状态Bean。
下图说明了Spring原型范围:
数据访问对象(DAO)通常不配置为原型,因为典型的DAO不拥有任何对话状态。对于我们而言,重用单例图的核心更为容易。)
以下示例将bean定义为XML原型:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
(3)其余的request,session,application,这些只能再web开发中使用到
bean的自动装配
- Spring会在上下文中自动寻找,并自动给bean装配属性。
- 在Spring中有三种自动装配的方式:
(1)在XML中显示的配置,上面我们一直用的这种方式
(2)在Java中显示配置
(3)隐式的自动装配bean (最重要)
- ByName自动装配
<bean id="people" class="com.twy.pojo.People" autowire="byName">
<property name="name" value="twy"/>
</bean>
byName:会自动在容器上下文中查找,和自己对象set方法后面对应的bean的id
- ByType自动装配
<bean id="people" class="com.twy.pojo.People" autowire="byType">
<property name="name" value="twy"/>
</bean>
byType:会自动在容器上下文中查找,和自己对象属性类型相同的bean
- 小结:
byName的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致;
byName的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致;
使用注解实现自动装配
jdk1.5支持注解,Spring2.5就支持注解,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过 @Autowired
的使用来消除 set ,get方法。
使用注解的注意:
- 导入约束:context约束
- 配置注解的支持
<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/>
</beans>
@Autowired
注意:Autowired会按byType,byName找一遍,都没有的情况下才需要qualifier注解。
beans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/>
<bean id="dog" class="com.twy.pojo.Dog"></bean>
<bean id="cat" class="com.twy.pojo.Cat"></bean>
<bean id="person" class="com.twy.pojo.Person"></bean>
</beans>
Person.java:
public class Person {
@Autowired
private Dog dog;
@Autowired
private Cat cat;
private String name;
@Override
public String toString() {
return "Person{" +
"dog=" + dog +
", cat=" + cat +
", name='" + name + '\'' +
'}';
}
}
从代码中可以看出使用@Autowired的简洁方便。
@Resource
@Resource如有指定的name属性,先按该属性进行byName方式查找装配;其次再进行默认的byName方式进行装配;如果以上都不成功,则按byType的方式自动装配。都不成功,则报异常
小结
:
@Resource和@Autowired的区别:
- 都是用来自动装配的,都可以放在属性字段上
- @Autowired优先使用根据byType进行标注装配,配置使用@Qualifier可完成按照名称进行装配
- @Resource如有指定的name属性,先按该属性进行byName方式查找装配;其次再进行默认的byType方式进行装配
开发中的常见注解
@component
(把普通pojo实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>
)
泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类。
下面写这个是引入component的扫描组件 :
<context:component-scan base-package=”com.twy”>
其中base-package为需要扫描的包(含所有子包)
1、@Service用于标注业务层组件
2、@Controller用于标注控制层组件(如struts中的action)
3、@Repository用于标注数据访问组件,即DAO组件.
4、@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
@Service
public class UserServiceImpl implements UserService { }
@Repository
public class UserDaoImpl implements UserDao { }
getBean的默认名称是类名(头字母小写),如果想自定义,可以@Service(“***”)
这样来指定,这种bean默认是单例的,如果想改变,可以使用@Service(“beanName”)
@Scope(“prototype”)来改变,变成原型模式
可以使用以下方式指定初始化方法和销毁方法(方法名任意):
@PostConstruct
public void init() { }
使用JavaConfig实现配置
JavaConfig,是在 Spring 3.0 开始从一个独立的项目并入到 Spring 中的。JavaConfig 可以看成一个用于完成 Bean 装配的 Spring 配置文件,即 Spring 容器,只不过该容器不是 XML文件,而是由程序员使用 Java 自己编写的 Java 类。
- 实体类:
//组件,注入User
@Component
public class User {
//给name注入值
@Value("twy")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
- 配置类:
//Configuration是一个配置类,就是我上面写的beans.xml
@Configuration
@ComponentScan("com.twy.config") //默认扫描的是当前包以及子包
public class MyConfig {
//注册一个bean,方法名相当于bean标签中的id标签
//返回值相当于bean标签中的class属性
@Bean("user")
public User user(){
return new User();
}
}
- 测试类:
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
User user = context.getBean("user", User.class);
System.out.println(user.getName());
}
}
进入ApplicationContext里面我们可以看到 AnnotationConfigApplicationContext实现类。
- 小结:
以上这种纯Java的配置方式,在SpringBoot中随处可见!!!