12-回顾
- 昨天先先讲了什么框架:封装具有支撑性的半成品项目
- springmvc - servlet
- mybatis - jdbc
- 框架+业务逻辑 = 项目
- IOC/DI 和 AOP
- 昨天学习的都是IOC
- IOC是一种思想:自己对对象的控制权,交给了程序本身。
- spring创建的默认就是单例
- DI就是依赖注入,依赖就是一种关系,注入就是赋值。
- ApplicationContext\ClassPathXmlApplicationContext
- applicationContext.xml - 配置文件
- SSM当中简单的配置:项目启动的时候,spring 的配置文件自动加载。
- set注入、constructor-arg注入、p命名空间
- 构造器注入的时候,必须要有相匹配的构造器。
- 构造方法是怎么去选择构造器呢,这跟java代码的底层有关系。
- value和ref:字面量,引用数据类型
- 内部bean:在某个bean的内部,定义的bean,就叫做内部bean
- 集合:property标签当中有很多子标签
- list
- array
- set
- map:keyset、values、entryset
- 集合bean:utils命名空间
老师电脑桌面
13 - bean的作用域
一共有四种,singleton,prototype,request,session。
- singleton:单例的。
- prototype:多例的。
- request:一次请求中有效。
- session:一次会话中有效。
演示
作用域
我们设置为单例。
怎么验证是不是单例
从容器当中获取两次。
我们在对象当中将toString方法注释掉。
toString方法是Object方法。
Object当中的toString方法,默认输出的是内存地址。
怎么判断两个对象是否是一样
- 对象使用object的toString方法。
- 恒等于
- .equal方法只要没有经过重写,对象就是可以比较。
- string字符串代表的是值不可以改变的unicode序列,是一个常量。
验证单例和多例是否生效
- spring是通过反射获取对象,class.forName,newInstance方法创建新实例,必须要有无参构造。
- 怎么能够看起来更明显,看看spring是通过反射来创建对象的呢?
- 在对象的无参构造器当中,输出一句话,看看会不会打印。
从这里就能够看出来,创建的对象是单例的,因为我们打印了两个对象,但是构造方法当中的打印语句,就输出了一次。
如果我们在配置文件当中将scope修改为prototype,再次执行程序,可以看到下面的结果:
单例和多例的进一步演示
- 当我们是prototype的时候,只初始化容器,什么都不打印的。
- 当我们的bean是singleton的时候,只初始化容器,就会创建对象。
- 说明:我们的spring管理的对象,是单例的,在容器初始化的时候,就会直接创建。
如果spring当中有单例模式的bean,在初始化容器的时候,就会创建,这个对象。
总结
14-bean的生命周期
笔记
闲聊
杨博超聊自己儿子。
杨博超在老家开了一个母婴店。
进口的奶粉450一罐。纯吃奶粉,不吃母乳的,一星期都要不了,就吃完了,一个月,十罐奶粉。
演示bean的生命周期
创建一个普通的类,标注出来生命周期。
在servlet当中servlet是可以初始化的。这是因为servlet当中集成的httpServlet当中,有init方法。
但是现在是一个普通的类。
package com.atguigu.ioc.life;
public class Person {
private Integer id;
private String sex;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
System.out.println("2-依赖注入");
this.id = id;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person() {
System.out.println("1-创建对象");
}
@Override
public String toString() {
return "4-使用";
}
public void init() {
System.out.println("3-初始化");
}
public void destory() {
System.out.println("5-销毁");
}
}
为什么,只有三步呢?
这是因为,对象的初始化和销毁的方法,没有被调用。
怎么才能够让他们被调用呢?
非常简单,在我们的配置文件当中,配置bean的时候,绑定上去,就可以了。
我们就指定了对象的初始化方法和销毁方法。
这样就正常了。
没有销毁的原因是,容器关闭的时候,才会销毁bean。
15-bean的后置处理器
bean的生命周期,默认是有五个步骤。
但是有的程序员还希望在bean初始化前后的时候,做一些其他的操作。
这个就需要bean的后置处理器。
- 后置处理器,是放在初始化之前,或者初始化之后。
- 后置处理器,设置了之后,对spring管理的每一个bean都有效果的。
后置处理器需要实现一个接口
这个接口叫做BeanPostProcesser。
- 我们有没有见过一个接口,实现了它之后,没有报错,没有需要实现的方法。
- 有这种接口的,序列化的接口,就是这样的,里面没有定义任何的抽象方法。
- 就相当于是一个标识,实现了接口,就是实现了序列化。
这两个方法,一个是在初始化之后,一个是在初始化之前。
两个参数,一个是Object,一个是String,不知道这两个参数是干什么的。
在java当中,参数名字,都应该是见名知意的。所以这些arg0或者是arg1的参数,不是很好。
我们把实现方法删除了,然后按ctrl+鼠标左键,然后导入一下源码。
导入源码之后,我们再次实现抽象方法,就是下面的效果:
这些传入的参数,是用来干嘛的。
后置处理器是在初始化前后做一些操作。
上面实现方法当中的Object,就是经过你处理之后的,新的bean。
小代码演示
这是为了演示后置处理器。
我们这样写一个后置处理器。
然后我们想办法,将后置处理器,跟spring关联起来。
xml中配置后置处理器
这样就完成了配置。
测试效果
16-引用外部资源文件
引用外部属性文件
比如jdbc。驱动名称,连接地址,用户名和密码放在properties文件中。
数据库连接对象,也可以交给spring管理。
那么spring怎么在xml中配置数据库连接池对象呢?
创建一个配置文件
配置数据库连接池对象。
配置数据库连接池对象的时候,上面的蓝色标注的,就是实现类。
怎么看一下,这个实现类当中的属性呢?通过outline视图。
在这DruidDataSource类当中没有找到对应属性的时候,我们可以去父类当中看看有没有对应的,我们要找的属性。
我们先搜索一下父类:DruidAbstractDataSource。
闲聊
- DNF游戏密码
- QQ密码被盗
- 验证码的作用防止暴力登录,验证码一般是保存在session。
- 12306的验证码。
xml配置连接池对象
写一个测试类,测试一下效果:
获取连接对象
错误记录
create connection SQLException, url: jdbc:mysql://localhost:3306/godemo, errorCode 0, state S1000
java.sql.SQLException: Unknown initial character set index ‘255’ received from server. Initial client character set can be forced via the ‘characterEncoding’ property.
- 这个报错信息意思是咱们的MySql和一个叫做mysql-connector-java包不匹配
- 一般来说5.1匹配的是mysql8.0以下的版本
我是用的mysql是这个版本:
我的connector-java包是这个版本:
mysql-connector-java-5.1.7-bin.jar,这个包,要升级一下。
效果验证
配置db.properties文件
这里面driver的名字,我们最好是写成jdbc.driver。
xml当中配置properties文件加载
这里面property当中,有一个叫做location的name。
js中location对象
用途是,实现页面跳转。
location.href,指定页面跳转的路径。
location.reload,指定页面刷新。
xml配置properties
根据db.properties文件配置连接bean
我们就不适用上面的配置方式了。
我们使用下面的写法:
jsp页面的el表达式当中,我们就是使用${},来取值的。作用域当中取值。
在xml当中,是从资源文件当中取值。
mybatis当中,也有${},还有#{},相当于占位符。
测试一下:
没问题的。
第一种加载资源文件的方法
第二种加载资源文件的方法
导入context的命名空间:
17-自动装配和兼容性
笔记
装配是什么意思?
装配就是注入,注入就是赋值。
spring管理的bean就是一个对象,有多个属性和方法,方法是调用,属性是赋值。
spring属性赋值,可以叫做装载,就做注入,就是自动赋值。
- 昨天讲解的value和ref,就是手动装配。
- 还有一种,就是自动装配。
记住一句话
自动装配,只针对于非字面量的属性,也就是需要用ref的。
自动装配的属性,都是非字面量的属性。
写个例子
然后对上面的类,进行封装,创建set和get,重写toString。
创建配置xml文件
- 首先创建一个emp的bean。
- 给emp的car或dept属性赋值有两种属性
创建测试类
正常的。
现在进入自动装配的正题
自动装配就是自动注入。
bean标签当中有一个autowire的属性。
这个就是自动装配的意思。
- autowire
- byName
- byType
- constructor
- default
- no
测试byName
设置了byName之后,然后重新测试:
效果正常的。
byName是什么意思
通过name属性,自动装配。
也就是说car和dept属性,通过某种方式,自动赋值。
byName的策略,是什么呢?
就是spring管理的bean当中,bean的id,跟我们的emp的属性名,一致,就能够自动赋值。
byType
只要在spring管理的范围内,有一个bean能够为目标bean的属性赋值,那就自动装配。
比如说:
- bean的class属性,跟目标bean的属性类型一致,就能够自动装配。
- bean有一个class属性,目标bean的属性类型是父类,也能够自动装配。
- 目标bean的属性类型,跟spring管理的bean类型,bean子类,bean的实现类,都可以自动赋值。
- 这个就叫做兼容性。
总结byType
通过类型自动装配的时候。
- spring管理的、能够给属性赋值的bean
- 目标bean的属性,可以是赋值bean的同一类型、父类、接口类。
测试byType
第一步:
创建一个car的父类:
目标对象,就是car,修改引用属性:
引用属性的类型,是赋值bean类型的,父类。
测试,是否可以自动装配。
第二步:
创建目标对象,就是car,引用属性类型,对应的dept接口。
目标对象emp当中,car属性类型是CarExtends,是目标bean car的父类。
目标对象emp当中,dept属性类型是DeptI,是目标bean dept的接口。
这个时候,可以自动装配的吗?
测试效果如下:
18-byName和byType选用建议
说明
- autowire,会根据我们,设置的策略,为,非字面量的属性,自动赋值。
- byType类比
- 实现分层,service层,有接口层,也有实现层。
- dao层,有接口层,也有实现层。
- servlet里面创建service对象,service对象当中创建dao对象。
- servlet里面,创建的service对象,就相当于属性,到时候,可以通过自动装配的时候,为service对象赋值。
- 咱们在servlet当中,创建service对象的时候,一般都是创建的接口的对象。
- userService的接口,实现类叫做userServiceImpl
- private userService userService = new userServiceImple。
- spring当中管理的对象,都是针对于类的,不能够针对于接口和抽象类。
- 因为接口,不能够直接创建对象。
- 因为抽象类,也不能够创建对象。
出现的小问题
- 昨天getbean方法的时候,通过getbean(class),这时候,容器有两个class对应的bean,报错没有找到唯一的bean
- byType在spring容器中,找到两个,能够赋值的bean,它该选谁?
举例如下:
emp对象有car属性,spring当中有car1和car2,应该自动装配哪个?
byType自动装配时候,spring当中只能出现一个,能赋值的bean。
在,使用byType的过程中,要求spring容器中,只能够有一个,能够为目标对象属性赋值的bean。
笔记
选用建议
byName和byType都不够好。
最大的问题,bean当中添加了autowire属性,作用于bean中所有非字面量属性。
我有时候创建对象,不一定要为所有属性赋值。
问题1:设置autowire属性,会作用域该bean中,所有的非字面量属性。
因此,我们谁都不用。
19-基于xml的自动装配和基于注解的组件扫描
笔记
用过注解吗?Junit的@Test。
servlet的时候@webservlet。web项目,写servlet,要去web.xml中配置,也可以servlet加@webservlet,括号里面写访问路径,相当于web.xml当中配置了servlet。方便。
注解一般以@开头。
今天讲好几个注解。
- 之前,xml当中配置bean,给spring管理。
- 项目中有1000个bean,我咋办。
- 还去xml当中配置,累死你。
- 如果bean中大部分非字面量属性。不能autowire配置xml。
spring中的组件是什么?
spring的配置文件,由bean标签组成,组件就是bean。
注解
组件是bean,bean是对象,对象由类产生,是class实例化。
要先确定bean属于哪个class。
- @Component:组件、bean的意思、加在哪里呢?
- 写bean标签,id可以不加,class必须加。
- 注解加在类上、方法上、属性上。
- 这个要加在类上,就可以被spring管理,作为spring的bean。
- 一个类加上这个注解,spring就会对这个class产生的对象,进行管理。
- @Repository:持久层
- servlet:控制层,service:业务逻辑层,dao:数据访问层,持久层,不要念dao。
- 这个注解,应该加在持久层
- @Service
- @Controller
- 加在控制层
- springmvc的请求响应和处理的层。
- jsp+servlet的时候,加在servlet
四个注解的关系和区别:没有区别,一模一样,名字不一样,标识。
- @Component
- @Repository
- @Service
- @Controller
- 这个东西,springmvc时候再说,有区别。
- @Controller标识控制层,在web项目中,由servlet容器tomcat管理。
- web容器怎么找到对应的控制层,要根据注解分辨。
- 原来你是写servlet继承httpservlet,明显。
- 但是springmvc之后,控制层,就是普通类,它封装了servlet,不明显。
- web容器分辨不出来,根据@Controller注解。
- structs2封装的是过滤器,控制层是需要继承。
演示
测试验证
- 咱们加了注解。
- 咱们在配置文件当中,添加了扫描包。加上注解的类,就是组件。
解释
加了注解之后,就相当于在xml当中,自动生成了相对应的bean,这些bean的id就是类的名称的首字母小写为值,class就是类的全限定名。
我们在测试类当中,验证通过id来获取,就是正常的。
扫描包的小问题
2019年6月12日 - 上午 - 结束
20-扫描组件之包含和排除
通过注解让spring管理bean,第一步加上注解,第二步,spring的xml写context:component-scan,有个属性叫做base-package,里面要写包结构。会在spring的配置文件当中自动生成,相应的bean标签,会有默认的id,以类的首字母小写为值。
这个过程,大家一定要注意。如果说咱们在使用的过程中,只有加了注解,没有扫描,spring不会管理这些类。
这两个步骤是缺一不可的。
所以说,原来在,教学的过程中,有的同学,会问加了注解,会问写了扫描组件标签是什么意思。
这两个东西同时存在才会有意义。
扫描包的时候,basepackage,这个属性里面写的一定是一个包结构。
这个包结构,看着是包,文件系统当中,是多个目录的。
com.atguigu.ioc,当中com就是一个文件夹。
包写越大,扫描到的类也越多,扫描时间长,也很可能做无用功。咱们尽量写的准确一些。
两个包是需要spring管理的,可以具体写出来,用逗号分隔。
如果以后需要管理的包非常多。怎么办呢?
举例:
如果包特别多,单独扫描某些包,或者把某些包排除掉,怎么实现呢?
包含和排除
- context:exclude-filter
- context:include-filter
- ?
- 假设我们扫描的就是userMod这个包,只要求扫描一个controller,应该怎么写呢?
- annotation是注解的一i是,type属性当中,注解的类型,就是
- expression是表达式的意思,这里填写:org.springframework.stereotype.Controller。
测试效果是:
这个效果,没有成功。
为什么呢?
use-default-filters属性
如果是要使用include或者exlude的时候,就要设置这个东西。
修改后测试
效果是正常的,成功的。
include的另外的设置方式
这是根据类型来进行包含。
笔记
排除
这样写,是不行的,一定是要把use-default-filters="false"去除掉,使用exclude-filter的时候。
测试效果是:
小总结
包含和排除的用法
context:component-scan:能够写多个包含
context:component-scan:能够写多个排除
不能够包含和排除同时写。
21-基于注解的自动装配
上午的时候,我们写了自动装配,推荐是不要用,也就是说,千万不要在xml当中写自动装配autowire属性。
在控制层写个方法,接收请求。
这里是一个处理,添加用户信息请求的方法。
这里需要调用service。
我们需要通过接口,创建service对象,然后调用service当中的addUser方法:
第二步,我们需要在接口当中定义一个addUser()方法。
第三步,我们需要在serviceImpl当中,实现这个addUser()方法。
在service当中处理完业务逻辑之后,需要将数据,保存到数据库当中的。
这个工作,咱们可以在serviceimpl当中写。但是service都干了,要dao干啥呢。
我们需要在这个serviceimpl类当中,创建userDao类。
第三步,在userDao接口类当中定义一个addUser()方法,然后在userDaoImpl当中实现这个方法。
测试
我们在测试类当中通过uc.addUser()自动调用controller当中的方法。
原来我们通过servlet的dopost方法是自动调用。
不推荐使用bean标签autowire属性的第二个原因
因为你后面会大量使用注解来把对象创建工作交给spring,spring会根据注解和组件扫描,自动在xml当中生成bean标签。
但是这些bean标签,我们是看不到的。
所以,我们连bean标签都看不到,我们还使用个鬼的autowire属性。
这个时候,我们怎么实现自动装配呢?
我们可以利用一个组件,叫做@autowired。这个注解,可以加在我们想要自动装配的属性上面。
我们前面讲解自动装配的时候,已经说过了,自动装配是对于对象bean当中的非字面量的属性来进行自动装配的。
我们在我们的controller类当中,我们设置了service属性,这是一个service对象。这是需要自动装配的。
我们在我们的service类当中,我们设置了dao属性,这是一个dao对象。这也是需要自动装配的。
自动装配的意思就是进行赋值,从spring管理的bean当中,找到对应的bean,然后进行赋值。
所以,下面颜色标注的内容,我们是不需要写的。
我们既然就已经写了@autowired的时候,我们是不需要进行new对象的。
这种写法是没有必要的。
我们可以直接写成下面的例子:
在这里,我们只需要创建一个接口对象,就可以了。
这里,是通过什么类型来进行自动装配的呢?
上午的时候,我们只有讲解了byName和byType两种方式的自动装配。
byName是在spring管理范围内寻找到对应的id是等于属性值的bean,然后进行赋值。
我们这里的属性就是userService,这是一个接口类,在spring的管理范围内,不会有接口类对应的bean的。
因为接口类是不能够创建对象的。
byType是在spring管理范围内寻找到唯一的一个,属性类型和bean类型一致的、或者属性类型是赋值bean的父类,或者属性类型是赋值bean的接口的情况,实现自动装配的。
我们这里的userService是一个接口的,它有一个实现类,这个实现类就是,userServiceImpl,这个就是属性userService类型UserService的接口类,是只有一个的,是可以自动赋值和装配的。
所以,@Autowired进行自动装配的时候,是通过byType的方式来实现自动装配的。
测试效果
22-组件管理总结
个人总结
下面再做一个操作
我们想一种情况,让spring的@Autowired的byType自动装配模式失效。
我们在xml当中写一个bean。
我们写的这个bean就是所谓的赋值bean,这个bean的类型,是目标bean也就是service属性userDao,属性类型的实现类。
是可以给属性进行自动装配赋值的。
现在我们通过@Repository注解的实现类,也是具有自动装配赋值的。
这个时候,我们进行测试的时候,就会报错了:
这是因为byType进行自动装配的时候,是不能够有2个赋值bean的。
疑问:如果autowire,只有byType的这么一种模式的话,用起来也不是很方便
假设,我们这样操作:
我现在来仔细描述一下,现在的情况。
第一,现在在目标bean,也就是userServiceImpl类当中有一个属性,属性的名字叫做userDao,属性的类型是userDao。
第二,现在,在spring管理范围内,类型是userDao实现类UserDaoImpl的,有两个bean,一个是@Autowired自动装配的类,一个是在xml当中手动配置的类。自动装配的类的id叫做userDaoImpl,是类的名字的首字母大小的。手动配置的类的id是,我们手动设置成为了userDao。
第三,这个时候,我们进行测试,byType自动装配类型是没有成功的,但是byName自动装配类型是成功的。
错误记录
第一,我们在userService当中自动装配了userDao属性。
第二,我们将userDaoImpl上的@Repository注解去掉了。
第三,这个时候,spring管理范围内,没有能够给userDao属性自动装配的bean。
这个时候,的错误,是长这个样子的:
@Autowired(required=false)
如果这样设置,就算是装配不成功,spring也不会报错的。
但是可能项目会报空指针异常。
Scarlett Ingrid Johansson (/d?o??h?ns?n/; born November 22, 1984) is an American actress.
斯佳丽·英格丽德·约翰逊(/d?o??h?ns?n/;生于1984年11月22日)是美国女演员。
She was the world's highest-paid actress in 2018 and 2019,
and has featured multiple times on the Forbes Celebrity 100 list.
她是2018年和2019年全球收入最高的女演员,
并多次入选福布斯名人100强。
Her films have grossed over $14.3 billion worldwide,
making Johansson the ninth-highest-grossing box office star of all time.
她的电影在全球票房超过143亿美元,
使得约翰逊成为有史以来票房第九高的明星。
She is the recipient of various accolades,
including a Tony Award for Best Featured Actress in a Play
and a BAFTA Award for Best Actress,
as well as nominations for two Academy Awards
and five Golden Globe Awards.
她获得了各种荣誉,
包括托尼奖最佳戏剧女主角
和英国电影电视艺术学院奖最佳女主角,
以及两项奥斯卡奖
和五项金球奖的提名。
@Controller的value属性
我们可以在@Controller当中的value属性当中,设置,spring自动生成的bean的id。
默认生成的bean,是使用类名的首字母小写,作为id的。
如果设置了value,就会使用你自定义的aaa。
如果你在小括号当中,写了多个属性,你就必须严格按照,属性名等于属性值,这种格式来进行书写。
如果你只写了一个属性,比如说是value属性,那么,也可以省略属性名。
再创建一个dao实现类
Born and raised in Manhattan, New York City,
Johansson aspired to be an actress from an early age and first appeared on stage in an Off-Broadway play as a child actor.
在纽约曼哈顿出生长大,
约翰逊从小就渴望成为一名演员,并作为一名儿童演员首次出现在百老汇以外的戏剧舞台上。
She made her film debut in the fantasy comedy North (1994),
and gained early recognition for her roles in Manny & Lo (1996), The Horse Whisperer (1998), and Ghost World (2001).
她在奇幻喜剧《北方》(1994)中首次出演电影,
并因在《曼尼&洛》(1996年)、《马语者》(1998年)和《幽灵世界》(2001年)中的角色而获得早期认可。
Johansson shifted to adult roles in 2003 with her performances in Sofia Coppola's Lost in Translation, which won her a BAFTA Award for Best Actress, and Girl with a Pearl Earring.
She was nominated for Golden Globe Awards for these films, and for playing a troubled teenager in the drama A Love Song for Bobby Long (2004), and a seductress in Woody Allen's psychological thriller Match Point (2005).
Other works during this period include Christopher Nolan's The Prestige (2006) and Allen's Vicky Cristina Barcelona (2008), and the albums Anywhere I Lay My Head (2008) and Break Up (2009), both of which charted on the Billboard 200.
这个时候userService这个目标bean当中的userDao属性,就有了两个可以进行byType自动装配的赋值bean。
一个就是UserDaoImpl,一个就是UserDaoMybatisImpl。
这就是之前手动创建相同类型赋值bean的情况。
我们还可以通过一个注解,指定,我们使用哪一个赋值bean来进行自动装配的。
@Qualifier
@Autowired是要和@Qualifier两个注解,同时使用的。
@Qualifier(value=“bean的id”)
这个注解就是指定的意思。
以后具体的情况,就使用具体的方法。
多说一点,看一下,这个@Qualifier注解的源码:
这里面,Target注解后面表示,这个注解,可以用在属性上面,也可以用在方法上面的。
小说明
原来我们,通过手动配置xml的时候,说过,配置的bean,都要求,对应的类当中要有set方法,这是 通过set注入的。
但是你发现,我写了@Autowired之后,自动装配的时候,根本就没有在我们的userDao当中写set方法的。
@Qualifier应用在方法上
这里的UserServiceImpl就是目标bean,有一个属性,叫做userDao,我们可以直接,在属性上面,进行自动装配,如果有属性有set方法的话,我们可以在set方法上面进行自动装配。
笔记
基于注解的组件化管理: @Component,@Controller-控制层,@Service-业务层,@Repository-持久层 以上四个注解功能完全相同, 不过在实际开发中,要在实现不同功能的类上,加上相应的注解。 完成组件化管理的过程: 1、在需要被spring管理的类上,添加相应的注解。 2、在配置文件中通过<context:component-scan> 标签对所设置的包结构进行组件扫描,就会将加上注解的类,作为spring的组件进行加载。 base-package属性 user-default-filters属性 context:include-filter标签 context:exclude-filter标签 组件:指spring中管理的bean, 作为spring的组件进行加载:会自动在spring的配置文件中生成相应的bean, 这些bean的id会以类的首字母小写为值。 也可以通过@Component(“beanId”)为自动生成的bean,指定id。 自动装配:在需要赋值的非字面量属性上,加上@Autowired注解,就可以实现自动装配。 就可以在spring容器中,通过不同的方式,匹配到相对应的bean。 默认使用的方式是byType,此时要求spring容器中只有一个能够为其赋值。 如果byType不行的话,就会自动切换到byName的方式进行自动装配。 当byType实现不了装配的时候,然后会自动切换到byName。 此时要求spring容器中具有一个bean的id是和属性名一致的。 如果自动装配的时候,匹配到了多个能够自动装配的bean, 可以使用@Qualifier(value=“beanId”)这个注解, 指定使用自动装配的bean。 @Autowired和@Qualifier可以一起作用于,带有形参的方法上面。 注意,这个时候,这个@Qualifier注解所指定的bean,作用于方法的形参。
在java当中也为我们提供了一个自动装配的注解叫做@resource,这个注解是和@Autowired功能是一模一样的,都是为了实现自动装配。 只不过@Autowired是先要byType,不行了然后byName。 @Resource是先要byName,不行了然后byType。
讲解到这里的时候,我们的IOC的部分就已经结束了。
佟丽娅回顾
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e6kM51IS-1633184495828)(https://programzsximages.oss-cn-beijing.aliyuncs.com/ssm/IOC.svg)]
23 - AOP前奏
引子
IOC是通过工厂模式实现的。 AOP是通过代理模式实现的。 大家都学过动态代理吗? 学过的。 代理模式要理解还是简单。 咱们理解的话,拿着静态代理理解。 代理模式有三个角色,三个对象。 原始对象 代理对象 目标对象。 生活的例子很多。 我完成某个事,我不想自己动,我找个代理对象,帮我干。 但是有个原则,保证最终的结果不变。 不管自己完成还是代理对象完成,结果不变。 找房子,我白天上班,没时间,我找中介,中介帮我找。 最终的结果,找到房子,不变。 别房子没找到,找到老婆。 天为被,地为床,旁边有人就行。
闲聊
尽量别找中介。 2014年5月份来北京。 2015年博超找房子。 当时在唐家岭住。 有小公寓。 特别便宜,一个月800,一个单间。 空调、洗衣机、冰箱、热水器、柜子、桌子。 加上水电费,不超过1000。 特别好。 2015年开始,北京建筑各种非法公寓强拆。 当时,博超去上班,出门了很多营地。 有很多广播。 限定几天之内搬走,不搬走,后果自负。 一看就是美国民主党的人。 一看没啥事。 第二天公寓墙就倒了。 问小伙子,都说没事,说了好几年了。 广播三天之内搬走。 第一天空调。 第二天热水器。 第三天床。 真的是这样,博超都丢了。 不敢住了。 睡觉砸进去,有什么办法。 说理有点难。 晚上就搬走了,找房子。 公寓老板都跑到了法国。 找到一个小区,两居室。 和朋友一起。 一个月4000块。 想要住,押一付三行规。 找了中介,押一付三再交一个月,给中介费。 非常贵。 不住。找了个大院,2个屋子,不是小区。 一个月3300元。 上班之后,各个地方找房子房价。 往后结婚了。 两个人去霍营地铁站。 旁边有个公寓。 35平米一个月三千三。 加上水电费,差不多4000块。 就是35平米。 不敢有什么大动作。 又找了个房子。 在昌平。 有个小区功华新村。 一个月3500,是两居室。 去了上海之后,那就不一样了。 上海的房子有的地方确实是贵。 上班在海边,走路10分钟,开车2分钟。 开窗就能看见海。 当时在地图上的最南边。 一个月1800,三居室 一星期一次台风。 台风大,不让出门,别家待了。 路没路灯。 特别偏,还穷。 晚上要开远光。 那边人喜欢晚上出去遛弯。 而且有人特别过分。 正儿八经躺在路中间睡觉。 房子便宜。 大家以后找房子。 最好不要一个人住。 几个人帮衬。 一个人住太贵。 尽量别找中介。
AOP前奏
写一下小案例。
先写一个接口,定义一个基本的功能。
然后将加减乘除都放到这个接口当中。
写完接口之后,下面写一个实现类,功能简单写一下。
java当中的除法就是取整,取模就是取余数。
javascript当中的除法就是正规的除法,该有小数点,就有小数点。
写一个测试类
需求里面要求,写日志。
日志功能是项目中,经常会用到的功能,会记录变量的变化情况。
这个东西,很有必要。
需求:日志记录,传参是什么,实现的方法是什么,结果是什么。
假设我们这样写日志:
就是用system.out.println语句来模仿,日志。
我们再次执行一下,就有效果了:
如果调用其他方法,也是一样的效果。
思考
这样写好不好?不好。
这是接口的实现类,是应该实现一些功能的。我这里写了很多的日志信息,代码很乱。
写一行功能,加几行日志,这不是有病吗?
这种写法,是不可以的。
不能将日志功能,写到具体实现某个功能的类中。
业务逻辑的类,只写业务逻辑的代码。
这个时候,就应该怎么办?
大家发现日志格式都是差不多。
能不能把日志功能抽取出来。作为一个类。
业务类当中,通过日志类,就可以解决所有的记录日志的问题。
第一步:将日志代码,都要放在日志类中。
第二步:业务类中需要记录的位置,让日志类生效。
这个就需要用,动态代理。
动态代理
动态代理的作用:能够为任何需要代理的对象,自动生成代理类。
代理模式中有三个元素:原始对象,代理对象,目标对象。
代理对象是生成的。目标对象就是要去做的事。
什么是动态代理,不管目标对象是什么,都可以通过一个X类,生成相对应的代理对象,让代理对象帮助我去完成功能。
这个X就是动态代理。X跟目标对象没有任何关系。
所以这个X更像是一个工具类。能够帮助程序员生成动态代理对象。
第一步,确定目标对象。
在这里例子当中,目标对象就是MathIImplI。就是代理对象要作用的类。
这里面我们的目标对象,就是MathImplI。
但是根据之前的说法,动态代理,是能够为任何的目标对象,生成代理对象的。
所以,我们的目标对象的类型,不能局限在MathImplI这个类型。
所以,我们应该写成Object。
但是,我们暂时先这样写。
第二步,工具类具有获取代理对象的方法。
现在我们是在动态代理工具类当中,是需要根据目标对象,获取代理对象的。
所以我们的动态代理工具类当中肯定是有一个方法,要返回一个代理对象的。
第三步,代理对象怎么获取呢?
如何创建代理对象呢?
代理原理
在jdk当中,为我们提供了一个Proxy的类。
- 这个Proxy的类,是在java.lang.reflect这个包当中。
- 这个反射包中的Proxy类,有一个方法,叫做newProxyInstance方法,可以生成一个新的代理对象。
- 所以,我们现在用的方式,叫做jdk动态代理。
- 我们经常用的动态代理有两种,jdk动态代理,cglib动态代理。
- jdk动态代理要求,目标对象必须要有接口。
- cglib动态代理,要求,目标对象必须要有继承关系。
- 这个newProxyInstance方法中有三个参数
- ClassLoader:
- interfaces:
- InvocationHandler
- 这三个参数,分别是什么意思。
- 动态代理要有一个原则,不管动态代理对象完成的事情,还是不用代理完成的事情,结果必须是一样的。
- 一定要保证结果的一致性。
- 我给动态代理工具类,传进去了目标对象,生成代理对象。
- 我不管这个代理对象中间如何添油加醋。
- 最终我要的结果必须是要一致的。
- 这个一致性原则,对于项目来说,如何保证结果的一致呢?
- 代理对象完成的结果,跟目标对象完成的结果,要保持一致,怎么做呢?
- 让代理对象实现功能的时候,最终去调用目标对象的方法,就可以了。
- 第一个参数:ClassLoader类加载器。
- 代理对象是我们通过jdk动态代理,通过java.lang.reflect.Proxy.newInstance动态生成的一个代理对象。创建一个代理对象。
Returns an instance of a proxy class for the specified interfaces
that dispatches method invocations to the specified invocation handler.
返回指定接口的代理类的实例
将方法调用分派给指定的调用处理程序。
- 什么叫做动态生成,就是原来没有。这一段代码执行了之后,才有的。
- 对象是依赖于类创建的。对象是依赖于类存在的。
- 如果有一个类,你就把它放在那里,不对它进行任何操作,它会加载和执行吗?
- 一个类要想被加载和执行,必须要有类加载器。
- 所以,ClassLoader是用来设置,当前代理对象所属类的类加载器。
- 只有有了这个类加载器,你的代理对象所属类,才能够被加载。
- 这个类才能够创建代理对象。
- 去哪里寻找类加载器呢?
- 我们的动态代理工具类,ProxyUtil这个工具类,就需要类加载器。
- 那么ProxyUtil这个工具类,和代理对象所属类,可不可以,共用一个类加载器。
- 一个类加载器是只能够加载一个类吗?不是的。它能够加载多个类。
- 所以,我们让代理对象所属类,和当前的动态代理工具类,使用一个类加载器。
- 我们通过this,表示当前的对象,this.getClass()获取这个动态代理工具类,然后通过getClassLoader()获取动态代理工具类的类加载器。
- 第二个参数叫做interfaces
- 这个参数,也很好理解,代理对象要帮助我们完成功能,它最起码要知道,你要实现什么功能。
- 咱们在代码当中表现的就是目标对象中的方法。
- 目标对象当中的方法,就是实现类,从接口当中继承过来的。
- 所以要想知道代理对象最终帮助我们完成什么,很简单。
- 只需要知道目标对象所实现的所有的接口,就可以了。
- 我要想让一个类和另外的一个类,具有相同的功能, 就让他们实现相同的接口就可以了。
- 所以第二个参数,就是,mathImpl.getClass().getInterfaces()。
-
第三个参数的类型,叫做InvocationHandler,Invocation叫做执行,Handler叫做处理,InvocationHandler叫做执行处理器。 -
用来干什么?用来设置,代理对象如何实现目标对象的功能。 -
第二个参数中已经知道了全部的目标对象的功能。 -
第三个参数就是要如何实现这样的功能呢? -
这里要用到一个接口,就是InvocationHandler接口。 -
这个接口里面只有一个方法。 -
那么在这里,我们怎么写第三个参数呢? -
第一种方法,让动态代理工具类,实现InvocationHandler接口。然后重写抽象方法。
- 然后第三个参数这样写:
- 因为第三个参数是需要一个InvocationHanlder。这个InvocationHandler是一个接口。
- 第三个参数,我们可以使用一个接口的实现类,来进行赋值。
- 我们可以将当前工具类,变成InvocationHandler的接口实现类。
- 第三个参数是InvocationHandler,这个接口或者这个接口的实现类,就是一个作用。
- 当代理实例,需要进行方法调用的时候,由InvocationHandler来执行方法,并返回结果。
Processes a method invocation on a proxy instance and returns the result.
处理代理实例上的方法调用并返回结果
This method will be invoked on an invocation handler
when a method is invoked on a proxy instance that it is associated with.
当在代理实例上调用方法时,此方法将在调用处理程序上调用
使用匿名内部类
- 功能在java中表现为:方法。
- 方法要想执行,在反射中,用的就是invoke方法。
- 所以上面的invoke,就是定义,方法应该如何执行的。
- InvocationHandler是用来设置,代理对象实现目标对象功能,是如何实现的。
- 如何去写invoke呢?
invoke方法
第一参数,代理对象。
第二个参数,method,方法。
第三个参数,args,参数。
invoke这个方法大家应该用过好多次,在反射当中,大家通过getMethod,getDeclaredMethod,获得一个method对象的时候,method对象当中,就会有一个invoke方法。invoke里面刚好就是两个参数。第一个参数用来指定调用方法的对象。第二个参数用来指定执行方法的时候的形参列表的。因为java的方法之中是存在重载的,方法同名但是不同参,同名不同参,与返回值无关。
重新解释
package com.atguigu.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyUtil {
private MathImpl mathImpl;
public Object getProxy() {
ClassLoader loader = this.getClass().getClassLoader();
Class[] interfaces = mathImpl.getClass().getInterfaces();
Object proxyObject = Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(mathImpl, args);
}
});
return proxyObject;
}
}
动态代理总结
|