swiftR

ioc配置

ioc容器是spring的核心之一,那么什么是ioc呢,在传统的程序开发中,需要获取对象时,通常由开发者来手动创建实例化对象,但是在 Spring 框架中创建对象的工作不再由开发者完成,而是交给 IoC 容器来创建,我们直接获取即可,整个流程完成反转,因此是控制反转。

ApplicationContext接口代表Spring IoC容器,并负责实例化,配置和组装Bean

那么什么是Bean呢,可以理解为一个对象就是一个Bean,那么spring是怎么知道有这个bean的呢,那就需要我们去配置matedata,之前写的xml就可以代表matedata,传统开发中都是使用xml来定义matadata的格式,现在开发中可以使用少量XML配置来声明性地启用对这些其他元数据格式的支持,从而指示容器将Java注释或代码用作元数据格式

以下是springbean的基本配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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">

<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>

<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>

<!-- more bean definitions go here -->

</beans>

用过spirng的应该不会陌生

1
2
该id属性是标识单个bean定义的字符串。
该class属性定义Bean的类型,并使用完全限定的类名。

当然如果要给bean赋值的话,可以使用如下的标签,对应的是一些java的基本对象和一些引用关系

1
2
3
4
5
6
7
8
9
10

<xsd:element ref="ref"/>
<xsd:element ref="idref"/>
<xsd:element ref="value"/>
<xsd:element ref="null"/>
<xsd:element ref="array"/>
<xsd:element ref="list"/>
<xsd:element ref="set"/>
<xsd:element ref="map"/>
<xsd:element ref="props"/>

下面通过一个例子来开启spring吧

xml配置

首先是定义了一个Stu的对象,当然这就是一个javabean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Stu {

private Integer id;
private String name;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "Stu{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}

然后是写xml了,xml写起来比较繁琐,后面会有别的替代方案

1
2
3
4
<bean id="stu" class="cn.lzl.Stu" >
<property name="id" value="44"></property>
<property name="name" value="swiftr"/>
</bean>

当然还有比较简单的方法,使用p命名标签

1
xmlns:p="http://www.springframework.org/schema/p

直接给bean赋值

1
2
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="someone@somewhere.com"/>

下面就开始创建spring容器吧,创建spring容器的方法比较多
图片

这里使用的是ClassPathXmlApplicationContext()来创建上下文容器,然后通过上面的id来获取Stu实例

1
2
3
ApplicationContext context = new ClassPathXmlApplicationContext("server.xml");
Stu stu = context.getBean("stu",Stu.class);
System.out.println(stu);

这里ClassPathXmlApplicationContext(),通过传入classpath下的server.xml作为参数,但如果项目过于庞大的话,全写在一个xml中并不好管理,spring提供了一个重载方法来解决这个问题

1
2
3
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}

直接传入多个xml即可

当然这里还有一个解决办法,通过import导入别的xml文件

1
2
3
4
5
6
7
8
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>

<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>

spring 中的继承

指定parent属性即可,这里的两个bean是来自于同一个类的

Spring 的依赖

依赖也是 bean 和 bean 之间的一种关联方式,配置依赖关系后,被依赖的 bean 一定先创建,再创建依赖的 bean,通过属性 depends-on来指定依赖的bean

Spring 读取外部资源

  • 创建 jdbc.properties

    1
    2
    3
    4
    driverName = com.mysql.jdbc.Driver
    url = jdbc:mysql://localhost:3306/myTest?useUnicode=true&characterEncoding=UTF-8
    user = root
    pwd = root
  • spring.xml 中配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- 导入外部的资源文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <!-- 创建 C3P0 数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="user" value="${user}"></property>
    <property name="password" value="${pwd}"></property>
    <property name="driverClass" value="${driverName}"></property>
    <property name="jdbcUrl" value="${url}"></property>
    </bean>

bean的定义

bean的别名

如果不想使用id来获取一个bean,那么可以试试给bean定义一个别名,使用alias标签

1
2
3
4
5
6
<bean id="stu" class="cn.lzl.Stu" >

<property name="id" value="44"></property>
<property name="name" value="swiftr"/>
</bean>
<alias name="stu" alias="test"/>

