第一部分
1,概述:先创建Java类,再把Java类定义到配置文件的 bean中,在启动项目时通过classpathxmlwebapplicationcontext
这个类这个容器去手动加载配置文件。将定义到配置文件的bean,通过控制反转,加载到spring容器中,再通过 java 反射机制把类实例化出来,可以从容器中 getbean 把实例获取出来,获取的是一个对象,转成定义的 class,再调用函数。
<bean id="spring16" class="com.qicong.s16.Spring16" autowire="byType"></bean>
public class Spring16 {
public void printUser(){
System.out.println("Spring16....");
}
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Spring16 s16 = (Spring16)context.getBean("spring16");
s16.printUser();
}
}
2,配置文件:相当于一份需要被实例化的 java 类的名单。
定义xsd才能使用对应的标签。
配置文件的创建:可以在创建项目时创建 spring 的配置文件,也可以在创建项目后,单独创建配置文件,在src目录下new一个xml configuration file, spring configu,一般将配置文件起名为application configuration. xml。
3,bean
如何定义一个bean?
bean 的 id 属性:java类的唯一标识,class属性:java类的包名,java类的全路径。
bean 其实是一个实例化的 java 类。
<bean id="user1" class="com.qicong.s15.User"/>
Core 一:
bean的加载原理就是控制反转,控制反转描述的是 bean 的生命周期,java 反射机制是 spring 的底层实例化 bean。
Java 的反射机制:通过全路径的字符串把 Java 类实例化出来。
尝试用 ClassForName 把类实例化出来,但是这么做类没有加载到 spring 容器中。
//java的反射机制
try {
Spring11 s1 = (Spring11)Class.forName("com.qicong.Spring11").getConstructor().newInstance();
s1.printSpring();
}catch (Exception e){
e.printStackTrace();
}
Spring 是如何将类实例化出来的:spring 把配置文件中的 bean 加载到容器,key value 的 map 结构加载到容器,通过get key get id获取一个Java 类的全路径,通过 ClassForName,把类实例化出来,实例化出来以后,放到容器中,getbean获取出来,转成当前定义的class,就可以去调用函数了。
控制反转 Invertion of control:
项目启动时配置文件相当于一份需要被实例化的 Java 类的名单传到容器,容器中根据配置文件定义的 bean, 去注册 bean,找到 bean对应的位置并实例化bean,通过反射机制实例化bean,并放到缓存池中,当有http请求时,直接从缓存池中调用,当运行结束或项目停止时 bean 自动销毁。
bean的生命周期:
bean的定义:在配置文件中定义 bean。
bean的初始化:将 bean 加载到容器并实例化出来。
bean的使用:http 请求时从缓存池中调用。
bean的销毁:当运行结束,项目停止时,bean自动销毁。
感觉到将 bean 加载到 spring 中变得更复杂了?/ 为什么要把bean加载到容器?
我们不可能把所有的代码都写到一个类里,要用 Service 类去分担业务逻辑,对数据进行处理,如何调用Service中的方法?如果去new 一个 Service类,这个类将不在 spring 容器里,不在同一个运行环境中就无法调用 Service 中的方法,降低了开发的效率。当我们把bean统一放到容器中进行托管,就可以更专注于业务的开发,提升开发的效率。
bean 的 name 属性:
在 Spring 容器中,id 是 bean 唯一的标识符,name可以是多个,name="n1,n2,n3…"类似于别名,也可以通过别名去获取实例,id 和 name 至少要指定一个,如果 id 和 name 都没有,class 全类名作为name,也可以通过 class 包名去获取这个实例。
<bean id="spring11" name="spring11-1,spring11-2" class="com.qicong.Spring11"/>
Core 二:
1,bean的依赖注入DI:denpendency injection:
强依赖:在 a类 中使用 b类, 如果用 new 关键字写固定一个b类的实现类,当需要修改使用的实现类,需要修改代码。用接口也要用 new 关键字写固定一个实现类,修改也需要修改代码,代码写固定,关联性太强,耦合度太高,通过配置文件把依赖转为注入,降低软件的耦合度,提高代码的灵活性。
例子:修改 jdbc 配置文件,添加 jar 包就可以用代码连不同的数据库,不用在代码中写固定数据库的驱动。
如何实现注入?
通过 property 实现注入,name 是属性名,ref 是注入的 bean 的 id 名,value 是给属性赋值。
在类中写属性要用setter&getter方法。
如果包中有很多类可以注入时,修改property 的 ref 值就可以改变注入的类。
2,bean 的 autowire 属性:
autowire Byname:类属性名和被加载到容器的 bean 的 id 一致,实现自动注入。
Bytype:通过类的属性的类型能获取到一个Java类,这个Java类也在容器中,且容器中通过这个Java类的 class 包路径能获取到与类属性类型获取一致的Java类,由此建立关联,实现自动注入。
candidate:针对 autowire Bytype 使用的。当出现:被注入的类有几个子类可以为实现类。
哪个父类或子类的 bean 加上 autowire candidate:ture 就选择将此父类或子类之一注入到类中。相当于primary。
该被注入的父类或子类中哪个加上 autowire candidate:false 就忽视哪个类。
总之父类和子类中只有一个能被注入。
3,bean 的 init-method 和 destroy-method 属性:
bean实例化时,回调 init-method 中对应的方法。
bean销毁时,回调 destroy-method 中对应的方法。
方法都定义在Java 类里面。方法里面可以打印内容。
代码实战:从容器中获取实例,启动项目实例化bean时,回调函数。从容器中手动销毁 bean时,context. register shutdown hook() 回调函数,打印内容。
<bean id="spring17" class="com.qicong.s17.Spring17" init-method="initSpring" destroy-method="destroySpring"/>
public class Spring17 {
public void initSpring(){
System.out.println("init s17");
}
public void destroySpring(){
System.out.println("destroy s17");
}
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Spring17 s = (Spring17)context.getBean("spring17");
context.registerShutdownHook();
}
}
4,bean 的 lazy-init属性:
lazy-init:true 表示懒加载。懒加载指只注册,不实例化,http 请求使用时才实例化并放到缓存池中,从缓存池调用。与游戏类似,不一次性加载全部组件,玩到哪块加载哪一块的组件。
代码实战:懒加载在项目启动时,不调用实例化的回调函数, getBean 获取实例,才回调 init-method 中定义的函数。
5,bean 的 scope 属性:
scope:bean 的作用范围。
singleton 默认的单例模式,在bean缓存池中只有一个此类的实例。
prototype 多例模式,每次使用都会实例化一个新的此类的实例。
代码实战:多次打印从容器中获取的对象,区分从缓存池中获取的是单例还是多例。用实例化的回调函数 init-method 证明多例模式。
<bean scope="singleton" id="spring19" class="com.qicong.s19.Spring19" init-method="initSpring19"/>
<bean scope="prototype" id="spring19" class="com.qicong.s19.Spring19" init-method="initSpring19"/>
多例模式调用时从缓存池中获取的实例
6,bean 的 depend on 属性:
可用作 bean 的加载顺序设定,可依赖多个 bean,把被依赖的bean 的 id 或 name 写上。被依赖的 bean 要先实例化,被依赖的 bean 如果是懒加载,则懒加载失效。
代码实战:依赖懒加载的bean,启动项目时,先实例化懒加载的 bean,回调其 init-method 中对应的函数。(懒加载不会在项目启动时实例化到缓存池,被使用时才加载 ,被依赖就是被使用了所以懒加载失效了,就要先加载)
<bean lazy-init="true" id="spring18" class="com.qicong.s18.Spring18" init-method="initSpring18" />
<bean id="spring20" class="com.qicong.s20.Spring20" depends-on="spring18"/>
第二部分:
Spring 的注解:
@Override注解:重写父类的函数时使用。
@Deprecated:表明函数已弃用,不建议使用,出问题也不维护。
@ SuppressWarnings:未加泛型或定义的内容未使用,让编译器 Eclipse 忽略警告。
元注解:
@Retention:自创的注解保存在哪
RetentionPolicy 枚举类:
Source:保存在代码中。
Class:编译进class中,不添加到虚拟机。
RunTime:进虚拟机,在运行时通过反射获取信息。
@Targets:自创的注解在哪使用
使用元注解的例子:
Java注解自动生成 sql ,通过bean自动生成大量 sql,提高开发效率:
1,针对数据库表创建Java Bean类,user类映射类,属性名映射数据库的字段类。后续会创建父类,user 类继承父类,打印 sql 的方法中传入父类,可以打印子类 user 类或其他子类的 sql 语句。
2,使用元注解创建注解:
创建两个注解类:用在类上的注解,映射数据库的表名。
用在变量上的注解:映射数据库的字段。
定义注解类的 name 属性,在使用注解时,再给属性赋值,赋数据库的表名,字段名。
怎么定义注解类的 name 属性?类似于定义抽象函数的方法,没有函数体。
3,写一个类去生成 sql:
main:给 user 类属性赋值,new 当前类调用 insert 的方法把 user 类传进去。
Insert into 1tablename (2columns)3Values.
insert:把user类的父类作为传入的参数。
获取表名。
获取类属性的数组。
遍历属性的数组,获取每个属性的注解数组,将每个注解的 name 属性的值放到columns 数组。
获取 values 数组
StringBuilder, append,StringJoin 串联 sql。
sout 将 StringBuilder 打印出来。
在 main 中给不同的子类赋值都可以把 sql 打印出来。
打印出来以后可以连接数据库,把 sql 语句插入到数据库中。
package com.qicong.s22;
import java.lang.reflect.Field;
/**
* User: 祁大聪
* SQL
* INSERT INTO t_user('name','user_name','password','id')
* VALUES ('三妮','sanNi','root123','100')
*/
public class JDBCTest {
public void insert(LongBean bean) throws Exception{
//这是表的名称的注解
MyTable mt = bean.getClass().getAnnotation(MyTable.class);
String tableName = mt.name();
//这是所有的属性
Field[] fields = bean.getClass().getFields();
String[] columns = new String[fields.length];
String[] values = new String[fields.length];
for(int i = 0 ; i < fields.length; i++){
MyColumn mc = fields[i].getAnnotation(MyColumn.class);
columns[i] = "'" + mc.name() + "'";
values[i] = "'" + fields[i].get(bean).toString() + "'";
}
//生成SQL
StringBuilder sb = new StringBuilder("INSERT INTO " + tableName);
sb.append("("
+ String.join(",",columns)
+ ") VALUES ("
+ String.join(",",values)
+ ")"
);
System.out.println(sb);
//调用SQL
}
public static void main(String[] args) {
User user = new User();
user.id = 100L;
user.name = "三妮";
user.username = "sanNi";
user.password = "root123";
Student student = new Student();
student.id = 100L;
student.school = "xx大学";
student.address = "北京";
student.height = 180;
//插入到数据库中
JDBCTest jdbc = new JDBCTest();
try { // ctrl + alt + t
jdbc.insert(user);
jdbc.insert(student);
} catch (Exception e) {
e.printStackTrace();
}
}
}
我们再回到配置文件中。
<context: component-scan base-package:" " />
context 容器去扫描哪些包,自动扫描这个包下所有带有 Spring 注解的类到容器,这个包下加了 @Component 注解的类会自动扫描到 context 容器中。
1,@Component中的 name 值相当于 bean 的 id,bean中的 id 当然不可以重复, 不写 name 时类的名称的第1个字母小写的全名为默认的 id 。
使用 @Component 注解的类,有不同包但同名的类,name改新不改老。因为老项目可能已经使用了这个 name。
2,@Service SpringBoot中处理业务逻辑的类使用的注解。
3,@Scope( Value= ConfigurableBeanFactory. Scope PROTOTYPE.)
可以多次打印从缓存池中获取的对象,查看是单例或多例模式。
4, @DependsOn(“被依赖的 bean 的 id”)被依赖的类先实例化。
5,@Bean (name=“与当前类的bean的 name 不同,容器中get此处的id可以 get 新加载到容器的bean”)
用在函数上的注解,用在返回当前 bean 的函数上的注解,用了以后会返回一个新的 bean 加载到容器。
代码实战:从容器中获取两个实例并打印出来。
6,DI 注解:
@ Autowire byType实现注入。
代码实战:获取当前类的实例,去调用被注入类的方法,打印内容。
@Primary:主要的优先的。
在接口被注入时,在接口的实现类之一上使用,表明优先调用此类实现接口,注入接口以后,能调用 此实现类的方法。
@Qualifier:限定名。
限定注入的接口使用的实现类。
如果实现类中使用 @Qualifier,把注入此实现类的 name 写固定了,那么在限定接口的实现类时写的 name 要与实现类中写固定的 name 相同。
注入成功以后,也可以从容器中用接口实现类默认的 id getBean。
如果实现类还是多例模式,通过 id 或 name 从缓存池中获取的实例不同。
7,配置注解
@Configuration
@ ComponentScan(basePackages=" “):代替 xml 文件中
<context: component-scan base-package:” " /> 会扫描这个包下所有带有 Component 注解的类到容器。
@ImportResource:引入配置文件,将配置文件中定义的 bean 也扫描的容器。