spring基础
spring的三大特性
IoC(inverse of control):控制反转
DI(dependency invoke):依赖注入
AOP(aspect oriented program):面向切面编程
这里我写了个beans.xml,业务层和dao层以及他们的实现类,结构如下:
在service层添加set方法,调用dao层:
public class UserServiceImpl implements UserService {
//beanFactory调用该方法,从容其中获得userDao设置到此处
//set方法注入
public void setUserDao(UserDao userDao){
System.out.println("BeanFactory调用该方法创建的userDao:"+userDao);
}
}
在beans.xml中配置bean对象,并且添加层级关系,service调用dao层
<?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对象-->
<bean id="userService" class="com.liku.service.impl.UserServiceImpl">
<!--业务层调用dao层 name为set后面的属性名,ref为bean的参考id-->
<property name="userDao" ref="userDaoBean"></property>
</bean>
<bean id="userDaoBean" class="com.liku.dao.impl.UserDaoImpl"></bean>
</beans>
BeanFactory
spring的早期接口,spring的bean工厂
使用方式
private static void createBean() {
//创建工厂对象BeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
//创建读取器,读取xml文件
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
//读取配置文件给工厂
reader.loadBeanDefinitions("beans.xml");
//根据id获取Bean实例对象
Object userService = beanFactory.getBean("userService");
System.out.println(userService);//com.liku.service.impl.UserServiceImpl@6adca536
}
上面在创建userservice的同时会发现,userDao也被创建了。
ApplicationContext
BeanFactory相对偏向底层,ApplicationContext对BeanFactory功能进行扩展,监听功能,国际化功能等,ApplicationContext在容器创建的时候就将Bean实例化并初始化
private static void createApplicationContext() {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("beans.xml");
Object userService = applicationContext.getBean("userService");
System.out.println(userService);
}
导入依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.7</version>
</dependency>
基于xml的spring应用
springBean的配置
id:bean的名称,唯一标识
name:bean的别名,可以多个,用逗号隔开,id没有配的时候,默认第一个别名为id
scope:作用范围
- singleton:单例,默认值,spring容器创建的时候就会进行Bean的实例化,并且存储到容器内部的单例池中,每次getBean的时候都是获取相同的bean实例
- prototype:原型,spring容器初始化时不会创建Bean实例,当去调用getbean时才会实例化bean,每次getBean都会创建一个新的Bean实例
当我修改beans.xml文件的bean对象userService的作用范围的时候,每次创建的bean对象都是不一样的:
lazy-init:延迟加载,当值设置为true的时候为延迟加载,也就是当容器创建的时候不会立即创建bean实例,对BeanFactory无效,只对ApplicationContext生效
init-method:初始化方法,跟构造方法一起执行
destory-method:销毁方法,当容器关闭的时候会调用
上面两个方法也可以通过实现initialization的接口,并且该接口的方法执行顺序先于init-method
当类中含有参构造,不会默认生成无参构造的时候需要在配置文件中注入参数名和参数值,产生对象的方法,有形参的时候需要在配置文件中添加constructor-arg配置实参传递过去
<constructor-arg name="argName" value="argValue"></constructor-arg>
factory-method:工厂方法,有静态工厂,实例工厂
静态工厂方式实例化bean,
public class UserServiceImpl implements UserService {
public static UserDao getUserDao(){
return new UserDaoImpl();
}
public void setUserDao(UserDao userDao){
System.out.println("BeanFactory调用该方法创建的userDao:"+userDao);
}
}
<bean id="userService" class="com.liku.service.impl.UserServiceImpl" factory-method="getUserDao"></bean>
实例工厂方式实例化bean,调用工厂方法的时候必须先有工厂对象,再利用对象去调用里面的方法 :
配置bean工厂对象:
<bean id="userService" class="com.liku.service.impl.UserServiceImpl">
<property name="userDao" ref="userDaoBean"></property>
<constructor-arg name="name" value="张三"></constructor-arg>
</bean>
配置对象调用的方法返回的bean对象:
<bean id="factoryBean" factory-bean="userService" factory-method="getUserDao">
实现factoryBean规范延迟实例化bean
实现FactoryBean接口重写里面的方法,创建容器的时候不会立马创建bean对象,而是将工厂对象放到工厂对象缓存池,等到需要使用的时候才会实例化,当再次被调用的时候不会重新创建对象,而是直接从缓存池中拿。
bean的依赖注入的两种方式
通过bean的set方法注入:
<property name="参数名" ref="引入的其他bean的id"/>
<property name="参数名" value="传递的实参"/>
通过构造bean的方法进行注入
<constructor-arg name="参数名" ref="其他bean的id"/>
<constructor-arg name="参数名" value="实参"/>
依赖注入的数据类型:
普通数据类型:string、int、boolean等通过value属性指定
引用数据类型:userdaoimp、datasource等通过ref属性指定
<bean class="com.java.entity.Sysuser">
<property name="userMap">
<map>
<entry key="1"><value>a</value></entry>
<entry key="2"><value>b</value></entry>
</map>
</property>
<property name="userProperties">
<props>
<prop key="23">LIKU</prop>
<prop key="12">qs</prop>
</props>
</property>
</bean>
集合数据类型:list、map、properties,在property嵌入对应的标签,然后在list标签里面赋值value或bean。如果是map,嵌入entry标签,并添加key和value属性,properties需要添加props标签,里面嵌套prop。
public class UserDaoImpl implements UserDao {
public UserDaoImpl() {
System.out.println("userDaoImpl的构造方法");
}
}
public class UserServiceImpl implements UserService {
private List<String> strings;
public void setStrings(List<String> strings) {
this.strings = strings;
}
private UserDao userDao;
public void setUserDao(UserDao userDao){
System.out.println("BeanFactory调用该方法创建的userDao:"+userDao);
}
}
<bean id="userService" class="com.liku.service.impl.UserServiceImpl">
<property name="strings">
<list>
<value>aaa</value>
<value>bbb</value>
<value>我是集合</value>
</list>
</property>
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.liku.dao.impl.UserDaoImpl">
</bean>
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("beans.xml");
Object userService = applicationContext.getBean("userService");
System.out.println(userService);
}
输出结果:
userDaoImpl的构造方法
BeanFactory调用该方法创建的userDao:com.liku.dao.impl.UserDaoImpl@52a86356
com.liku.service.impl.UserServiceImpl@61f8bee4
自动装配【设置属性autowire】
<bean id="userService" class="com.liku.service.impl.UserServiceImpl" autowire="byName">
设置autowire为byName,可以实现自动识别名字,但是要求set方法后面的名字要跟属性名字相匹配,不然没法识别。
设置autowire为byType时,可以自动识别对应类型,但是如果是参考bean类型的时候,配置文件有多个bean,是同一种类型,也会无法识别。
命名空间
一个xml里面可以有多个beans,bean得放在beans里面
<beans profile="dev">
<bean id="userService1" class="com.liku.service.impl.UserServiceImpl"></bean>
</beans>
如果这个时候想要找到dev里面的userService1,需要指定环境:
使用代码的方式设置环境变量
System.setProperty("spring.profiles.active","dev");
使用命令行动态参数,虚拟机参数位置加载
-Dspring.profiles.active=dev
一个xml里面多个beans,此时当指定beans环境之后,该beans外面的内容也会生效,但是与他同级的beans里面的bean则不会生效,测试和开发可以指定两个不同的beans,公共代码部分可以放在外面的bean
import标签:用于导入其他配置文件
<import resource="beans1.xml"></import>
alias标签:起别名,之前起别名是通过给bean标签设置name属性,也可以通过alias设置别名
<alias name="userDao" alias="bieming"></alias>
在beanFactory里面有一个aliasMap,存放bean的别名map集合
引入context标签:添加属性:xmlns
xmlns:context="http://www.springframework.org/schema/context"
schemaLocation后面加上一对地址【一个标签需要引入一对地址】
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-beans.xsd
引入mvc:
xmlns:mvc="http://www.springframework.org/schema/mvc"
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
看看配好的标签:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
NoUniqueBeanDefinitionException
在getBean的时候后面可以传递类型:
applicationContext.getBean(UserService.class);
//也可以指定名字获取bean
applicationContext.getBean("userService1",UserService.class);
但是当读取的配置文件中存在多个bean都是UserService类型,但是没有指定bean名字的时候可能会报错。
druid配置
先导入依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
在xml文件中添加bean:
<bean id="dataSources" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc://mysql://localhost:3306/finance_db"></property>
<property name="username" value="oyll"></property>
<property name="password" value="123456"></property>
</bean>
配置connection对象
以前配置connection的方式:
//获取mysql驱动
Class.forName("com.mysql.jdbc.Driver");
//获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://192.168.3.86:3306/finance_db","root","123456");
第一步是通过class获取驱动,bean里面这样写,获取对象的工厂方法里面有参数,因此需要加上参数
<bean id="clazz" class="java.lang.Class" factory-method="forName">
<constructor-arg name="className" value="com.mysql.jdbc.Driver"></constructor-arg>
</bean>
第二步通过driverManager获取连接,工厂方法也要传实参
<bean id="connection" class="java.sql.DriverManager" factory-method="getConnection">
<constructor-arg name="url" value="jdbc:mysql://192.168.3.86:3306/finance_db"></constructor-arg>
<constructor-arg name="user" value="root"></constructor-arg>
<constructor-arg name="password" value="123456"></constructor-arg>
</bean>
由于每次获取的connection对象都需要是新的,否则一个连接关闭之后其他连不上数据库,因此在配置文件加上scope,设置为原型prototype,每次连接就会创建一个新的connection对象。
配置日期对象
普通方式:
String dateStr = "2022-08-12 12:23:34";
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date parse = dateFormat.parse(dateStr);
String format = dateFormat.format(parse);
xml配置bean,由于date对象是由simpleDateFormart里面的parse方法制造的,因此指定加工的工厂bean
<bean id="dateFomart" class="java.text.SimpleDateFormat">
<constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"></constructor-arg>
</bean>
<bean id="parse" factory-bean="dateFomart" factory-method="parse">
<constructor-arg name="source" value="2022-08-12 12:23:34"></constructor-arg>
</bean>
bean配置mybatis获取SqlSession工厂:
普通方式获取:
InputStream inputStream=Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
SqlSessionFactory build = sqlSessionFactoryBuilder.build(inputStream);
bean配置,第一步、通过resources类的getResourceAsStream方法实例化inputStream:
<bean id="resources" class="org.apache.ibatis.io.Resources" factory-method="getResourceAsStream">
<constructor-arg name="resource" value="mybatis-config.xml"></constructor-arg>
</bean>
第二步、无参构造实例化sqlSessionFactoryBuilder:
<bean id="sqlBuild" class="org.apache.ibatis.session.SqlSessionFactoryBuilder"></bean>
第三步、通过上面实例化的bean,调用build方法,实例化SqlSessionFactory对象:
<bean id="ssf" class="org.apache.ibatis.session.SqlSessionFactory" factory-bean="sqlBuild" factory-method="build">
<constructor-arg name="inputStream" ref="resources"></constructor-arg>
</bean>
bean实例化的基本流程:
spring容器在初始化时,会将xml配置的bean信息封装成一个BeanDefinition对象,所有BeanDefinition存储到一个名为beanDefinitionMap的Map集合中取,spring框架对该map进行遍历,使用反射创建bean实例对象,创建好的bean实例对象存储在一个名为singletonObejects的map集合中,当调用getBean方法的时候,从该map集合中取出bean实例对象。
spring后处理器
允许介入bean的整个实例化流程中,以达到动态注册BeanDefinition,动态修改BeanDefinition,一级动态修改bean。
spring有两种后处理器:
BeanFactoryPostProcessor
bean工厂后处理器,在BeanDefinitionMap填充完毕,bean实例化之前执行,只会执行一次
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition userDao = beanFactory.getBeanDefinition("userDao");
userDao.setBeanClassName("com.liku.service.impl.UserServiceImpl");
}
}
<bean id="userDao" class="com.liku.dao.impl.UserDaoImpl"></bean>
<bean class="com.liku.entity.MyBeanFactoryPostProcessor"></bean>
此时打印出来的userDao的类型为userServiceImpl
往map里面添加自定义的beanDefinition:
BeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName("com.liku.entity.TestBean");
DefaultListableBeanFactory defaultListableBeanFactory= (DefaultListableBeanFactory) beanFactory;
defaultListableBeanFactory.registerBeanDefinition("testBean",beanDefinition);
再去获取bean的时候就可以获取到testBean,上面的需要强转,通过下实现BeanDefinitionRegistryPostProcessor接口,重写里面的方法:
public class MyBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition userDao = beanFactory.getBeanDefinition("userDao");
userDao.setBeanClassName("com.liku.service.impl.UserServiceImpl");
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
BeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName("com.liku.entity.TestBean");
registry.registerBeanDefinition("testBean", beanDefinition);
}
}
BeanDefinitionMap实例化好对象之后先通过BeanDefinitionRegistryPostProcessor添加注册之后的bean,然后再通过BeanFactoryPostProcessor修改Bean对象,然后再放到singletonObjects里面
自定义@component
类上面添加注解MyComponent,并且创建注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent {
String value();
}
工具类:
public class BaseClassScanUtils {
//设置资源规则
private static final String RESOURCE_PATTERN = "/**/*.class";
public static Map<String, Class> scanMyComponentAnnotation(String basePackage) {
//创建容器存储使用了指定注解的Bean字节码对象
Map<String, Class> annotationClassMap = new HashMap<String, Class>();
//spring工具类,可以获取指定路径下的全部类
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
try {
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
ClassUtils.convertClassNameToResourcePath(basePackage) + RESOURCE_PATTERN;
Resource[] resources = resourcePatternResolver.getResources(pattern);
//MetadataReader 的工厂类
MetadataReaderFactory refractory = new CachingMetadataReaderFactory(resourcePatternResolver);
for (Resource resource : resources) {
//用于读取类信息
MetadataReader reader = refractory.getMetadataReader(resource);
//扫描到的class
String classname = reader.getClassMetadata().getClassName();
Class<?> clazz = Class.forName(classname);
//判断是否属于指定的注解类型
if(clazz.isAnnotationPresent(MyComponent.class)){
//获得注解对象
MyComponent annotation = clazz.getAnnotation(MyComponent.class);
//获得属value属性值
String beanName = annotation.value();
//判断是否为""
if(beanName!=null&&!beanName.equals("")){
//存储到Map中去
annotationClassMap.put(beanName,clazz);
continue;
}
//如果没有为"",那就把当前类的类名作为beanName
annotationClassMap.put(clazz.getSimpleName(),clazz);
}
}
} catch (Exception exception) {
}
return annotationClassMap;
}
public static void main(String[] args) {
Map<String, Class> stringClassMap = scanMyComponentAnnotation("com.liku");
System.out.println(stringClassMap);
}
}
创建bean的注册类,往里面注册bean对象
public class MyBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
Map<String, Class> stringClassMap = BaseClassScanUtils.scanMyComponentAnnotation("com.liku");
stringClassMap.forEach((k,v)->{
String className=v.getName();
System.out.println("value获得名字之后的值"+className);
BeanDefinition beanDefinition=new RootBeanDefinition();
beanDefinition.setBeanClassName(className);
registry.registerBeanDefinition(k,beanDefinition);
});
}
}
配置文件里面就只有一个bean:
<bean class="com.liku.entity.MyBeanFactoryPostProcessor"></bean>
但是,当操作下面:
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object testBean = applicationContext.getBean("testBean");
Object userDao = applicationContext.getBean("userDao");
Object userService = applicationContext.getBean("userService");
System.out.println(testBean);
System.out.println(userDao);
System.out.println(userService);
}
三个对象都有【因为三个类上面我都加了MyComponent注解】
BeanPostProcessor
Bean后处理器,一般在Bean实例化之后,填充到单例池singletonObejects之前执行,每个bean实例化之后都会执行
继承BeanPostProcessor的类:
public class MyBeanFactoryPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof UserDaoImpl){
UserDaoImpl userDao= (UserDaoImpl) bean;
userDao.setValue("传的实参值");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
想要创建的bean对象里面的结构:
public class UserDaoImpl implements UserDao {
private String value;
public UserDaoImpl() {
System.out.println("userDaoImpl的无参构造");
}
public void setValue(String value) {
this.value = value;
System.out.println(value);
}
}
里面有两个方法,postProcessBeforeInitialization/postProcessAfterInitialization,执行顺序都在构造方法之后,两个方法中间还有操作--->初始化-->属性赋值完毕。
在bean中可以设置初始化方法:|
init-method,初始化方法,我这里创建了一个初始化方法init |
实现InitializingBean接口,里面有afterPropertiesSet方法:
public class UserDaoImpl implements UserDao,InitializingBean {
private String value;
public UserDaoImpl() {
System.out.println("userDaoImpl的无参构造。。");
}
public void setValue(String value) {
this.value = value;
System.out.println(value);
}
public void init() {
System.out.println("初始化。。");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("属性赋好值之后。。");
}
}
当我添加上上面的方法之后再此启动,打印出的顺序是:
构造方法-->before初始化-->属性赋好值–>初始化–>after初始化
bean的后处理器案例
包装增强bean并返回:
public class MyBeanFactoryPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//使用动态代理对目标bean进行增强,返回proxy对象,进而存储到singleton
Object proxyInstance = Proxy.newProxyInstance(
bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
(proxy, method, args) -> {
//开始时间
System.out.println("开始:" + method.getName() + " " + new Date().getTime());
//执行方法
Object invoke = null;
for (int i = 0; i < 20; i++) {
invoke = method.invoke(bean, args);
}
//结束时间
System.out.println("结束:" + method.getName() + " " + new Date().getTime());
return invoke;
}
);
System.out.println(bean + " after " + new Date().getTime());
return proxyInstance;
}
}
在bean的配置文件中:
<bean class="com.liku.entity.MyBeanFactoryPostProcessor"></bean>
<bean id="dao" class="com.liku.dao.impl.UserDaoImpl" init-method="init"></bean>
之后得到的bean对象不是userDaoImpl类型,而是包装过后的bean对象。
从上面可以看到spring的实例化过程可以加以细化如下: