IoC:Inversion of Control,中文一般翻译为“控制反转”,它还有一个别名叫作依赖注入(Dependency Injection)。但实际上依赖注入控制反转的一种表达方式(还有一种叫依赖查找)。什么是控制反转呢,简单来讲就是原本上层建筑依赖下层建筑,下载经过依赖注入是下层建筑依附于上层建筑。具体表现就是经过注入的方式,为高级类(接口)添加依赖,注入方式能够为构造方法、set方法和接口注入(用得少,侵入性高)。java
而Spring就一种是典型的IoC容器(用来管理bean),而且能够帮助咱们管理注入,省去许多麻烦(感受有点像JVM帮咱们管理内存同样)mysql
推荐看一下《Spring揭秘》这本书,讲的很是不错。面试
首先导入IoC相关依赖:
spring
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.9.RELEASE</version> </dependency>
而后是service和dao层的接口及其实现类:
sql
public interface UserDao { public void save(); } public class UserDaoImpl implements UserDao { @Override public void save() { System.out.println("保存用户信息"); } } public interface UserService { public void register(); } public class UserServiceImpl implements UserService { @Override public void register() { System.out.println("注册"); } }
再而后是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/"> <!-- 定义bean id:惟一标识符 class:bean所对应的类 --> <bean id="userService" class="com.bilibili.service.impl.UserServiceImpl"></bean> <bean id="userDao" class="com.bilibili.dao.impl.UserDaoImpl"></bean> </beans>
经过spring工厂获取定义的JavaBean:session
//加载配置文件,获取spring工厂,从容器中获取dao和service的实现类 ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); //从容器中获取service UserService accountService = (UserService) ac.getBean("userService"); System.out.println(accountService); //从容器中获取dao UserDao userDao = (UserDao) ac.getBean("userDao"); System.out.println(userDao);
放一张被转烂了的图:app
能够看到BeanFactory是工厂的顶层接口,也就是帮助咱们管理bean的,ApplicationContext是其子接口。固然,ApplicationContext除了具备BeanFactory的全部功能以外,还有国际化支持。统一资源加载策略、容器内时间发布的特性。同时,二者对于bean的建立时机也不同,BeanFactory在须要的时候(调用getbean方法)时建立,ApplicationContext会在读取配置以后当即建立。ide
上面给出了ApplicationContext的使用方法,BeanFactory则不太同样(XmlBeanFactory在3.1以后已过期):函数
Resource resource=new ClassPathResource("applicationContext.xml"); BeanFactory factory = new DefaultListableBeanFactory(); BeanDefinitionReader bdr = new XmlBeanDefinitionReader((BeanDefinitionRegistry) factory); bdr.loadBeanDefinitions(resource);
ApplicationContext 接口的实现类非的三种实现类:
bean标签的属性:
id
:给对象在容器中提供的惟一标识,用于获取对象class
:指定类的全限定类名。用于反射建立对象。默认状况下调用无参构造函数。scope
:指定对象的做用范围。singleton
:默认值,单例prototype
:多例request
:WEB 项目中,Spring 建立一个 Bean 的对象,将对象存入到 request 域中.session
:WEB 项目中,Spring 建立一个 Bean 的对象,将对象存入到 session 域中.global session
:WEB 项目中,应用在 Portlet 环境.若是没有 Portlet 环境那么 globalSession 至关于 session.init-method
:指定类中的初始化方法名称(生命周期相关)。destroy-method
:指定类中销毁方法名称(生命周期相关)。bean的生命周期:
在ApplicationContext中:
周期 | 单例singleton | 多例prototype |
---|---|---|
对象出生 | 当应用加载,建立容器时,对象就被建立了。 | 当使用对象时,建立新的对象实例(getBean被调用) |
对象存在 | 只要容器在,对象一直活着 | 只要对象在使用中,就一直活着 |
对象死亡 | 当应用卸载,销毁容器时,对象就被销毁了 | 当对象长时间不用时,被java的垃圾回收器回收了 |
建立bean的三种方式:
<!-- 默认无参构造,通常用这种 --> <bean id="userService" class="com.bilibili.service.impl.UserServiceImpl"></bean>
工厂方式:
//静态工厂 public class BeansFacotory1 { public static Object getBeans(){ return new UserServiceImpl(); } } //示例工厂 public class BeansFacotory2 { public Object getBeans(){ return new UserServiceImpl(); } }
配置方式:
<!-- 静态工厂方法建立对象 class:工厂类的全限定名 factory-method:工厂的静态方法 --> <bean id="userService" class="com.bilibili.utils.BeansFacotory1" factory-method="getBeans"></bean> <!-- 实例工厂方法建立对象--> <!-- 首先配置工厂类的实例 --> <bean id="beansFactory2" class="com.bilibili.utils.BeansFacotory2"></bean> <!-- factory-bean:配置工厂类实例对象 factory-method:工厂类中用于建立对象的方法 --> <bean id="userService" factory-bean="beansFactory2" factory-method="getBeans"></bean>
小声BB:工厂都有了还要你spring干啥
面试官:为何使用spring?
应聘者:由于方便?
面试官:什么?
让spring来管理bean的确方便😂
构造方法注入须要存在有参构造:
public class UserServiceImpl implements UserService { private String userName; private int age; private UserDao userDao; public UserServiceImpl(String userName, int age, UserDao userDao) { this.userName = userName; this.age = age; this.userDao = userDao; } }
在xml中使用constructor-arg
标签进行注入:
<bean id="userService" class="com.bilibili.service.impl.UserServiceImpl"> <!-- 以下3个属性是用来指定给象中的哪一个具体属性赋值 index: 经过下标来指定构造方法中的属性 name: 经过参数名来指定构造方法中的属性 type: 经过参数的类型(全限定名)来指定构造方法中的属性 以下2个属性是用来指定给对象中的属性赋什么值 value: 赋值基本类型的值 例如:string,int,double... ref : 被spring管理的其余bean类型。必须是xml中配置的bean --> <constructor-arg name="userName" value="王者荣耀"/> <constructor-arg name="age" value="18" /> <constructor-arg name="userDao" ref="userDao" /> </bean> <bean id="userDao" class="com.bilibili.dao.impl.UserDaoImpl"></bean>
通常使用这种,比构造方法更灵活。
<bean id="userService" class="com.bilibili.service.impl.UserServiceImpl"> <!-- property:set方法注入属性 name:set方法的名字后面的内容,小写开头 例如:setUserName - userName 底层: userName - UserName - setUserName value:基本属性类型的值 例如 String int... ref:被spring管理的bean类型的值 --> <property name="userName" value="呜啦啦"/> <property name="age" value="20"/> <property name="userDao" ref="userDao"/> </bean> <bean id="userDao" class="com.bilibili.dao.impl.UserDaoImpl"></bean>
其实也是set注入,只不过能够少些一些标签,没什么用。(由于可读性不强)
<!-- 须要在beans标签中添加命名空间:xmlns:p="http://www.springframework.org/schema/p" --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userDao" class="com.bilibili.dao.impl.UserDaoImpl"></bean> <bean id="userService" class="com.bilibili.service.impl.UserServiceImpl" p:userName="呜啦啦" p:age="18" p:userDao-ref="userDao"></bean> </beans>
先来UserServiceImpl实现类:
public class UserServiceImpl implements UserService { private String[] myArray; private List<String> myList; private Map<String,String> myMap; private Set<String> mySet; public String[] getMyArray() { return myArray; } public void setMyArray(String[] myArray) { this.myArray = myArray; } public List<String> getMyList() { return myList; } public void setMyList(List<String> myList) { this.myList = myList; } public Map<String, String> getMyMap() { return myMap; } public void setMyMap(Map<String, String> myMap) { this.myMap = myMap; } public Set<String> getMySet() { return mySet; } public void setMySet(Set<String> mySet) { this.mySet = mySet; } }
而后是xml使用特定标签中注入:
<!-- 注入集合属性: 使用set方法注入集合属性: array:通常用来设置数组 list:通常用来设置list集合 map:通常用来设置map集合 --> <bean id="userService" class="com.bilibili.service.impl.UserServiceImpl"> <property name="myArray"> <array> <value>a</value> <value>b</value> <value>c</value> </array> </property> <property name="myList"> <list> <value>aa</value> <value>bb</value> <value>cc</value> </list> </property> <property name="myMap"> <map> <entry key="key1" value="value1"></entry> <entry key="key2" value="value2"></entry> </map> </property> <property name="mySet"> <set> <value>aaa</value> <value>bbb</value> <value>ccc</value> </set> </property> </bean>
bean除了使用xml进行注入,还可使用注解进行注入,只不过像JdbcTemplate
这种依赖中的类(暂时)就只能使用xml文件来配置注入(context标签须要给beans根标签添加命名空间):
<!-- 加载外部jdbc.properties配置文件 --> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 构造器方式注入数据源 --> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> <!-- 静态方法配置dataSource --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" > <property name="driverClassName" value="${jdbc.driverClass}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean>
jdbc.properties文件
jdbc.driverClass=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring jdbc.username=root jdbc.password=root
使用注解方式进行注入时须要给beans标签添加命名空间:
<?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"> <!-- ↑须要添加context命名空间 --> <!-- 配置注解方式扫描的包:在指定的包下进行扫描,若是发现类上面有注解,让其装配到容器中 --> <context:component-scan base-package="com.bilibili"/> </beans>
声明bean的注解:
@Component("beanName")
:至关于xml配置的<bean><bean/>
标签,注解的value属性值至关于bean标签的id属性,若是不指定value属性,默认bean的id就是类名,首字母小写(Component:组件)@Controller
:通常用于表现层@Service
: 通常用于业务层@Respository
: 通常用于持久层注入相关注解:
@Autowired
:自动装配,标注在须要注入的属性上。当使用该注解注入属性时,set方法能够省略,当有多个相同类型的时候,bean的id必需要和属性的名字一致,才能注入成功,不然报错@Qualifier
:须要结合@Autowired注解一块儿使用,在自动注入的基础上,能够给属性注入指定id的bean@Resource
:直接注入指定id的bean@Value
注解用来给基本类型的属性注入值。可使用${key}
从外部properties配置文件中引入值,须要注意properties配置文件须要在applicationContext.xml中引入做用范围注解:
@Scope
:注解和<bean>
标签的scope属性的做用一致。值能够为prototype
和singleton
(默认)生命周期注解:
@PostConstruct
:声明这个方法是初始化方法,对象被建立的时候调用一次。@PreDestroy
:声明这个方式是销毁方法,对象被销毁的时候调用一次。xml方式和注解方式对比:
\ | xml | 注解 |
---|---|---|
bean定义 | <bean id="" class="" .../> |
@Component 衍生: @Controller @Service @Respository |
bean名称 | 经过id 或name 属性指定 |
经过上面三个注解的value属性指定 |
bean注入 | property 或p命名空间 |
@Autowired 按类型注入@Qualifier 配合@Autowired指定@Resource 的name属性,按名称注入 |
bean做用范围 生命周期 |
init-method destroy-method scope |
@PostConstruct @PreDestroy @Scope |
上面说到像JdbcTemplate
这种依赖中的类(暂时)就只能使用xml文件来配置注入,固然也可使用纯注解进行配置。
主配置:
//声明当前类是一个spring的配置类,用来替代xml配置文件 //获取容器时须要使用AnnotationApplicationContext(@Configuration标注的类.class) @Configuration //用于配置容器初始化时须要扫描的包 //和xml配置中<context:component-scan base-package="com.bilibili"/>做用一致 @ComponentScan("com.bilibili") //导入其余配置类 @Import(JdbcConfig.class) public class SpringConfig { //标注这个方法的返回值做为一个bean而且交给spring容器管理,value属性就是bean的id @Bean("jdbcTemplate") public JdbcTemplate getJdbcTemplate(@Qualifier("dataSource") DataSource dataSource){ return new JdbcTemplate(dataSource); } }
外部配置:
//引入外部文件,和<context:property-placeholder location="classpath:jdbc.properties"/>做用同样 @PropertySource("classpath:jdbc.properties") public class JdbcConfig { //使用value注解引用外部变量,这样就不用写死配置了。 @Value("${jdbc.url}") private String url; @Value("${jdbc.driverClass}") private String driverClass; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; //标注这个方法的返回值做为一个bean而且交给spring容器管理,value属性就是bean的id @Bean("dataSource") public DataSource getDataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driverClass); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } }
测试类:
public class SpringConfigTest { @Test public void getJdbcTemplate() { //使用AnnotationConfigApplicationContext实现类来获取工厂 ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class); JdbcTemplate jdbcTemplate = (JdbcTemplate)ac.getBean("jdbcTemplate"); System.out.println("jdbcTemplate = " + jdbcTemplate); } }
在每一个单元测试类中,咱们都须要获取Spring容器,而后获取要测试的类:
@Before public void setUp() throws Exception { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); accountService = (AccountService) ac.getBean("accountServiceImpl"); }
那么能不能直接在测试类中注入要测试的bean呢?
固然是能够。
首先添加依赖:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- spring5及以上版本要求junit的版本必须是4.12及以上。 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.6.RELEASE</version> </dependency>
而后给测试类配置注解:
//配置spring的单元测试运行器,自动建立spring容器 @RunWith(SpringJUnit4ClassRunner.class) //配置容器建立时依赖的配置 //xml文件方式,直接给value赋值(注意前缀classpath:) @ContextConfiguration("classpath:applicationContext.xml") //纯注解方式,给classes属性赋值 @ContextConfiguration(classes = SpringConfig.class) public class AccountServiceImplTest { //依赖注入 @Resource(name = "accountService") private AccountService accountService; }
而后就能够愉快地在测试类中使用注入的依赖了。依赖少的时候好像并无方便多少😅