bean的实例化

  • 使用构造函数实例化bean
    这里还必须引入依赖注入(Dependency Injection)的概念,简称DI,通过这个过程,对象仅提供构造所需要的参数就可以实例化一个bean,而不用通过new来创建,也解决了bean复杂的依赖关系,降低了程序的耦合性,因此这个过程还要控制反转(Control Inversion)的说法

1.基于构造函数的依赖注入

这个也比较简单,在bean写好构造函数,然后在xml中配置,通过constructor-arg来赋值,也可以使用type=”int”来显示指定属性的类型,当然你可能不知道index是干什么的,index属性用来指定参数的序号,当然也可以使用name=来代替,使用name是记得在构造函数加上 @ConstructorProperties({“id”,”name”})注解

1
2
3
<bean id="stu" class="cn.lzl.Stu" >
<constructor-arg index="0" value="123"/>
<constructor-arg index="1" value="swiftr"/>

2.基于Setter的依赖注入

这种方法之前的实例已经使用过了

这里说下@Required这个注解,在set方法上使用,表示这个方法是必须的,也就是该属性必须赋值,否则就会报错,但比较新的spring版本不建议这么做,这个看需求吧。setter注入的一个好处是,setter方法使该类的对象在以后可以重新配置或重新注入。

下面说一下seter注入的过程吧

  • 通过setValue方法给bean赋值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    public void setValue(final @Nullable Object value) throws Exception {
    final Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ?
    ((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() :
    this.pd.getWriteMethod());
    if (System.getSecurityManager() != null) {
    AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
    ReflectionUtils.makeAccessible(writeMethod);
    return null;
    });
    try {
    AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () ->
    writeMethod.invoke(getWrappedInstance(), value), acc);
    }
    catch (PrivilegedActionException ex) {
    throw ex.getException();
    }
    }
    else {
    ReflectionUtils.makeAccessible(writeMethod);
    writeMethod.invoke(getWrappedInstance(), value);
    }
    }

先看this.pd这个内部属性

1
private final PropertyDescriptor pd;org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=id]

从字面上的解释是一个属性描述器,他有个子类GenericTypeAwarePropertyDescriptor

1
2
3
4
5
6
7
8
9
10
11
12
13
private final Class<?> beanClass;

@Nullable
private final Method readMethod;

@Nullable
private final Method writeMethod;

@Nullable
private volatile Set<Method> ambiguousWriteMethods;

@Nullable
private MethodParameter writeMethodParameter;

里面也包含了一些和方法有关的属性

接着往下看,拿到method之后System.getSecurityManager() ,这是java的安全管理器,
最后获取实例并进行赋值操作

1
writeMethod.invoke(getWrappedInstance(), value);
  • 用静态工厂方法实例化
    1
    2
    3
    <bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>
1
2
3
4
5
6
7
8
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}

public static ClientService createInstance() {
return clientService;
}
}
  • 使用实例工厂方法实例化
    1
    2
    3
    <bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>
1
2
3
4
5
6
7
8
public class DefaultServiceLocator {

private static ClientService clientService = new ClientServiceImpl();

public ClientService createClientServiceInstance() {
return clientService;
}
}

bean的懒加载 Lazy-initialized Beans

默认情况下,spring容器加载过程中会初始化所有的单例bean,如果不希望出现这种情况,可以在xml配置lazy-init=”true”

1
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>

也可以在beans标签中加入,控制上级容器

1
2
3
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>

自动装配

自动装配是spring中比较好用的功能,可以很好的解决bean之间的依赖关系

自动装配的模式

模式 info
no 默认为no,可以通过ref来指定bean的id
byName spring自动寻找与属性名相同的bean
byType spring自动寻找与属性类型相同的bean
constructor 与byType相似,不过使用的是构造方法

通过加入来 autowire=”byType”
如果要在自动装配中排除bean可以加入autowire-candidate=”false”

spring方法注入

spring中的bean默认都是以单例形式存在的,但如果不想使用重复的呢,这里可以使用spring的方法注入

