Spring学习笔记02
1.Bean的自动装配
1.1 Bean的作用域(Bean Scopes)
在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为Bean
简单来讲,Bean就是由IoC容器初始化、装配及管理的对象
类别 | 说明 |
---|---|
singleton | 在Spring IoC容器中仅存在一个Bean实例,Bean以单例模式存在,默认值 |
prototype | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean(),相当于执行new XxxBean() |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
session | 同一个HTTP Session共享一个Bean,不同Session使用不同Bean,仅适用于WebApplicationContext环境 |
几种作用域中,request,session作用域仅在基于Web的应用中使用(不必关心你所采用的是什么Web应用框架),只能用在基于web的Spring ApplicationContext环境
- Singletion
当一个Bean的作用域为singletion,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例
Singletion是单例类型,就是在创建容器时就同时自动创建了一个bean的对象,不管你是否使用,它都存在了,每次获取到的对象都是同一个对象。注意:Singletion作用域是Spring中的缺省作用域,要在xml中将bean定义为singletion
<bean id="user" class="com.jcsune.pojo.User" c:age="18" c:name="小明" scope="singleton"/>
测试:
@Test
public void testT(){
ApplicationContext context = new ClassPathXmlApplicationContext("beansuser.xml");
User user=(User)context.getBean("user");
User user2=(User)context.getBean("user");
System.out.println(user==user2);
}
结果:
- Prototype
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的Bean实例,Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象,根据经验,对有状态的bean应该使用prototype作用域,对无状态的bean则应该使用singleton作用域,在xml中将bean定义为prototype,可以这样配置:
<bean id="user" class="com.jcsune.pojo.User" p:name="小明" p:age="18" scope="prototype"/>
或者
<bean id="user" class="com.jcsune.pojo.User" p:name="小明" p:age="18" singleton="false"/>
测试:
@Test
public void testT(){
ApplicationContext context = new ClassPathXmlApplicationContext("beansuser.xml");
User user=(User)context.getBean("user");
User user2=(User)context.getBean("user");
System.out.println(user==user2);
}
结果:
- Request
当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例:即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成,该作用域仅在基于web的Spring ApplicationContext情形下有效,考虑下面bean定义:
<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的loginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化,当处理请求结束,request作用域的bean实例将被销毁
- Session
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例,该作用域仅在基于web的Spring ApplicationContext情形下有效,考虑下面bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效,与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences 创建的实例,将不会看到这些特定于某个HTTP Session的状态变化,当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉
1.2 自动装配说明
自动装配是使用spring满足bean依赖的一种方法
spring会在应用上下文中为某个bean寻找其依赖的bean
Spring中bean有三种装配机制
- 在xml中显式配置
- 在java中显式配置
- 隐式的bean发现机制和自动装配
这里主要介绍第三种:自动化的装配bean
Spring的自动装配需要从两个角度来实现或者说是两个操作:
- 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean
- 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少
推荐不使用自动装配xml配置,而使用注解
1.3 测试环境搭建
- 新建一个项目
- 新建两个实体类Cat Dog都有一个叫的方法
Cat.java
package com.jcsune.pojo;
public class Cat {
public void shout(){
System.out.println("miao~");
}
}
Dog.java
package com.jcsune.pojo;
public class Dog {
public void shout(){
System.out.println("wang~");
}
}
- 新建一个用户类 User
package com.jcsune.pojo;
public class User {
private Cat cat;
private Dog dog;
private String name;
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 编写spring配置文件
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dog" class="com.jcsune.pojo.Dog"/>
<bean id="cat" class="com.jcsune.pojo.Cat"/>
<bean id="user" class="com.jcsune.pojo.User">
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
<property name="name" value="jcsune"/>
</bean>
</beans>
- 测试
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user=(User)context.getBean("user");
user.getCat().shout();
user.getDog().shout();
}
- 结果
结果正常输出,环境OK
1.4 byName
autowire by Name(按名称自动装配)
由于在手动配置xml过程中常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发的效率降低
采用自动装配将避免这些错误,并且使配置简单化
测试:
- 修改bean配置,增加一个属性 autowire=”byName”
<bean id="user" class="com.jcsune.pojo.User" autowire="byName">
<property name="cat" ref="cat"/>
</bean>
- 再次测试,结果依旧成功输出
- 再将cat的bean id修改为catXXX
- 再次测试,执行时报空指针java.lang.NullPointerException,因为按byName规则找不对应set方法。真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针异常
小结:
当一个bean节点带有autowire byName的属性时
- 将查找其类中所有的set方法名。例如setCat,获得将set去掉并且首字母小写的字符串,即cat
- 去spring容器中寻找是否有此字符串名称id的对象
- 如果有,就取出注入;如果没有,就报空指针异常
1.5 byType
autowire by Type(按类型自动装配)
使用autowire by Type首先需要保证:同一类型的对象,在spring容器中唯一,如果不唯一,会报不唯一的异常
测试:
将user的bean配置修改一下:autowire=”byType”
测试,正常输出
在注册一个cat的bean对象
<bean id="dog" class="com.jcsune.pojo.Dog"/>
<bean id="cat" class="com.jcsune.pojo.Cat"/>
<bean id="cat2" class="com.jcsune.pojo.Cat"/>
<bean id="user" class="com.jcsune.pojo.User" autowire="byType">
<property name="name" value="jcsune"/>
</bean>
- 测试,报错:NoUniqueBeanDefinitionException
- 删掉cat2,将cat的bean名称改掉!测试,因为是按类型装配,所以并不会报异常,也不影响最后的结果,甚至将id属性去掉,也不影响结果
这就是按照类型自动装配
1.6 注解实现自动装配
jdk1.5开始支持注解, spring2.5开始全面支持注解
准备工作:利用注解的方式注入属性
- 在spring配置文件中引入context文件头
- 开启属性注解支持
- @Autowired是按类型自动装配的,不支持id匹配
- 需要导入spring-aop的包
- 直接在属性上使用即可,也可以在set方式上使用
- 使用Autowired我们可以不用编写set方法了,前提是你自动装配的属性在IoC(Spring)容器中存在,且符合名字byName
测试:
- 将User类中的set方法去掉,使用@Autowired注解
public class User {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String name;
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
public String getName() {
return name;
}
}
- 此时配置文件中内容:
<bean id="dog" class="com.jcsune.pojo.Dog"/>
<bean id="cat" class="com.jcsune.pojo.Cat"/>
<bean id="user" class="com.jcsune.pojo.User"/>
- 测试,成功输出结果
@Autowired(required=false)说明:false,对象可以为null; true,对象必须存对象,不能为nullfont>
//如果允许对象为null,设置required=false,默认为true
@Autowired(required=false)
private Cat cat;
- @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
- @Qualifier不能单独使用
测试实验步骤:
- 配置文件修改内容,保证类型存在对象,且名字不为类的默认名字
<bean id="dog1" class="com.jcsune.pojo.Dog"/>
<bean id="dog2" class="com.jcsune.pojo.Dog"/>
<bean id="cat1" class="com.jcsune.pojo.Cat"/>
<bean id="cat2" class="com.jcsune.pojo.Cat"/>
- 没有加Qualifier测试,直接报错
- 在属性上添加Qualifier注解
@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;
测试成功!
- @Resource如有指定的name属性,先按该属性进行byName方式查找装配
- 其次再进行默认的byName方式进行装配
- 如果以上都不成功,则按byType的方式自动装配
- 都不成功,则报异常
实体类:
@Resource(name= "cat2")
private Cat cat;
@Resource
private Dog dog;
private String name;
beans.xml
<bean id="dog" class="com.jcsune.pojo.Dog"/>
<bean id="cat1" class="com.jcsune.pojo.Cat"/>
<bean id="cat2" class="com.jcsune.pojo.Cat"/>
<bean id="user" class="com.jcsune.pojo.User"/>
测试结果:OK!
配置文件2:beans.xml,删掉cat2
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
实体类 上只保留注解
@Resource
private Cat cat;
@Resource
private Dog dog;
测试结果:OK!
结论:先进行byName查找,失败;再进行byType查找,成功
小结:@Autowired与@Resource异同
@Autowired 与@Resource都可以用来装配bean,都可以写在字段上,或写在setter方法上
@Autowired默认按类型分配(属于spring规范)默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,比如@Autowired(required=false),如果我们想使用名称装配可以结合@Qualifier注解进行使用
@Resource(属于J2EE复返) 默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配,当找不到与名称匹配的bean时才按照类型进行装配,但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配
它们的作用相同都是用注解方式注入对象,但执行顺序不同@Autowired先byType@Resource先byName
2.使用注解开发
2.1 说明
在Spring4之后,想要使用注解形式,必须要引入aop的包
在配置文件当中,还得要引入一个context约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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-context.xsd">
</beans>
2.2 Bean的实现
我们之前都是使用bean的标签进行bean注入,但是实际开发中,我们一般都会使用注解!
- 配置扫描哪些包下的注解
<!-- 指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.jcsune.pojo"/>
- 在指定包下编写类,增加注解
@Component("user")
//相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
public String name="jcsune";
}
- 测试
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User)context.getBean("user");
System.out.println(user.name);
}
- 结果
2.3 属性注入
使用注解注入属性
- 可以不用提供set方法,直接在名上添加@value(“值”)
@Component
//相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
@Value("jcsune")
// 相当于配置文件中 <property name="name" value="jcsune"/>
public String name;
}
- 如果提供了set方法,在set方法上添加@Value(“值”)
@Component
//相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
public String name;
@Value("jcsune")
public void setName(String name) {
this.name = name;
}
}
2.4 衍生注解
我们这些注解,就是替代了在配置文件当中配置步骤而已,更加德 方便快捷
@Component三个衍生注解
为了更好的进行分层,Spring可以使用其他三个注解,功能一样,目前使用哪一个的功能都一样
- @Controller: web层
- @Service:service层
- @Repository: dao层
写上这些注解,就相当于把这个类交给Spring管理分配了
自动装配注解:见1.6
作用域
- singleton: 默认的,Spring会采用单例模式创建这个对象,关闭工厂,所有的对象都会销毁
- prototype:多例模式,关闭工厂,所有的对象不会销毁,内部的垃圾回收机制会回收
@Controller("user")
@Scope("prototype")
public class User {
@Value("jcsune")
public String name;
}
2.5 小结
xml与注解比较
- XML可以适用于任何场景,结构清晰,维护方便
- 注解不是自己提供的类使用不了,开发简单方便
xml与注解整合开发:推荐最佳实践
- XML管理Bean
- 注解完成属性注入
- 使用过程中,可以不用扫描,扫描是为了类上的注解
作用
- 进行注解驱动注册,从而使注解生效
- 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向spring注册
- 如果不扫描包,就需要手动配置bean
- 如果不加注解驱动,则注入的值为null
2.6 基于Java类进行配置
JavaConfig原来是Spring的一个子项目,它通过java类的方式提供Bean的定义信息,在Spring4的版本,JavaConfig已正式成为Spring4的核心功能
测试:
- 编写一个实体类:Dog
public class Dog {
public String name="dog";
}
- 新建一个config配置包,编写一个MyConfig配置类
@Configuration //代表这是一个配置类
public class MyConfig {
@Bean //通过方式注册一个bean,这里的返回值就是Bean的类型,方法名就是bean的id
public Dog dog(){
return new Dog();
}
}
- 测试
@Test
public void test(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
Dog dog = (Dog) applicationContext.getBean("dog");
System.out.println(dog.name);
}
- 结果:
导入其它配置的方法
- 我们再编写一个配置类
@Configuration
public class MyConfig2 {
}
- 在之前的配置类中我们来选择导入这个配置类
@Configuration //代表这是一个配置类
@Import(MyConfig2.class)
public class MyConfig {
@Bean //通过方式注册一个bean,这里的返回值就是Bean的类型,方法名就是bean的id
public Dog dog(){
return new Dog();
}
}
关于这种java类的配置方式,我们再之后的SpringBoot和Spring Cloud中还会大量看到,我们需要知道这些注解的作用即可!
3.代理模式
为什么要学习代理模式,因为AOP的底层机制就是动态代理
代理模式:
- 静态代理
- 动态代理
3.1 静态代理
静态代理角色分析
- 抽象角色: 一般使用接口或抽象类来实现
- 真实角色: 被代理的角色
- 代理角色:代理真实角色;代理真实角色后,一般会做一些附属的操作
- 客户: 使用代理角色来进行一些操作
以租房为例:
Rent.java:抽象角色
package com.jcsune.demo1;
//抽象角色:租房
public interface Rent {
public void rent();
}
Host.java 真实角色
package com.jcsune.demo1;
//真实角色:房东,房东要出租房子
public class Host implements Rent{
public void rent(){
System.out.println("房屋出租");
}
}
Proxy.java 代理角色
package com.jcsune.demo1;
//代理角色:中介
public class Proxy implements Rent{
private Host host;
public Proxy(){}
public Proxy(Host host){
this.host=host;
}
//租房
public void rent(){
seeHouse();
host.rent();
fare();
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
Client.java 客户
package com.jcsune.demo1;
//客户类,一般客户都会去找代理
public class Client {
public static void main(String[] args) {
//房东要出租房
Host host=new Host();
//中介帮助房东
Proxy proxy=new Proxy(host);
//去找中介租房
proxy.rent();
}
}
结果:
分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情
静态代理的好处:
- 可以使我们的真实角色更加纯粹,不再去关注一些公共的事情
- 公共的业务由代理来完成,实现了业务的分工
- 公共业务发生扩展时变得更加集中和方便
缺点:
- 类多了,多了代理类,工作量变大了,开发效率降低
我们想要静态代理的好处,又不想要静态代理的缺点,所以就有了动态代理
3.2 静态代理再理解
练习步骤:
- 创建一个抽象角色,比如平时做的用户业务,抽象起来就是增删改查
//抽象角色:增删改查业务
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
- 我们需要一个真实对象来完成这些增删改查操作
//真实对象,完成增删改查操作的人
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("更新了一个用户");
}
public void query() {
System.out.println("查询了一个用户");
}
}
- 需求来了,比如我们需要增加一个日志功能,怎么实现
- 思路:1:在实现类上增加代码【麻烦!】
- 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能是最好的了!
- 设置一个代理类来处理日志!代理角色
public class UserServiceProxy implements UserService {
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
public void add() {
log("add");
userService.add();
}
public void delete() {
log("delete");
userService.delete();
}
public void update() {
log("update");
userService.update();
}
public void query() {
log("query");
userService.query();
}
public void log(String msg){
System.out.println("执行了"+msg+"方法");
}
}
- 测试访问类
public class Client {
public static void main(String[] args) {
//真实业务
UserServiceImpl userService=new UserServiceImpl();
//代理类
UserServiceProxy proxy=new UserServiceProxy();
//使用代理类实现日志功能
proxy.setUserService(userService);
proxy.add();
}
}
- 结果
OK,到了现在代理模式应该没什么问题了,重点是需要理解其中的思想
做到在不改变原来的代码的情况下,实现对原有功能的增强,这是AOP中最核心的思想
AOP;纵向开发,横向开发
3.3 动态代理
动态代理的角色和静态代理的一样
动态代理的代理类是动态生成的,静态代理的代理类是我们提前写好的
动态代理分为两类:一类是基于接口动态代理,一类是基于类的动态代理
- 基于接口的动态代理——-JDK动态代理
- 基于类的动态代理——cglib
- 现在用的比较多的是javasist来生成动态代理
- 我们这里使用JDK的原生代码来实现,其余额的道理都是一样的
JDK的动态代理需要了解两个类
核心:InvocationHandler 和 Proxy ,打开JDK文档了解一下
[InvocationHandler :调用处理程序]
Object invoke(Object proxy,方法 method,Object[] args);
//参数
//proxy-调用该方法的代理实例
//method-所述方法对应于调用代理实例上的接口方法的实例,方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口
//args-包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数,原始类型的参数包含在适当的原始包装器类的实例中例如java.lang.Integer或java.lang.Boolean
[Proxy: 代理]
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
}
代码实现:
抽象角色和真实角色和之前的一样
- Rent.java即抽象角色
//抽象角色:租房
public interface Rent {
public void rent();
}
- Host.java即真实角色
//真实角色:房东,房东要出租房子
public class Host implements Rent{
public void rent(){
System.out.println("房屋出租");
}
}
- ProxyInvocationHandler.java 即代理角色
public class ProxyInvocationHandler implements InvocationHandler {
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
//核心:本质利用反射实现
Object result = method.invoke(rent,args);
fare();
return result;
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
- Client.java
public class Client {
public static void main(String[] args) {
//真实角色
Host host=new Host();
//代理实例的调用处理程序
ProxyInvocationHandler pih= new ProxyInvocationHandler();
pih.setRent(host);//将真实角色放置进去
Rent proxy=(Rent)pih.getProxy();//动态生成对应的代理类!
proxy.rent();
}
}
- 结果
核心:一个动态代理,一般代理某一类业务,一个动态代理可以代理多个类,代理的是接口
3.4 动态代理的好处
静态代理有的它都有,静态代理没有的,它也有
- 可以使得我们的真实角色更加纯粹,不再去关注一些公共的事情
- 公共的业务由代理来完成,实现了业务的分工
- 公共业务发生拓展时变得更加集中和方便
- 一个动态代理,一般代理某一类业务
- 一个动态代理代理的是多个类,代理的是接口