介绍springIOC的时候还顺便介绍了spring。介绍的有点乱,没有介绍到的地方还是需要到官网看看。
Spring特征
Spring不只有IOC和AOP。
-
[核心技术](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html):依赖项注入,事件,资源,i18n,验证,数据绑定,类型转换,SpEL,AOP。
-
[测试](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/testing.html):模拟对象,TestContext框架,Spring MVC测试
-
[数据访问](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/data-access.html):事务,DAO支持,JDBC,ORM,封送XML。
-
[Spring MVC](https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html)和[Spring WebFlux](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html)Web框架。
-
[集成](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/integration.html):远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
-
[语言](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/languages.html):Kotlin,Groovy,动态语言。
SpringIOC文档
文档地址 我没有一次性的找到springIOC的文档地址,就做个说明。
什么是IOC、DI
控制反转(Inversion of Control,缩写为IOC),是面向对象编程中的一种设计原则,可以降低代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)。简单来说,就是由IOC容器控制程序之间的关系,而非传统中有代码直接操控。控制权由应用代码转移到外部容器,控制权的转移,就是所谓的反转。依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
IOC是一种设计模式,是一种思想,相当于一个容器,而DI就好比是实现IOC的一种方式
Spring实现IOC
spring实现IOC的思路是提供一些配置信息用来描述类之间的依赖关系,然后由容器去解析这些配置信息,继而维护好对象之间的依赖关系,
- 应用程序中提供类,提供依赖关系(属性或者构造方法)例如A.class中有一个B.class的属性,那么我们可以理解为A依赖了B
- 把需要交给容器管理的对象通过配置信息告诉容器(xml、annotation,javaconfig)
- 把各个类之间的依赖关系通过配置信息告诉容器,此过程称为自动注入
配置信息方式
-
schemal-based--------基于Xml配置
-
annotation-based-----基于注解配置
-
java-based-------------基于java的配置
会在基于构造器注入详细介绍
依赖注入DI
注入的两种方式
基于构造函数的依赖注入和基于Setter的依赖注入。(接口注入。在spring4被取消了)
基于构造函数的依赖注入
通过容器调用具有多个参数(每个参数代表一个依赖项)的构造函数来完成的。
XML定义
UserService
public class UserService {
UserDao userDao;
public void getUser() {
userDao.getUser();
}
// 提供一个构造函数,spring容器注入userDao
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
UserDaoImpl
public class UserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println(new User(1, "张三").toString());
}
}
ApplicationContext.xml
<bean id="userDaoImpl" class="com.zjy.dao.UserDaoImpl"></bean>
<bean id="userService" class="com.zjy.dao.UserService">
<constructor-arg ref="userDaoImpl"></constructor-arg>
</bean>
测试
通过classpath下的xml文件来初始化spring。使用来ClassPathXmlApplicationContext加载application.xml
// 结果:User(id=1, username=张三)
public static void main(String[] args) {
ClassPathXmlApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) ac.getBean("userService");
userService.getUser();
}
基于注解
引入context命名空间
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
开启注解,开启包扫描
<context:annotation-config></context:annotation-config>
<context:component-scan base-package="com"></context:component-scan>
也可以写为
<context:component-scan base-package="com"></context:component-scan>
UserService
@Service
public class UserService {
@Autowired
UserDao userDao;
public void getUser() {
userDao.getUser();
}
}
UserDaoImpl
@Component
public class UserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println(new User(1, "张三").toString());
}
}
测试
public static void main(String[] args) {
ClassPathXmlApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) ac.getBean("userService");
userService.getUser();
}
基于java的配置
注解还需要依赖xml配置。而java Configuration取消了xml文件。使用@Configuration标记为xml文件,@ComponentScan扫描路径,还可以使用@ImportResource("classpath:applicationContext.xml")引入xml文件实现三者同时使用
ApplicationContext.java
@Configuration //标记为spring的xml文件
@ComponentScan("com.zjy")
public class ApplicationContext {}
测试
使用来AnnotationConfigApplicationContext加载ApplicationContext.java配置类
public static void main(String[] args) {
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(ApplicationContext.class);
UserService userService = (UserService) ac.getBean("userService");
userService.getUser();
}
基于setter的依赖注入
由于基于构造器已经介绍了三种配置方式,这里就不过多介绍。
UserService
public class UserService {
UserDao userDao;
public void getUser() {
userDao.getUser();
}
// 提供setter方法,spring容器进行注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
UserDao
public class UserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println(new User(1, "张三").toString());
}
}
applicationContext.xml
<bean id="userDaoImpl" class="com.zjy.dao.UserDaoImpl"></bean>
<bean id="userService" class="com.zjy.dao.UserService">
<property name="userDao" ref="userDaoImpl"></property>
</bean>
测试
通过classpath下的xml文件来初始化spring。使用来ClassPathXmlApplicationContext加载applicationContext.xml
// 结果: User(id=1, username=张三)
public static void main(String[] args) {
ClassPathXmlApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) ac.getBean("userService");
userService.getUser();
}
注入的详细配置
- 属性
<property/>
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
</bean>
-
其他bean
<ref bean="someBean"/> -
内置bean
bean元素中内置了一个<bean>标签
<bean id="outer" class="...">
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
- 集合
spring中的<list/>、<set/>、<map/>、<props>分别对应Collection中的List、Set、Map、Properties
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
- Null或空字符串
<null/>
value值可以为“” 或
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
- p-namespace的xml
需要引入xmlns:p="http://www.springframework.org/schema/p"
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="someone@somewhere.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="someone@somewhere.com"/>
- c-namespace的xml
c:名称空间可以简化基于构造函数注入
需要引入xmlns:c="http://www.springframework.org/schema/c"
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
自动装配
IOC的注入有两个地方需要提供依赖关系,一是类的定义中,二是在spring的配置中需要去描述。自动装配则把第二个取消了,我们仅仅需要在类中提供依赖,继而把对象交给容器管理即可完成注入。
- 自动装配可以大大减少指定属性或构造函数参数的需要
- 自动装配可以更新配置。例如,如果需要将依赖项添加到类中,则无需修改配置即可自动满足该依赖项
下表描述了四种自动装配模式
XML方式
- 全局指定 在
<beans> 中添加default-autowire="byType" - 类中指定
<bean id="userDao1" class="com.zjy.dao.UserDaoImpl1" autowire="byType"></bean>
接口中有两个实现类时使用byType会报错
No qualifying bean of type 'com.zjy.dao.UserDao' available: expected single matching bean but found 2: userDao,userDao1
使用ByName注入 中如果没有指定name,默认name等于id
<bean id="userDao" class="com.zjy.dao.UserDaoImpl"></bean>
当然还有其他解决方法,会在下面介绍
注解
@Repository、@Service、@Controller分别是@Component针对特定用例(分别在持久性,服务和表示层)
详细介绍请看官网
懒加载lazy
懒加载lazy,默认情况下,ApplicationContext实现创建和配置所有单例bean作为初始化过程的一部分。当这种行为不可取,可以使用lazy延迟初始化。延迟初始化的bean告诉IoC容器创建一个bean实例是第一次请求时,而不是在启动。
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
作用域
singleton、prototype、request、session、application、websocket。
- singleon:(默认值)将每个Spring IoC容器的单个bean定义范围限定为单个对象实例。
- prototype:将单个bean定义的作用域限定为任意数量的对象实例。
其余几个是spring mvc中使用的,目前不介绍。
单例singleton
UserService
@Service
@Scope("singleton")
public class UserService {
@Autowired
UserDao userDao;
public void getUser() {
userDao.getUser();
}
}
测试
//1147258851 1147258851
public static void main(String[] args) {
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(Spring.class);
UserService userService = (UserService) ac.getBean("userService");
UserService userService1 = (UserService) ac.getBean("userService");
System.out.println(userService.hashCode());
System.out.println(userService1.hashCode());
}
原型prototype
@Scope("prototype")
public class UserService {
@Autowired
UserDao userDao;
public void getUser() {
userDao.getUser();
}
}
结果: 577405636 1931444790
Bean的生命周期不同引发的问题
在单例中引入prototype时没有意义。因为单例只加载一次
@Service
public class UserService {
@Autowired
UserDao userDao;
public void getUser() {
System.out.println("userService" + this.hashCode());
System.out.println("userDao" + userDao.hashCode());
}
}
@Repository
@Scope("prototype")
public class UserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println(new User(1, "张三").toString());
}
}
结果:
userService577405636
userDao1931444790
userService577405636
userDao1931444790
方案1:
@Service
public class UserService implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Autowired
UserDao userDao;
public void getUser() {
System.out.println("userService" + this.hashCode());
System.out.println("userDao" + applicationContext.getBean("userDaoImpl").hashCode());
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
实现ApplicationContextAware接口,每次调用userDao时都需要通过getBean先获取。由于需要实现ApplicationContextAware接口,和spring框架的耦合度高,所以不建议使用
方案2:
Lookup方法注入
@Service
public class UserService{
@Lookup
public UserDao getUserDao() {
return null;
}
public void getUser() {
System.out.println("userService" + this.hashCode());
System.out.println("userDao" + getUserDao().hashCode());
}
}
练习
改变BeanName命名规则
public class BeanName implements BeanNameGenerator {
public BeanName() { }
@Override
public String generateBeanName(BeanDefinition beanDefinition,
BeanDefinitionRegistry beanDefinitionRegistry) {
return beanDefinition.getBeanClassName();
}
}
配置类中使用
@Configuration
@ComponentScan(basePackages = "com.zjy", nameGenerator = BeanName.class)
public class Spring {}
最终还是要以spring官网为准