本文是自己学习的一个总结
在Spring官网中搜索bean,找到相应的xml基本架构,复制到我们xml中后,开始装配bean。
Spring官网:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#spring-core。
依赖注入的方式主要有三种,构造器注入,setter注入,接口注入。XML装配方式也有与这三者相对应的装配方式。不过因为接口注入很少见,所以这里就只记录前两种装配方式。
先建立一个带有构造方法的类。
package org.company.think.in.spring.ioc.overview.domain;
public class User {
private Long id;
private String name;
public User(Long id, String name) {
this.id = id;
this.name = name;
}
}
之后在XML文件中这样定义。我们在bean标签下使用
装配的例子如下
<!--该xml名为application-context.xml-->
<bean id="user1" class="org.company.think.in.spring.ioc.overview.domain.User">
<constructor-arg index="0" value="1"/>
<constructor-arg index="1" value="user"/>
</bean>
那么关于该bean的描述就存储在application-context.xml文件中,之后根据这个XML文件生成对应的容器,通过容器就可以取到我们描述的Bean实例。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application-context.xml");
User user = (User) applicationContext.getBean("user1");
Java中构造器是可以有多个的。XML注入方式中,系统会根据参数的类型和顺序与类中的构造器进行匹配。尽管XML中参数的定义都是以字符串的形式(比如上述例子中的id是Long类型,但XML中是写成index=“0” value=“1”),但系统会为我们自动进行类型转换。
现在我们给User新增一个构造器,新的构造器和旧的一样,只是参数的位置调换了,代码如下。
package org.company.think.in.spring.ioc.overview.domain;
public class User {
private Long id;
private String name;
//旧的构造器
public User(Long id, String name) {
this.id = id;
this.name = name;
}
//新的构造器
public User(String name, Long id) {
this.id = id;
this.name = name;
}
}
然后XML文件中也新增一个bean,对应着新的构造器。
<bean id="user1" class="org.company.think.in.spring.ioc.overview.domain.User">
<constructor-arg index="0" value="1"/>
<constructor-arg index="1" value="user1"/>
</bean>
<bean id="user2" class="org.company.think.in.spring.ioc.overview.domain.User">
<constructor-arg index="0" value="user2"/>
<constructor-arg index="1" value="2"/>
</bean>
然后在取出这两个bean并打印,我们能发现Java自动帮我们根据参数的类型和顺序找到了对应的构造器。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application-context.xml");
User user1 = (User) applicationContext.getBean("user1");
User user2 = (User) applicationContext.getBean("user2");
System.out.println(user1);
System.out.println(user2);
将上面例子的User类写成下面这样,建立属性的getter和setter方法。
package org.company.think.in.spring.ioc.overview.domain;
public class User {
private Long id;
private String name;
//省略getter和setter
}
对于setter注入,我们使用
<bean id="role" class="com.ssm.chapter9.pojo.Role">
<property name="id" value="1"/>
<property name="name" value="用户1"/>
</bean>
上述的例子中我们赋值的属性都是基本类型,比如Long,Integer,String。但是如果属性的类型是引用型或者集合,那就要使用其他方式了。
如果User的定义如下
package org.company.think.in.spring.ioc.overview.domain;
public class User {
private Long id;
private String name;
private Address address;
//省略getter和setter
}
其中的Address是一个类。那这个属性的赋值就不能像之前一样"value = ···",我们要先保证容器中已有一个类型是Address的Bean,然后使用"ref = ···",其中ref是指类型是Address的Bean在容器中的id。
假设容器中已有一个类型是Address的Bean,id是address。引用型赋值的示例如下。
<bean id="role" class="com.ssm.chapter9.pojo.Role">
<property name="id" value="1"/>
<property name="name" value="总经理"/>
<property name="Address" ref="address"/>
</bean>
如果属性的类型是集合,那要分好几种情况,比如Map,List。类型不同赋值方式也不同。
List类型的话,我们使用标签代替value或者ref。在
下则正常使用value或ref对元素赋值。
<bean id="id" class="全限定性类名">
<property name="list">
<list>
<value>"1"</value>
<ref bean="myDataSource"/>
</list>
</property>
</bean>
Set类型的话,我们使用
<bean id="id" class="全限定性类名">
<property name="list">
<set>
<value>"1"</value>
<ref bean="myDataSource"/>
</set>
</property>
</bean>
Map类型我们使用
<bean id="id" class="全限定性类名">
<property name="map">
<map>
<!--key和value是基本类型-->
<entry key="0" value="value1"/>
<!--key和value是引用类型-->
<entry key-ref="1" value-ref="class1"/>
</map>
</property>
</bean>
假设User的定义如下
package org.company.think.in.spring.ioc.overview.domain;
public class User {
private Long id;
private String name;
private Map<Integer, String> map;
//省略getter和setter
}
开始装配User。
<bean id="user1" class="org.company.think.in.spring.ioc.overview.domain.User">
<property name="map">
<map>
<entry key="0" value="map1"/>
<entry key="1" value="map2"/>
</map>
</property>
</bean>
然后初始化容器并输出User的map。
BeanFactory beanFactory = new ClassPathXmlApplicationContext("META-INF/test.xml");
User user1 = (User) beanFactory.getBean("user1");
System.out.println(user1.getMap());
所谓的命名空间其实就是上面提到的构造器注入、setter注入,只是语法上更为简单简洁。
c命名空间就是构造器注入,我个人是将c看成contructor的简称。
要使用c命名空间,要先引入一些东西。我们在spring官方文档中可以直接搜索『 c: 』,直接复制过来。
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#spring-core
c命名空间是
假设User类的定义如下
package org.company.think.in.spring.ioc.overview.domain;
public class User {
private Long id;
private String name;
public User(Long id, String name) {
this.id = id;
this.name = name;
}
}
使用c命名空间指定参数名称的方法如下
<!-- 如果name的类型是引用,则要写成c:name-ref="引用名称"-->
<bean id="user2" class="org.company.think.in.spring.ioc.overview.domain.User"
c:name="user1" c:id="1"/>
这样写就简介得多了。我们初始化容器后输出user2看看结果。
BeanFactory beanFactory = new ClassPathXmlApplicationContext("META-INF/test.xml");
User user2 = (User) beanFactory.getBean("user2");
System.out.println(user2);
我们也可以指定参数顺序,还是上面的例子。
<!-- 同样的,如果参数的类型是引用,则要写成c:_0-ref="引用名称"-->
<bean id="user2" class="org.company.think.in.spring.ioc.overview.domain.User"
c:_0="1" c:_1="user"/>
p命名空间就是setter注入。基本的setter注入的写法中使用
setter注入没有所谓的顺序,所以p命名空间只能指定成员属性名,用法与c命名空间相似。
当然,使用之前也要先引入p命名空间,在官方文档中搜索搜索『 p: 』,然后复制相关引用到自己的XML中。
使用方式如下。
<bean id="user2" class="org.company.think.in.spring.ioc.overview.domain.User"
p:name="user2" p:id="1"/>
上面的c命名空间和p命名空间都是只能对基本类型和引用进行赋值。集合类型就得使用util命名空间。
同样的,util命名空间使用之前要先引入,我们在官方文档中搜索『 util: 』,复制相关引用。
其实util命名空间和原来的写法相比并没有简介到哪里去,util命名空间还有其他用途,不过这里不过多介绍了。
下面看看使用方法。假设User的定义如下。
package org.company.think.in.spring.ioc.overview.domain;
import java.util.Map;
public class User {
private Long id;
private String name;
private Map<User, String> map;
//省略setter和getter
}
我们使用util命名空间对其中的map赋值。
<bean id="user1" class="org.company.think.in.spring.ioc.overview.domain.User">
<property name="map">
<util:map id="map">
<entry key="0" value="map1"/>
<entry key="1" value="map2"/>
<entry key="2" value="map3"/>
</util:map>
</property>
</bean>
对于list和set,就替换成util:list和util:set即可。
util还有一些子属性可以了解
在util:map中,我们可以使用map-class属性指定map的具体类型。比如下面的例子,我们指定map的类型是Hashmap。
<bean id="user1" class="org.company.think.in.spring.ioc.overview.domain.User">
<property name="map" map-class="java.util.HashMap">
<util:map id="map">
<entry key="0" value="map1"/>
<entry key="1" value="map2"/>
<entry key="2" value="map3"/>
</util:map>
</property>
</bean>
同时也可以使用key-type和value-type指定key和value的类型。List和Set也有类似的属性。
我们可以在类中定义一个静态方法,将该方法的返回对象作为Bean。
假设User的定义如下
package org.company.think.in.spring.ioc.overview.domain;
public class User {
private Long id;
private String name;
public static User createUser() {
User user = new User();
user.setId(1L);
user.setName("user1");
return user;
}
//省略setter和getter
}
类中定义了一个静态方法,返回一个User对象。在XML中我们可以获取这个静态方法的返回对象作为Bean。
XML中可以使用
<bean id="user" class="org.company.think.in.spring.ioc.overview.domain.User"
factory-method="createUser"/>
我们可以指定一个类,然后调用该类的一个实例方法,并将该方法的返回值作为Bean。
User类定义如以往一样,成员变量是id和name。我们定义一个新的类,其中有一个实例方法返回值为User。
public class DefaultUserFactory implements UserFactory{
public User createUser() {
User user = new User();
user.setId(1L);
user.setName("user");
return user;
}
}
之后我们在XML声明DefaultUserFactory的一个Bean,然后利用这个Bean的实例方法实例化User类型的Bean。
<!-- 先要有工厂类的Bean-->
<bean id="userFactory" class="org.company.think.in.spring.bean.factory.DefaultUserFactory"/>
<!-- factory-bean指定工厂类,factory-method指定工厂的生产方法-->
<bean id="user-by-instance-method" factory-bean="userFactory" factory-method="createUser"/>
之后生成容器输出user-by-instance-method的bean信息。
BeanFactory beanFactory = new ClassPathXmlApplicationContext("META-INF/bean-creation-context.xml");
User user = beanFactory.getBean("user-by-instance-method", User.class);
System.out.println(user);
我们可以借助FactoryBean完成Bean的实例化。
定义一个类,使其实现FactoryBean接口,复写其中的getObject()和getObjectType()两个方法(其实还有一个isSingleton(),但是这个方法已经默认实现了,默认是单例,一般不用我们操心)。
其中,getObject()定义的是Bean的具体信息,返回值就是Bean。getObjectType()的返回值则是Bean的类型。
之后在XML中定义这个实现FactoryBean的类即可
例子如下
package org.company.think.in.spring.bean.factory;
import org.company.think.in.spring.ioc.overview.domain.User;
import org.springframework.beans.factory.FactoryBean;
public class UserFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
User user = new User();
user.setId(1L);
user.setName("user");
return user;
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}
XML中的定义如下
<bean id="user-by-factory-bean" class="org.company.think.in.spring.bean.factory.UserFactoryBean"/>
我们就可以通过getBean(“user-by-factory-bean”)得到User类的bean。
使用注解是一个更为简洁的方式,它是直接在类加注解,比起xml方式更为直观。
编写注解装配一般要经历两个过程,一是在目标类上加入相关注解;二是指定扫描类,扫描类中会指定范围,该范围内被标注的类才会被装配成Bean。
我们可以使用@Component标注类,表明这个类需要被装配成一个Bean。同时可以直接使用@Value对属性进行赋值。
示例如下
package org.company.think.in.spring.ioc.overview.domain;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class User {
@Value("1")
private Long id;
@Value("user1")
private String name;
}
这样一个Bean就标注好了,之后是需要定义扫描类,不过在这里要多讲一些关于@Component的事。
Component有一个属性value可以设置,它是表示该类被扫描成Bean以后,在容器中的名字,相当于是XML中的id。如果value没有设置,系统会默认该类在容器中的id为首字母小写后的类名。
为了更贴合业务,Sping还提供了@Controller,@Service和@Repository来标注控制层,服务层和持久层三层架构的Bean。似乎在目前的Spring框架中,这三个注解与@Component没有太多区别,但是文档还是建议不同层的的Bean使用对应的注解。
一方面在语义上能让人们更好区分被标注的Bean属于哪一层;另一方面在以后的版本(5.2.7以后)中,很可能会对这些不同层的注解专门实现一些定制化的需求。所以强烈建议不同层的Bean使用对应层的注解标注。
注解标注类以后,还需要定义扫描类。我们使用@ComponnetScan进行扫描。
首先我们先定义一个类,这个类可以是一个空类。然后我们在这个类上添加@ComponentScan注解
package org.company.think.in.spring.ioc.overview.domain;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class PojoConfig {
}
这个类很简单,但是有一点是要注意的。
注意第一行代码,这个类的包名必须和需要装配的类的包名是一样的。因为@ComponentScan扫描默认是扫描当前包的路径,所以包名必须和需要装配的类相同。
从这里就可以理解为什么需要装配的类分散在不同的包时,使用注解不容易管理,需要使用XML的原因。
@ComponentScan的属性有两个,这两个都是用来指定扫描范围的。
这个属性是用来设置扫描的包名。扫描范围是改包以及改包的子包下的所有类。要注意到basePackages是复数,所以我们可以指定多个包进行扫描。
package org.company.think.in.spring.ioc.overview.domain;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackages = {"org.company.think.in.spring.ioc.overview.domain", "org.company.think.in.spring.ioc.overview.dto")}
public class PojoConfig{
}
这段代码代表着Spring IoC会扫描包org.company.think.in.spring.ioc.overview.domain和org.company.think.in.spring.ioc.overview.dto下所有的类(包括它们子包的所有类)。
该属性指定的是,容器只对该属性指定的类,以及该类的子类进行进行扫描,如果这个类被注解标注要成为Bean,那该类就会被变成Bean放到容器中维护。
package org.company.think.in.spring.ioc.overview.domain;
import org.springframework.context.annotation.ComponentScan;
//Role和RoleImpl是定义好的类或接口
@ComponentScan(basePackageClasses = {User.class, Address.class})
public class PojoConfig{
}
使用@Componnet注解时,对于成员变量的赋值我们是使用@Value注解。但是@Value只能完成一般类型的赋值,对于引用类型的赋值,就要使用到@Autowired。
@Component
public class User {
@Value("1")
private Long id;
@Value("user1")
private String name;
@Autowired
private Address address;
}
我们使用@Autowired后,系统会在容器中已注册的Bean中去寻找与@Autowired标注类型一致的Bean,然后将这个Bean赋值给@Autowired标注的成员变量。
@Autowired只有一个属性,required。该属性只能赋值true或false。true表示如果容器中没有找到想对应类型的Bean,那就报错,@Autowired标注的该成员变量必须指向一个实例;false表示即使没有找到相对应类型的Bean也不会报错。
@Autowired注解方法多半用于setter方法上,当然其他方法也可以,只要@Autowired标注方法的参数类型是引用即可。对于@Autowired标注的方法,这个方法的类在实例化成Bean时,会自动执行方法(即使方法不是构造方法,也不是静态方法),并且系统会在容器中寻找和方法参数类型相同的Bean传值到方法中执行。
下面是使用的例子。
@Component
public class User {
@Value("1")
private Long id;
@Value("user1")
private String name;
private Address address;
@Autowired
public void setter(Address address) {
this.address = address;
}
}
@Autowired是根据类型赋值,但如果容器中符合条件的Bean不止一个的话,计算机会糊涂,会不知道应该使用哪个Bean进行赋值。
在这种情况下,我们可以使用@Primary和@Qualifier两个注解来解决这个问题
被@Primary标注的类在@Autowired自动装配中具有优先权。比如有两个类继承同一个接口,这时候容器类就有两个RoleService类
@Component
@Primary
public class RoleService1 implements RoleService{
@Override
public void RoleService(Role role) {
System.out.println(role.sword + "1");
}
}
@Component
public class RoleService2 implements RoleService{
@Override
public void RoleService(Role role) {
System.out.println(role.sword + "2");
}
}
当需要自动装配RoleService时,因为RoleService1标注了@Primary,所以系统会有限选择RoleService1进行装配。
要注意,@Primary可以同时标注多个Bean的。上述例子中RoleService1标注@Primary,那RoleService2也可以同时标注@Primary。这时候RoleService1和RoleService2就具有同样的优先权,歧义性依然存在,系统依然会报错。
@Primary只是解决了首要性的问题,容器依然是根据类型来自动装配。而@Qualifier是根据名称查找来进行装配,完美地消除了歧义性。
@Qualifier紧跟着@Autowired标注,并且指定名称,指定Bean在容器中的名称,指定要这个Bean,这样就不会再有歧义性的问题。使用方式如下
public class User {
@Value("1")
private Long id;
@Value("user1")
private String name;
@Autowired
@Qualifier("address")
private Address address;
}
对于Java而言,很多时候都需要引入第三方的包(jar文件),并且这些包都没有源码,或者源码是只读状态无法修改。这时候就无法使用注解将第三方的类变为开发环境的Bean。
这时候可以使用@Bean注解来解决这个问题。@Bean可以注解到方法上,并且将标注方法的返回值作为Bean放到容器中。比如我们要使用DBCP数据源,这时候就可以使用@Bean标注来装配数据源的Bean
@Bean
public DataSource getDataSource() {
Properites props = new Properties();
props.setProperty("drivers", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/mysql");
props.setProperty("username", "root");
porps.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDateSource(props);
} catch(Exception e) {
e.printStackTrace();
}
return dataSource;
}
在使用@Bean注解方法的时候,要保证被注解方法的类被相关注解标注了。当扫描类扫描到这个类时,要让系统知道这个类需要被扫描。如果这个类没有被任何注解标注,只是一个普通的类,那系统扫描到这个类时,会认为这个类不需要被装配,那么其中被@Bean标注的方法也会被略过。@Bean就没有起到任何作用。
比如下面这个例子
public class Data {
@Bean(name = "sword1")
public Sword getSword(){
Sword sword = new Sword();
sword.setDamage(10);
sword.setLength(10);
return sword;
}
}
容器扫描到Data类时会直接略过,最终容器中不会有一个name为sword1的Bean。
要想要@Bean起作用,就可以用下面两种方式。
//PojoConfig.class是扫描类
ApplicationContext ac = new AnnotationConfigApplicationContext(PojoConfig.class, Data.class);
有时候我们已经有了一些容器资源来源,比如已经写好的XML文件,或者被@ComponentScan标注的定义好的扫描类,我们想在这些资源上再加上一些Bean的定义组成新的容器,同时又不想再重复定义这些容器,那我们就可以通过引入的方式。
对于XML而言,我们就只能引入XML文件资源,在beans标签下使用import标签,例子如下
<?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/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<import resource="dependency-lookup-context.xml"/>
<bean id="userRepository" class="org.company.think.in.spring.ioc.overview.repository.UserRepository">
<property name="users">
<util:list>
<ref bean="superUser"/>
<ref bean="user"/>
</util:list>
</property>
</bean>
</beans>
对于通过注解生成的容器,我们可以引入其他注解的资源,也可以引入XML定义的资源。
使用@ImportResource即可。在扫描类中加入@ImportResource,该注解的属性就是XML的项目路径。加上这个注解之后,该注解中指定的XML文件中定义的Bean就会被引入到由该扫描类生成的容器中。
使用例子如下
@ComponentScan
@ImportResource({"bean1.xml", "bean2.xml"})
public class PojoConfig {
}
通过这样通过该扫描类生成的容器中,也会存在这bean1.xml和bean2.xml中定义的Bean。
和@ImportResource一样,在相同的位置使用@Import即可。@Import中的属性是其他的扫描类
@ComponentScan
@Import({ApplicationConfig2.class, ApplicationConfig3.class})
public class PojoConfig {
}
@Import和@ImportResource是可以同时使用的
引入资源时,很有可能会对同一个类对此扫描 ,或者XML中多次定义,这时就涉及到一个类被多次定义或者多次扫描时,容器中存在关于这个类的多个Bean还是一个Bean。
对于XML而言,无论重复与否,定义了多少次就产生多少个Bean,这点比较简单。
对于注解而言,同一个扫描类多次扫描了一个类,那关于这个类只会生成一个Bean;如果是多个扫描类扫描到了同一个类,那每个扫描类会分别产生一个关于这个类的Bean。