继承ApplicationContextAware

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FUn implements ApplicationContextAware {
private ApplicationContext applicationContext;


public Stu getstu(){
Stu stu = this.applicationContext.getBean("stu", Stu.class);
System.out.println(stu);
stu.setName("lzl");
return stu;
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

xml中配置

1
2
3
<bean id="stu2" class="cn.lzl.FUn">

</bean>

最后main方法

1
2
3
4
Stu stu = context.getBean("stu",Stu.class);
System.out.println(stu);
FUn stu1 = context.getBean("stu2",FUn.class);
System.out.println(stu1.getstu().getName());

运行结果

1
2
3
Stu{id=44, name='swiftr'}
Stu{id=44, name='swiftr'}
lzl

将xml改为

1
2
3
4
5
6
<bean id="stulzl" class="cn.lzl.Stu" scope="prototype">
</bean>

<bean id="stu2" class="cn.lzl.FUn">
<lookup-method name="getstu" bean="stulzl"></lookup-method>
</bean>

通过 lookup-method 元素标签为 FUn 的 getstu()提供动态实现,返回 prototype 类型的 car Bean,这样 Spring 将在运行期为 MagicBoss 接口提供动态实现,由于stulzl的scope为prototype,所以得到的Stu实例都是不同的引用,hashcode都是不同的

bean的scope

  • singleton 单例类
    spring中的bean默认就是singleton,每次从spring容器中根据id获取bean时,都会返回一个相同的引用
  • prototype 原型类
    与singleton相反,都会返回不同的引用
  • request,请求,表示在一次 HTTP 请求内有效;
  • session,会话,表示在一个用户会话内有效。

bean的生命周期

  • 初始化回调
    如果使用的是注解,在要调用的方法上加上@PostConstruct注解,如果是xml的话加上init-method属性即可,获取实现InitializingBean接口中的afterPropertiesSet方法

    1
    2
    3
    4
    5
    6
    7
    public class AnotherExampleBean implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
    // do some initialization work
    }
    }
  • 销毁回调
    当bean容器被销毁时执行的方法,与初始化一样同样是三种方法

xml加上destroy-method属性,注解加上@PreDestroy,

获取实现org.springframework.beans.factory.DisposableBean接口

  • 默认回调方法
    xml中配置

  • spring容器的关闭
    之前定义的销毁回调并没有执行,是应为spring容器并没有正常的关闭,这里调用registerShutdownHook()即可

    1
    ctx.registerShutdownHook();

BeanPostProcessor接口

BeanPostProcessor是spring中比较重要的接口,通过他可以实现自定义bean,
postProcessBeforeInitialization方法代表的是初始化前,postProcessAfterInitialization代表的是初始化后的操作,
写个例子看看吧

1
2
3
4
5
6
<bean id="stu" class="cn.lzl.Stu" init-method="init" destroy-method="init">
<constructor-arg name="id" value="123"/>
<constructor-arg name="name" value="swiftr"/>
<!--<property name="id" value="44"></property>-->
<!--<property name="name" value="swiftr"/>-->
</bean>

这是在xml配置的一个bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 public class BeanPostProcessorTest implements BeanPostProcessor {

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("before start");
bean = new Stu(23,"test");
System.out.println("before end");

System.out.println(bean);
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("after start");
bean = new Stu(23,"test2");
System.out.println(bean);
System.out.println("agter end");
return bean;
}
}

通过过BeanPostProcessor对初始化的bean进行修改

1
<bean class="cn.lzl.BeanPostProcessorTest"/>

将BeanPostProcessorTest配置到spring环境中,因为它是bean,所以可以像其他任何bean一样依赖注入

org.springframework.beans.factory.FactoryBean接口

Spring通过反射机制利用bean的class属性指定实现类来实例化bean 。在某些情况下,实例化bean过程比较复杂,如果按照传统的方式,则需要在中提供大量的配置信息,配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.Springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化bean的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class CarFactoryBean implements FactoryBean<Car> { 
private String carInfo;
public Car getObject() throws Exception {
Car car = new Car();
String[] infos = carInfo.split(",");
car.setBrand(infos[0]);
car.setMaxSpeed(Integer.valueOf(infos[1]));
car.setPrice(Double.valueOf(infos[2])); return car;
}
public Class<Car> getObjectType() {
return Car.class;
}
public boolean isSingleton() {
return false;
}
}

