day25-day26
一、Spring引入
首先,在之前设计分层架构时,添加一个功能需要很多步骤。把时间精力放在业务层而不是放在持久层和表现层。<br /> 结合设计模式,需要考虑到设计的流程和效率。
二、Spring
2.1 概念:
- spring是分层的JavaSE/EE应用的full-stack轻量级开源框架。
内核:IOC (反转控制)、AOP(面向切面编程);
控制反转:解耦的一种解决方案的统称。
提供:展现层(Spring MVC)、持久层(Spring JDBC)、业务层管理等众多企业级应用。
-
控制反转:
创建对象的权利发生了变化,创建对象的权力放在客户端、降低耦合
- 控制正转:由开发者创建对象。
- 解耦方式:
- 依赖注入:通过客户端进行构造方法,创建对象的方式
通过工厂模式:
通过一种标识,创建对应的类的实例:;例如可以使用全类名作为创建实例的的表示,通过反射机制创建实例。
延申————>Spring有自己对应的解耦方式
2.2 Spring的入门
2.2.1 发展历程:
- IBM :EJB思想
- EJB的发展
- 2017 最新Spring5.0
2.2.2 优势
- 方便解耦,简化开发。
- AOP编程支持: 使用面向切面编程,可以见简便地完成oop实现的工程。
- 声明式事物的支持: 提高开发效率
- 方便的测试:单元测试
- 方便集成各种优秀的框架:降低各种框架的使用难度。
- 降低了JavaEE API的使用难度 进行薄薄封装层。
- Java源码是经典学习范例。
2.2.3 Spring的体系结构
无论哪个模块都需要核心模块的支持,需要从核心模块获取实例。
2.2.3 Spring的体系结构
Spring 的相关jar包支持存放路径
D:\project\Spring5.02\spring-framework-5.0.2.release\spring-framework-5.0.2.RELEASE
Spring 通常使用xml来作为配置文件
- 使用demo4j来解析
通过xml方式进行配置
- 配置环境
需要的支持包:
- 点击打开:D:\project\Spring5.02\spring-framework-5.0.2.release\spring-framework-5.0.2.RELEASE\docs\spring-framework-reference\core.html
- 创建xml文件并黏贴入约束
- 获取约束方式:进入官网:ctrl+f 输入xmlns 查找约束
<?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">
</beans>
2.2.4 实例测试
- 首先,建立一个需要注册入spring核心容器的类
- 其次,注册
- 从核心容器中取出改类的实例:
- 使用到的方法:注意添加路径
- ClassPathXmlApplicationContext(“/xml/beans.xml”)
- 取出对象使用到的方法
- getBean(“students”)注意对象对应的参数是id,是核心容器中注册时对应的id。 ```java //创建核心容器对象 ClassPathXmlApplicationContext a = new ClassPathXmlApplicationContext(“/xml/beans.xml”);
//从核心容器中取出实例 Object students = a.getBean(“students”);
输出效果<br />
<a name="Q3bS2"></a>
### 三、Spring依赖注入
定义:组件之间的依赖关系有容器运行决定,提升组件的重用频率,搭建可拓展平台。不需要关心很多内容。
<a name="UlUM1"></a>
##### 3.1 通过set方法进行依赖注入
1. 在xml文件内进行,通过绑定类中的set方法,对数据进行赋值
简单数据:
```java
<bean id = "students" class="com.czf.pojo.students">
<property name="id" value = "123"></property>
<property name="name" value="jack"></property>
</bean>
对于简单数据而言,直接在bean下使用property 使用id绑定该类下的某个属性,该属性必须有set方法。
- 对于复杂数据而言,如日期类型的属性:这样的情况我们需要引入容器中已经存在的实例,在bean外部先申明,并引入该类型对应的全类名
```java
3. 对于数组类型数据的set注入
```java
<property name="strings">
<array>
<value>jack</value>
<value>tom</value>
</array>
</property>
- 给map注入数据(键值对的形式)
<property name="map">
<map>
<entry value="value1" key="key1"/>
<entry value="value2" key="key2"/>
</map>
</property>
3.2 通过构造方法进行依赖注入
<bean id="stuents" class="com.czf.pojo.students">
<constructor-arg name="name" value="zc"></constructor-arg>
<constructor-arg name="id" value="1222"></constructor-arg>
</bean>
- 需要注意平时在xml里创建实例需要类中必须要有无参构造
- 但是通过”构造方法进行注入“不需要无参构造(有也可以),需要将每个参数属性进行赋值后就不会报错。
3.3 BeanFactory和ApplicationContext的区别
- BeanFactory才是Spring容器中的顶层接口
- ApplicationContext是它的子接口。
主要区别:
- 创建对象的时间点不样。
- ApplicationContext:只要读取配置文件默认情况下就会创建对象。
- BeanFactory:什么使用什么时候创建对象
四、相关内容
4.1ApplicationContext接口的实现类
- ClassPathXmlApplicationContext:
它是从类的根路径下加载配置文件推荐使用这种
- FileSystemXmlApplicationContext:
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
- AnnotationConfigApplicationContext:
当我们使用注解配置容器对象时,需要使用此类来创建spring容器。它用来读取注解。
4.2 bean标签
- 作用:用于配置对象让spring来创建,默认情况下他调用的是类中的无参构造函数,没有无参构造函数则会报错。
- 属性
- id 唯一标识用于获取对象
- class 指定权限定类名,用于反射创建对象,默认使用无参构造函数
- scope:指定对象作用范围。
4.3 一些方法
- init_method 实例初始化时要用到的方法。
- destroy-method 实例销毁时调用的方法。
- scope 设置对象的作用范围 (单例或者多例)默认情况下创建的实例是单例的。单例(指向同一份内存地址)singleton 单例 prototype多例
<bean id="students" class="com.czf.pojo.students" init-method="init" destroy-method="destroy" >
public void init(){
}
public void destroy(){
}
五、基于注解的IOC配置
5.1 基于注解 配置文件
- @Component: 把资源让spring来管理。相当于在xml中配置一个bean.
- @Controller: 般用于表现层的注解。
- @Service: 一般用于业务层的注解。
- @Repository: 般用于持久层的注解。
spring基于注解的方式进行解耦的过程
开启spring对注解的支持(开启一个扫描包)查看有没有新的注解
spring程序在运行时会扫描指定包下的所有资源容器,然后检测对于资源 上有没有spring提供的注解。
如果有,通过反射得分方式添加到容器中,将类中的实例进行注入。
- servlet ——>service——>dao
(1) 添加约束:和之前一样到网站中搜索xmlns:co 复制如下约束
<?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">
<context:component-scan base-package="com.czf"></context:component-scan>
</beans>
以注解来配置时,唯一标识为类名首字母小写。
(2)添加注解
(3)测试
5.2 用于注入数据的注解
- @Autowired(根据类型注入)
- @Qualifier (不能单独使用 ,与@Autowired 结合使用)
- @Resource(根据id注入的)
- @Value (用于注入基本数据类型和String类型)
5.2 新注解
新注解说明
- @Configuration 注解:声明当前类为核心配置类。
@ComponentScan 开启一个扫描包
@ComponentScan(“com.eagleslab”)
@Bean 类似于xml中的bean标签,将当前方法的返回值最为一份实例存储 进核心容器中。
@Bean(“user”)
@PropertySource 用于导入别的核心配置类
- @Import 引入 别的核心配置类
@Import({StuConfig.class,JdbcConfig.class})添加.class注解
@Configuration
@ComponentScan("com.eagleslab")
@Import({StuConfig.class,JdbcConfig.class})
public class SpringConfig {
@Bean("user")
public User getUser(){
return new User(1001,"jack");
}
}
测试:@PropertySource 用于读取properties配置文件的
例如先将数据存入jdbc.properties内 db.username=‘1213’db.password=‘1213132’
@Configuration
//classpath声明当前的配置类在类路径下面
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
//使用value对数据进行注入
@Value("${db.username}")
private String username;
@Value("${db.password}")
private String password;
//将返回的对象传入
@Bean("jdbc")
public Jdbc getJdbc(){
return new Jdbc(username,password);
}
}
六、案例
需求:根据设计模式(工厂模式)+反射机制完成模拟springIOC基于注解的解耦过程。
设计思路:
首先,所有注解都得自己完成:
- 自己写一个注解:
注解格式:接口文件、 ```java @Target(ElementType.FIELD) //用于修饰:作用于属性 @Retention(RetentionPolicy.RUNTIME)//保持性策略:用于给反射机制获取 public @interface AutoWired {
}
```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {
}
创建工厂类:用于存放组件的实例
public class BeanFactory {
//创建一个集合对象
private static Map beansMap = new HashMap();
//通过map存放对象
public static Map getBeansMap() {
return beansMap;
}
/**
* 用于获取实例的方法
* @return
*/
public static Object getBean(String beanId){
//通过map的get方法由id获取对象
return beansMap.get(beanId);
}
}
对层架构进行修饰 ```java ——————————-接口—————————— public interface AccountService { void saveAccount(); }
———————————实现类—————————— @Service public class AccountServiceImpl implements AccountService {
@AutoWired
private AccountDao accountDao;
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}
4. 设计扫描类
设计流程:
- 设计方法1:①传入参数为全类名;②将全类名的点转化为/;③创建文件对象File读取该路径内容。 ④调用方法2;
- 设计迭代过滤符合条件的文件的方法2:①传入参数为File;②创建文件过滤对象;③迭代过滤文件夹下的文件,获取该文件夹下的所有文件的路径,对文件的路径进行处理。切割出符合我们使用的全类名并保存到数组中。
```java
// 模拟spring底层基于注解的方式进行解耦
public class ComponentScan {
//创建集合对象,用于存放全类名
private static List<String> fileList = new ArrayList<>();
/**
* 模拟装配实例、注入实例的过程
* @param pathName
*/
public static void getComponentScan(String pathName){
//将.替换成/
pathName = pathName.replace(".","/");
//获取当前工程的绝对路径
String path = ClassLoader.getSystemResource("").getPath() + pathName;
//创建File对象
File file = new File(path);
//调用过滤文件的方法
addFiles(file);
//遍历全类名的集合
for (String className : fileList){
try {
//获取字节码对象
Class aClass = Class.forName(className);
//根据反射机制来检测类上有没有自定义的注解
Controller controller = (Controller)aClass.getAnnotation(Controller.class);
Service service = (Service)aClass.getAnnotation(Service.class);
Repository repository = (Repository)aClass.getAnnotation(Repository.class);
//如果类上有以上任意一个注解,那么我们就创建该类的实例,将实例添加到容器中
if (controller != null || service != null || repository != null){
//根据反射机制将实例创建出来
Object object = aClass.newInstance();
//获取类的简类名
String simpleName = aClass.getSimpleName();
//将实例添加到工厂里面
BeanFactory.getBeansMap().put(simpleName,object);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
//继续遍历集合,将需要注入的属性来完成实例的注入
for (String className : fileList) {
try {
//获取字节码对象
Class aClass = Class.forName(className);
//根据反射机制来检测类上有没有自定义的注解
Controller controller = (Controller)aClass.getAnnotation(Controller.class);
Service service = (Service)aClass.getAnnotation(Service.class);
Repository repository = (Repository)aClass.getAnnotation(Repository.class);
//如果类上有以上任意一个注解,接下来要检测该类中有没有属性需要注入(查看属性有没有被AutoWired注解修饰)
if (controller != null || service != null || repository != null){
//获取该类该类中所有的属性
Field[] declaredFields = aClass.getDeclaredFields();
//遍历属性数组
for (Field declaredField : declaredFields) {
//检测属性上有没有AutoWired注解
AutoWired autoWired = declaredField.getAnnotation(AutoWired.class);
//如果该注解不为空,就意味着该属性需要注入(根据类型去注入)
if (autoWired != null){
//获取容器对象
Map<Object,Object> beansMap = BeanFactory.getBeansMap();
//将map集合转为set集合
Set<Map.Entry<Object, Object>> entries = beansMap.entrySet();
//遍历集合
for (Map.Entry<Object, Object> entry : entries) {
//获取实例所实现的接口字节码对象
Class[] interfaces = entry.getValue().getClass().getInterfaces();
//遍历接口数组
for (Class anInterface : interfaces) {
if (anInterface == declaredField.getType()){
//打破封装
declaredField.setAccessible(true);
//给属性注入实例
declaredField.set(BeanFactory.getBean(aClass.getSimpleName()),entry.getValue());
}
}
}
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/**
* 定义一个递归的方法,用于过滤符合条件的文件
*
* file.listFiles 将所有的文件或者是文件夹切成一个数组
* FileFilter 过滤文件的接口
*
*/
public static void addFiles(File file){
File[] files = file.listFiles(new FileFilter() {
@Override
public boolean accept(File f) {
//f.isDirectory() 判断file是不是文件夹
if (f.isDirectory()){
//如果是文件夹的话,继续调用递归的方法
addFiles(f);
}
//我们过滤的文件时以.class结尾的
return f.getPath().endsWith(".class");
}
});
//遍历文件数组,将符合要求的文件切割成全类名的形式 com.eagleslab.ioc.account.dao.AccountDaoImpl
for (File f : files) {
//获取文件的路径
String path = f.getPath();
//将/替换成.
path = path.replace("\\",".");
//将com前面所有的字符串删掉
path = path.substring(path.lastIndexOf("com"),path.length());
//将.class删掉
path = path.replace(".class","");
//将切割好的全类名添加到集合中
fileList.add(path);
}
}
}