文章目录
1. 程序的耦合
耦合:程序间的依赖关系,包括类之间的依赖关系,方法之间的依赖关系
解耦:降低程序间的依赖关系
实际开发中,应该做到:编译期不依赖,运行时才依赖。
2. 解耦的思路
第一步:使用反射来创建对象,而避免使用new关键字
//1.注册驱动
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());//使用new关键字,则在编译器就创建对象,形成依赖
Class.forName("com.mysql.jdbc.Driver");//使用反射来创建对象,括号里面只是表示字符串
第二步:通过读取配置文件来获取要创建的对象全限定类名
3. Bean
Bean:在计算机英语中,有可重用组件的含义。
JavaBean:用Java语言编写的可重用组件。
创建Bean对象的工厂:创建service和dao对象的。
如何创建:
第一个:需要一个配置文件来配置我们的service和dao
配置的内容:唯一标识=全限定类名(key=value)
第二个:通过读配置文件中配置的内容,反射创建对象。
4. 加载路径信息
不要用
InputStream in = new FileInputStream("");
需要使用类加载器
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
5. 单例模式和多例模式
单例的对象只被创建一次,从而类中的成员也就只会初始化一次。
多例的对象被创建多次,执行效率没有单例对象高。
6. 控制反转(解耦,降低程序间的依赖关系)
控制反转(Inversion of Control,IoC)把创建对象的权利交给框架,是框架的重要特征。包括依赖注入(Dependency Injection,DI)和依赖查找(Dependency Lookup)。
IOC的作用:削减计算机程序的耦合
7. 获取spring的IoC核心容器,并根据id获取对象
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
ApplicationContext的三个常用实现类:
ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下,不在则无法加载。
FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
AnnotationConfigApplicationContext:它是用于读取注解创建容器的。
8. 核心容器的两个接口引发出的问题:(BeanFactory是ApplicationContext的父接口)
ApplicationContext:单例对象适用
它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。
BeanFactory:多例对象适用
它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式,也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。
9. Spring对Bean的管理细节
9.1 创建Bean的三种方式
- 第一种方式:使用默认构造函数创建。在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
- 第二种方式:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
- 第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>
9.2 bean的作用范围调整
bean标签的scope属性:
作用:用于指定bean的作用范围
取值:常用的就是单例和多例的
singleton:单例的(默认值)
prototype:多例的
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype"></bean>
9.3 bean对象的生命周期
单例对象
出生:当容器创建时对象出生
活着:只要容器还在,对象一直活着
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
多例对象
出生:当我们使用对象时spring框架为我们创建
活着:对象只要是在使用过程中就一直活着
死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
10. spring中的依赖注入(Dependency Injection)
IoC的作用:降低程序间的耦合(依赖关系)
依赖关系的管理:以后都交给spring来维护
在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明
依赖关系的维护:就称之为依赖注入。
依赖注入:
能注入的数据:有三类
基本类型和String
其他bean类型(在配置文件中或者注释配置过的bean)
复杂类型/集合类型
注入的方式:有三种
第一种:使用构造函数提供
第二种:使用set方法提供
第三种:使用注解提供
-
构造函数注入:
使用标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值,索引的位置是从0开始的
name:用于指定给构造函数中指定名称的参数赋值(常用的)
=以上三个用于指定给构造函数中哪个参数赋值=
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的是就是在spring的IoC核心容器中出现过的bean对象。
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">//在默认情况下,使用查找到的类的默认构造函数进行实例化 <constructor-arg type="java.lang.String" value="test"></constructor-arg>//当没有默认构造函数时,则这一行代码,传递给构造函数的参数值 <constructor-arg name="name" value="test"></constructor-arg> <constructor-arg name="birthday" ref="now"></constructor-arg> </bean> <!-- 配置一个日期对象 --> <bean id="now" class="java.util.Date"></bean> public class AccountService{ private String name; public AccountService(String name){//构造函数方法注入 this.name = name; } }
优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
-
set方法注入(调用类中的set方法)(最常用)
涉及的标签:property
出现的位置:bean标签的内部
标签的属性
name
value
ref
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2"> <property name="name" value="test"></property> <property name="age" value="21"></property> <property name="birthday" ref="now"></property> <bean> public class AccountService{ private String name; public void setName(String name){//set方法注入 this.name = name; } }
优势:创建对象时没有明确的限制,可以直接使用默认构造函数。
弊端:如果有某个成员必须有值,则获取对象时有可能set方法没有执行。
-
复杂类型的注入/集合类型的注入
用于给List结构集合注入的标签:list,array,set
用于给Map结构集合注入的标签:map,props
结构相同,标签可以互换
<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3">
<property name="myStrs">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="myList">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="mySet">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>BBB</value>
</entry>
</map>
</property>
<property name="myProps">
<props>
<prop key="testC">ccc</prop>
<prop key="testD">ddd</prop>
</props>
</property>
<bean>
public class AccountServiceImpl implements IAccountService{
private String[] myStrs;
private Map<String,String> myMap;
private Properties myProps;
public void setMyStrs(String[] myStrs){
this.myStrs = myStrs;
}
public void setMyProps(Properties myProps){
this.myProps = myProps;
}
}
11. 注解配置
曾经的XML的配置:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
scope="" init-method="" destory-method="">
<property name="" value="" | ref=""></property>
</bean>
用于创建对象的:
他们的作用就是和在XML配置文件中的编写一个<bean>
标签实现的功能是一样的
@Component
:
**作用:**用于把当前类对象存入spring容器中
**属性:**value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
@Controller
:一般用在表现层
@Service
:一般用在业务层
@Repository
:一般用在持久层
以上三个注解的作用和属性与@Component
是一模一样的,是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰。
<!-- 告知spring在创建容器时要扫描的包,配置所需要的标签不是在bean的约束中,而是一个名称为context名称空间和约束中 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
用于注入数据的:
他们的作用就和在XML配置文件中的bean标签中写一个<property>
标签的作用是一样的
@Autowird
:
**作用:**自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。
如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
**如果Ioc容器有多个类型匹配时,**如下图,@Autowired
中的IAccountDao类型会与Ioc容器中的value进行匹配,若找到两个相同的,则@Autowired
中accountDao变量名称会与IoC容器中的key进行匹配,若找到相同的变量名称,则匹配成功,否则则报错。
**出现位置:**可以是变量上,也可以是方法上。
**细节:**在使用注解注入时,set方法就不是必须的了。
@Qualifier
:
作用: 在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是给方法参数注入时可以。
**属性:**value:用于指定注入bean的id。
@Autowired
@Qualifier("accountDao1")
private IAccountDao accountDao = null;
public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
return new QueryRunner(dataSource);
}
@Resource
:
**作用:**直接按照bean的id注入。它可以独立使用
**属性:**name:用于指定bean的id。
@Resource(name="accountDao2")
private IAccountDao accountDao = null;
以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。另外,集合类型的注入只能通过XML来实现。
@Value
:
**作用:**用于注入基本类型和String类型的数据
**属性:**value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)
SpEL的写法:${表达式}
用于改变作用范围的:
他们的作用集合在bean标签中使用scope属性实现的功能是一样的
@Scope
**作用:**用于指定bean的作用范围
**属性:**value:指定范围的取值。常用取值:singleton prototype
和生命周期相关:
他们的作用就和在bean标签中使用init-method和destory-method的作用是一样的
@PreDestroy
:
**作用:**用于指定销毁方法
@PostConstrust
:
**作用:**用于指定初始化方法
12. Spring的新注释
@Configuration
:
**作用:**指定当前类是一个配置类
**细节:**当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
@ComponentScan
:
作用:用于通过注解指定spring在创建容器时要扫描的包
**属性:**value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。
我们使用了此注解就等同于在xml中配置了:
<context:component-scan base-package="com.itheima"></context:component-scan>
等同于:
@ComponentScan(basePackages="com.itheima")
@Bean
:
作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
**属性:**name:用于指定bean的id。当不写时,默认值是当前方法名称。
**细节:**当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象
查找的方式和Autowired注解的作用是一样的。
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name="runner")
@Scope("prototype")
//变成多例对象
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.cj.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy? useUnicode=true&characterEncoding=utf8");
ds.setUser("root");
ds.setPassword("123456");
return ds;
} catch (Exception e){
throw new RuntimeException(e);
}
}
等同于:
<!-- 配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!-- 注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!-- 配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy?useUnicode=true&characterEncoding=utf8"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
更改获取容器的方法:
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
@Import
:
**作用:**用于导入其他的配置类,一般用在主配置类中导入其他子配置类
**属性:**value:用于指定其他配置类的字节码。
当我们使用Import的注解之后,有Import注解的类就是父配置类,而导入的都是子配置类。
@ComponentScan("com.itheima")
@Import(jdbcConfiguration.class)
等同于:
@ComponentScan({"com.itheima","config"})
@PropertySource
:
作用:用于指定properties文件的位置
属性:value:指定文件的名称和路径。
关键字:classpath,表示类路径下
@PropertySource("classpath:config/spring/jdbcConfig.properties")
13. Spring整合junit的配置
-
导入spring整合junit的jar坐标
-
使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的
@Runwith
-
告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
@ContextConfiguration
locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
classes:指定注解类所在的位置@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfiguration.class) public class AccountServiceTest { @Autowired private IAccountService as; }
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:bean.xml")
**细节:**当我们使用spring5.x版本的时候,要求junit的jar必须是4.12及以上
14. 动态代理
特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法增强
分类:
基于接口的动态代理
基于子类的动态代理
基于接口的动态代理:
涉及的类:Proxy
提供者:JDK官方
如何创建代理对象:
使用Proxy类中的newProxyInstance方法
创建代理对象的要求:
被代理类最少实现一个接口,如果没有则不能使用
newProxyInstance方法的参数:
ClassLoader:类加载器
它是用于加载代理对象字节码的,和被代理对象使用相同的类加载器。
Class[ ]:字节码数组
它是用于让代理对象和被代理对象有相同的方法,固定写法。
InvocationHandler:用于提供增强的代码
它是让我们写如何代理。我们一般都是写一个该接口的实现类。通常情况下都是匿名内部类,但不是必须的。
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler(){
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* 方法参数的含义
* proxy:代理对象的引用
* method:当前执行的方法
* args:当前执行方法所需的参数
* return:和被代理对象方法有相同的返回值
* throws Throwable
*/
@Override
public Object invoke(Object proxy,Method,Object[] args) throws Throwable{
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())){
returnValue = method.invoke(producer,money*0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(10000f);
基于子类的动态代理:
涉及的类:Enhancer
提供者:第三方cglib库
如何创建代理对象:
使用Enhancer类中的create方法
创建代理对象的要求:
被代理类不能是最终类
create方法的参数:
Class:字节码
它是用于指定被代理对象的字节码。
Callback:用于提供增强的代码
它是让我们写如何代理。我们一般都是写一个该接口的实现类。通常情况下都是匿名内部类,但不是必须的。
此接口的实现类都是谁用谁写。
我们一般写的都是该接口的子接口实现类:MethodInterceptor
Enhancer.create(producer.getClass(),new MethodInterceptor()){
/**
* 执行北地阿里对象的任何方法都会经过该方法
* proxy
* method
* args
* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
* methodProxy:当前执行方法的代理对象
* throws Throwable
*/
@Override
public Object intercept(Object proxy,Method method,Object[] args,MethodProxy methodProxy){
//提供增强的代码
Object returnVlaue = null;
//1.获取方法执行的参数
Float money = (Float) args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())){
returnValue = method.invoke(producer,money*0.8f);
}
return returnValue;
}
cglibPorducer.saleProduct(12000f);
}
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
</dependencies>
15. AOP
面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,函数式编程的一种衍生泛型。
就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理技术,在不修改源码的基础上,对我们已有方法进行增强。
**作用:**在程序运行期间,不修改源码对已有方法进行增强。
**优势:**减少重复代码 提高开发效率 维护方便
AOP的实现方式:使用动态代理技术