xml配置

1
<bean id="car" class="com.test.factorybean.CarFactoryBean" carInfo="大众SUV,180,180000"/>

注解

  • @Autowired对于那些众所周知的解析依赖接口:BeanFactory,ApplicationContext,Environment,ResourceLoader, ApplicationEventPublisher,和MessageSource。这些接口及其扩展接口(例如ConfigurableApplicationContext或ResourcePatternResolver)将自动解析,而无需进行特殊设置。以下示例自动装配ApplicationContext对象

  • @Primary
    表示优先级,当多个bean需要自动装配时,谁优先就加入@Primary

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Configuration
    public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
    }
  • @Qualifier

  • @Value()
    通常被用来注入外部属性

    1
    2
    3
    public MovieRecommender(@Value("${catalog.name}") String catalog) {
    this.catalog = catalog;
    }

写个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
public class Student {

@Value("${stu.name}")
private String name;

public Student(@Value("${stu.name}")String name){
this.name = name;
}

public Student() {

}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
@PropertySource("classpath:application.properties")
@ComponentScan("cn.lzl")
public class AppConfig {

@Bean
public static Student student(){
return new Student(null);
}
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}

}
1
2
3
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
Student student = applicationContext.getBean("student",Student.class);
System.out.println(student);

基于java代码的配置

这里主要使用两个注解@Configuration和@Bean

一个简单的例子

1
2
3
4
5
6
7
8
@Configuration
class AppConfig{

@Bean
public Server server(){
return new ServerImp();
}
}

这里相当于xml中的

1
<bean id="server" class="ServerImp"/>

与xml的创建方式不同,这里使用AnnotationConfigApplicationContext来创建一个spring容器

1
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

那么如何使用@Autowired注解来进行注入呢,可以直接将bean放入构造方法中

1
2
ApplicationContext context = new AnnotationConfigApplicationContext(UserService.class, UserServiceImpl.class, ServiceTest.class);
context.getBean(ServiceTest.class).test();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
public class ServiceTest {

@Autowired()
private UserService userService;

public void test(){
userService.getuser();
}
}

public interface UserService {

public void getuser();
}

@Service("userService")
public class UserServiceImpl implements UserService {
@Override
public void getuser() {
System.out.println("getuser");
}
}

除了使用构造方法外,还可以使用register来注册bean

1
2
3
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();

当然上面的方法都比较繁琐,这里可以使用组件扫描的注解来构建

1
2
3
4
5
@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig {
...
}

相当于xml中的

1
2
3
<beans>
<context:component-scan base-package="com.acme"/>
</beans>

当然注解中是可以配置bean的name和生命周期方法的

upload successful

或者在返回bean时执行init方法

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class AppConfig {

@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}

// ...
}

bean的作用域

使用@Scope注解即可

1
2
3
4
* @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
* @see ConfigurableBeanFactory#SCOPE_SINGLETON
* @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
* @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION

bean的别名

1
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})

块化配置

与xml一样,java的注解配置也可以进行模块化配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class ConfigA {

@Bean
public A a() {
return new A();
}
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

@Bean
public B b() {
return new B();
}
}

java注解配置类中使用xml

@ImportResource(“classpath:/com/acme/properties-config.xml”)

spring环境配置

使用spring开发时,可能会有生产环境和开发不一致的情况
这里可以加入@Profile注解来解决这个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
public class AppConfig {

@Bean("dataSource")
@Profile("development")
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}

@Bean("dataSource")
@Profile("production")
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}

当然可以用Profile做元注解也是ok的,

如果是xml的话可以在beans的属性中加入

1
<beans profile="development"

最后启动容器

1
2
3
4
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();

当然这可以不用通过编程试的方法来激活这个特性,

1
-Dspring.profiles.active =“ profile1,profile2”

如果是默认配置文件,可以这样

1
@Profile("default")

引入环境配置

在spring环境中是可以加载配置文件的,那么怎么来读配置文件呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {

@Autowired
Environment env;

@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}