课程来源
Spring
是一种容器框架。
第一个程序
记得先换 maven 源并且不出现傻叉错误。
还有注意添加 junit5 的时候检查一下模块那部分依赖的 junit 是不是 compile。
src/net/yxchen/bean/People.java
1 2 3 4 5 6 7 8 9 10 11
| package net.yxchen.bean;
public class People { private String name; private String gender; private String email;
public People() { System.out.println("Person 创建了"); }
|
src/applicationContext.xml
1 2 3 4 5 6 7 8 9 10
| <?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="people1" class="net.yxchen.bean.People"> <property name="name" value="poorpool"/> <property name="gender" value="male"/> <property name="email" value="poorpool@yxchen.net"/> </bean> </beans>
|
src/net/yxchen/test/PeopleTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package net.yxchen.test;
import net.yxchen.bean.People; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
class PeopleTest { @Test public void qwq() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); System.out.println("容器创建完毕"); People people = (People) applicationContext.getBean("people1"); People people1 = (People) applicationContext.getBean("people1"); System.out.println(people); System.out.println(people == people1); } }
|
输出结果:
1 2 3 4
| Person 创建了 容器创建完毕 People{name='poorpool', gender='male', email='poorpool@yxchen.net'} true
|
getBean 的时候也可以用 Person.class 这种东西,但要保证只有一个匹配。或者你全都要,名字和类型都写上。
自然可以用有参构造器。实在不想写甚至可以省略 name,或者使用 index 指定顺序。也可以用 type="xxx"
指定类型。只有神经病才会使用后头的东西。
1 2 3 4 5
| <bean id="people2" class="net.yxchen.bean.People"> <constructor-arg name="name" value="sheska"/> <constructor-arg name="gender" value="female"/> <constructor-arg name="email" value="sheska@yxchen.net"/> </bean>
|
超级样例:
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
| <bean id="people1" class="net.yxchen.bean.People"> <property name="name"> <null/> </property> <property name="gender" value="male"/> <property name="car" ref="car1"/>
<property name="carList"> <list> <bean class="net.yxchen.bean.Car" p:color="red" p:carId="123"/> <bean class="net.yxchen.bean.Car" p:color="blue" p:carId="456"/> </list> </property>
、 </bean>
|
使用 parent=”xxx” 可以继承一个 bean 的配置,这样就不用写好多。如果一个 bean 只想让别人继承,就加一个 abstract=”true”。如果有依赖(就是谁要在谁之前创建),就写 depends-on=”person1, book1” 之类的。
bean 的 scope
加一个 scope=”xxx” 可以指定 bean 的作用域,常用的为 prototype 多实例和 singleton 单实例。还有 request 和 session 但是用得少。prototype 在容器启动时候不会创建实例,每次取得才会。
工厂类取得 bean
静态工厂:
1 2 3 4 5 6 7 8
| public class CarStaticFactory { public static Car getCar(String color) { Car car = new Car(); car.setCarId(1); car.setColor(color); return car; } }
|
1 2 3
| <bean id="car1" class="net.yxchen.factory.CarStaticFactory" factory-method="getCar"> <constructor-arg value="yellow"/> </bean>
|
实例工厂,先搞出来工厂再创建 bean:
1 2 3 4
| <bean id="factory1" class="net.yxchen.factory.CarInstanceFactory"/> <bean id="car1" class="net.yxchen.bean.Car" factory-bean="factory1" factory-method="getCar"> <constructor-arg value="yellow"/> </bean>
|
还有一个操作是实现 FactoryBean 接口:
src/net/yxchen/factory/FactoryBeanImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package net.yxchen.factory;
import net.yxchen.bean.Car; import org.springframework.beans.factory.FactoryBean;
public class FactoryBeanImpl implements FactoryBean<Car> { @Override public Car getObject() throws Exception { System.out.println("factory bean 创建对象"); return new Car("red", 1); }
@Override public Class<?> getObjectType() { return Car.class; }
@Override public boolean isSingleton() { return false; } }
|
1
| <bean id="car1" class="net.yxchen.factory.FactoryBeanImpl"/>
|
别看长得像一个普通 bean,只要实现了 FactoryBean 接口就会被认为是一个工厂:
1 2 3 4 5
| Car car = (Car) applicationContext.getBean("car1"); Car car1 = (Car) applicationContext.getBean("car1"); System.out.println(car); System.out.println(car1); System.out.println(car == car1);
|
1 2 3 4 5
| factory bean 创建对象 factory bean 创建对象 Car{color='red', carId=1} Car{color='red', carId=1} false
|
顺带一提,不要想着用 ${username} 这种东西,这是 Spring 的关键字……系统用户名。
生命周期
可以使用 init-method=”xxx” 和 destroy-method=”xxx” 来指定创建和销毁 bean 的时候的方法。
可以添加后置处理器,在调用初始化方法前后对 IOC 容器里头所有的实例都搞一遍。
- 通过构造器或工厂方法创建 bean 实例;
- 为 bean 的属性设置值和对其他 bean 的引用
- 将 bean 实例传递给 bean 后置处理器的 postProcessBeforeInitialization() 方法;
- 调用 bean 的初始化方法;
- 将 bean 实例传递给 bean 后置处理器的 postProcessAfterInitialization() 方法;
- bean 可以使用了;
- 当容器关闭时调用 bean 的销毁方法。
自动装配
使用 autowire。有 byName,byType,constructor 等等。
1 2 3 4
| <bean id="car" class="net.yxchen.factory.FactoryBeanImpl"/> <bean id="people1" class="net.yxchen.bean.People" autowire="byName"> <property name="name" value="poorpool"/> </bean>
|
java bean 程序里头怎么写,这里的 id 就要怎么写。
constructor 的话,比如public People(Car car)
的话,首先按照有参构造器类型进行装配,没有就直接赋 null。有的话,如果有多个,就拿名作为 id,没有就直接赋 null。
SpEL
就和 EL 表达式差不多。写法是 #{xxx}
。可以写字面量 #{123.4*5}
,其他 bean 的属性 #{book01.bookName}
,其他 bean #{car01}
,甚至是静态方法 #{T(全类名).静态方法名(args)}
。动态方法和其他 bean 的属性差不多。
注解配置
有四个注解:@Repository
给 dao /持久层,@Service
给业务层,@Controller
给控制层,@Component
给其他受 Spring IOC 容器管理的组件。
写的时候加上对应的注解:
1 2 3
| @Repository public class BookDao { }
|
当然也可以指定 bean 名和单例多例之类的:
1 2
| @Repository("qwqwq") @Scope("prototype")
|
然后在配置 xml 里头写上自动扫描:
1
| <context:component-scan base-package="net.yxchen"/>
|
可以排除不要包含的文件:
1 2 3 4 5 6 7
| <context:component-scan base-package="net.yxchen"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/> <context:exclude-filter type="assignable" expression="net.yxchen.dao.BookDao"/> </context:component-scan>
|
也可以指定要包含什么:
1 2 3 4
| <context:component-scan base-package="net.yxchen" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/> </context:component-scan>
|
autowired 注解
通过 autowired 注解实现自动装配:
1 2 3 4 5 6
| @Repository public class BookDao { public void saveBook() { System.out.println("bookDao-保存图书"); } }
|
1 2 3 4 5 6 7 8 9 10 11
| @Service public class BookService {
@Autowired private BookDao bookDao;
public void saveBook() { System.out.println("bookService-保存图书"); bookDao.saveBook(); } }
|
1 2 3 4 5 6 7 8 9
| @Controller public class BookServlet { @Autowired private BookService bookService;
public void doGet() { bookService.saveBook(); } }
|
Autowired 是根据类型自动装配,找到一个就装配一个,没找到就爆炸。找到多个就按照变量名的 id 找。如果找不到的话……用 Qualifier
例如:
1 2
| @Repository public class BookDao {
|
1 2
| @Repository public class BookDaoExt extends BookDao {
|
1 2 3 4 5 6 7 8 9 10 11 12
| @Service public class BookService {
@Autowired @Qualifier("bookDao") private BookDao bookDaoExt;
public void saveBook() { System.out.println("bookService-保存图书"); bookDaoExt.saveBook(); } }
|
如果没有那个 Qualifier,因为变量名叫 bookDaoExt,所以就会找一个叫这个的。但是有了 Qualifier,就会按照 Qualifier 钦点的找。
如果钦点了 Qualifier 居然还没找到就还是得报异常。可以钦点 @Autowired(required=false),这样找不到就装配 null 不报异常了。
Qualifier 甚至可以在参数上写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Controller public class BookServlet { @Autowired private BookService bookService;
public void doGet() { bookService.saveBook(); }
@Autowired public void func1(BookService bookService, @Qualifier("bookDao") BookDao bookDaoExt) { System.out.println(bookService + ", " + bookDaoExt); } }
|
顺带一提,autowired 的方法,当 Spring 容器在创建这个方法的时候会自动调用这个方法。每一个参数都会自动注入值。
另外,@Resource 这个 java ee 中的注解也可以实现自动装配。比 @Autowired 要差,但是扩展性好(因为后者必须要 Spring)。
单元测试
如果想在单元测试中使用自动装配呢?是这样吗:
1 2 3 4 5 6 7 8 9 10 11 12
| @Component public class IOCTest { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
@Autowired private BookService bookService;
@Test public void test1() { System.out.println(bookService); } }
|
发现死循环了……
其实要这么做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package net.yxchen.test;
import net.yxchen.service.BookService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class IOCTest { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
@Autowired private BookService bookService;
@Test public void test1() { System.out.println(bookService); } }
|
泛型注入
1 2 3 4 5
| package net.yxchen.dao;
public abstract class BaseDao<T> { public abstract void save(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| package net.yxchen.dao;
import net.yxchen.bean.Car; import org.springframework.stereotype.Repository;
@Repository public class CarDao extends BaseDao<Car> {
@Override public void save() { System.out.println("carDao...save"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package net.yxchen.service;
import net.yxchen.dao.BaseDao; import org.springframework.beans.factory.annotation.Autowired;
public class BaseService<T> { @Autowired BaseDao<T> baseDao;
public void save() { System.out.println("baseService...save"); baseDao.save(); } }
|
1 2 3 4 5 6 7 8 9
| package net.yxchen.service;
import net.yxchen.bean.Car; import org.springframework.stereotype.Service;
@Service public class CarService extends BaseService<Car> {
}
|
然后发现这玩意居然能够自动装配!结果是:
1 2
| baseService...save carDao...save
|
其实也很好理解。对一个 carService 执行 save(),自动装配的是 BookDao<Car>
,自然回去找这样的类。