课程来源
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>
,自然回去找这样的类。