目录
spring学习的核心内容
IOC:控制反转,可以管理Java工具
AOP : 切面编程
JDBCTemplate : 是 spring 提供一套访问数据库的技术, 应用性强,相对好理解
声明式事务: 基于 ioc/aop 实现事务管理,
IOC, AOP 是重点同时难点
一.概念
1.spring可以整合其他的框架(是管理框架的框架)
2.spring有两个核心技术:IOC和AOP
3.IOC [Inversion Of Control 反转控制]
传统的开发模式[Jdbcutils/反射]
程序--->环境 //程序读取配置环境,然后自己创建对象
图片解析(以数据库为例说明):
1、程序员编写程序, 在程序中读取配置信息
2. 创建对象, new Object???() // 反射方式
3. 使用对象完成任务
IOC 的开发模式 [EmpAction EmpService EmpDao Emp]
程序<-----容器 //容器创建好对象,程序直接使用.
图片解析
1、Spring 根据配置文件 xml/注解, 创建对象, 并放入到容器(ConcurrentHashMap)中,
并且可以完成对象之间的依赖
2、当需要使用某个对象实例的时候, 就直接从容器中获取即可
3、程序员可以更加关注如何使用对象完成相应的业务, (以前是 new ... ==> 注解/配置
方式)
4. DI—Dependency Injection 依赖注入,可以理解成是 IOC 的另外叫法.
5. Spring 最大的价值,通过配置,给程序提供需要使用的
web 层[Servlet(Action/Controller)]/Service/Dao/[JavaBean/entity]对象,
这个是核心价值所在,也是 ioc 的具体体现, 实现解耦
海绵hong对于ioc的理解:
本来属于你控制的事情,交由别人来控制,而你只在需要的时候进行获取就可以,把创建对象的过程交给Spring来进行管理,从而做到将原来需要自己手动new对象,变成直接从Spring中获取。
为何我们要使用ioc哪?
我们来想想当我们需要点菜的时候可以直接去new一个对象,但是如果我们需要多次点这个菜,不可能一个菜只有一个人去购买把。难道我们还需要继续去new这个类吗?这样只会增加代码冗余,但是当我们使用ioc之后,就会发现不论是一个人还是多个人点这个菜都会从ioc中获取,而且当菜的属性发生变化只需要修改ioc中的即可
二.spring快速入门
1.最基本的spring实例
1.1javabean类
package com.hong.spring;
/**
* Created with IntelliJ IDEA.
*
* @Author: 海绵hong
* @Date: 2022/09/28/10:51
* @Description: JavaBean对象
*/
public class Car {
private String id;
private String name;
private Double price;
public Car() { //在反射调用的时候会使用car的无参构造器
}
public Car(String id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", price=" + price +
'}';
}
}
1.2beans.xml
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="car01" class="com.hong.spring.Car">
<property value="001" name="id"/>
<property value="兰博基尼" name="name"/>
<property value="999999" name="price"/>
</bean>
</beans>
1.3 ioc容器使用
package com.hong.spring;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created with IntelliJ IDEA.
*
* @Author: 海绵hong
* @Date: 2022/09/28/10:55
* @Description: 最基本的spring练习
*/
public class CarAppication {
public static void main(String[] args) {
ClassPathXmlApplicationContext ioc=new ClassPathXmlApplicationContext("beans.xml");
Car car01 = ioc.getBean("car01", Car.class);
System.out.println("car01="+car01);
System.out.println(car01.getName());
}
}
2.spring 容器结构/机制
2.1beanDefinitionMap
- beanDefinitionMap类型是ConcurrrntHashMap集合
- 存放beans.xml中的bean节点配置的bean对象的信息
2.1.1 table
- 在beanDefinitionMap中有属性table
- table使数组类型是ConcurrrntHashMap$Node
- 因为是数组所以可以存放很多的备案对象信息,就是beans.xml配置
- 初始化是512,当超过时,会自动扩容
例如上例:
- 通过hash算法我们的Car01对象的信息就保存在index=373位置
- 保存是以ConcurrrntHashMap$Node类型保存
- key就是beans.xml中配置的car01
- value就是car01对象的信息[属性/属性值/类信息/是不是懒加载]
2.2singletonObject
- 类型是ConcurrrntHashMap集合
- 存放初始化的对象(car01的属性名/属性值)
2.2.1table
- 如果你在bean.xml文件中配置的对象是单例的就会初始化放在table中
- 类型使ConcurrrntHashMap$Node
2.3beanDefinitionNames
- 记录了我们在beans.xml中配置的bean的名称,方便查找
2.4注意点
如果在beans.xm中没有分配id系统会默认分配 id ,分配 id 的规则是 全类名#0 , 全类名#1 这样的规则来分配 id , 我们可以通过 debug 方式来查看。
三.Spring 管理 Bean-IOC
1.Spring配置/管理bean介绍
1.2bean的基于xml文件配置方式
1.2.1通过类型来获取bean
ClassPathXmlApplicationContext ioc=new ClassPathXmlApplicationContext("beans.xml");
Car car01 = ioc.getBean( Car.class);
System.out.println("car01="+car01);
- 按类型来获取 bean, 要求 ioc 容器中的同一个类的 bean 只能有一个, 否则会抛出异常 NoUniqueBeanDefinitionException
- 这种方式的应用场景:比如 XxxAction/Servlet/Controller, 或 XxxService 在一个线程 中只需要一个对象实例(单例)的情况
- 这里在说明一下: 在容器配置文件(比如 beans.xml)中给属性赋值, 底层是通过 setter 方法完成的, 这也是为什么我们需要提供 setter 方法的原因
1.2.1 通过构造器配置bean(全参构造器)
constructor-arg标签可以指定使用构造器的参数
index表示构造器的第几个参数,从0开始计算
除了可以通过index,还可以通过那么/type初始化
<bean id="car01" class="com.hong.spring.Car">
<constructor-arg value="001" index="0"/>
<constructor-arg value="兰博基尼" index="1"/>
<constructor-arg value="999999" index="2"/>
</bean>
<bean id="car01" class="com.hong.spring.Car">
<constructor-arg value="001" type="java.lang.String"/>
<constructor-arg value="兰博基尼" type="java.lang.String"/>
<constructor-arg value="999999" type="java.lang.Double"/>
</bean>
<bean id="car01" class="com.hong.spring.Car"
p:id="002"
p:name="宝马"
p:price="66666"
/>
1.3通过ref来配置bean
比如某些场景下有多个数据源,项目连了多个数据库,就可以分开管理
1.4引用/注入内部bean对象(同ref使用场景类似)
<bean id="memberServiceImpl02" class="com.hong.spring.Hong">
<property name="age" value="18">
<bean class="com.hong.spring.Hong"/>
</property>
</bean>
1.5引用/注入集合/数组类型
实例演示
package com.hong.spring.bean;
import java.util.*;
/**
* @海绵hong
* @version 1.0
* Master类
*/
public class Master {
private String name;//主人名
private List<Monster> monsterList;
private Map<String, Monster> monsterMap;
private Set<Monster> monsterSet;
//数组
private String[] monsterName;
//Java基础
//这个Properties 是 Hashtable的子类 , 是key-value的形式
//这里Properties key和value 都是String
private Properties pros;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Monster> getMonsterList() {
return monsterList;
}
public void setMonsterList(List<Monster> monsterList) {
this.monsterList = monsterList;
}
public Map<String, Monster> getMonsterMap() {
return monsterMap;
}
public void setMonsterMap(Map<String, Monster> monsterMap) {
this.monsterMap = monsterMap;
}
public Set<Monster> getMonsterSet() {
return monsterSet;
}
public void setMonsterSet(Set<Monster> monsterSet) {
this.monsterSet = monsterSet;
}
public String[] getMonsterName() {
return monsterName;
}
public void setMonsterName(String[] monsterName) {
this.monsterName = monsterName;
}
public Properties getPros() {
return pros;
}
public void setPros(Properties pros) {
this.pros = pros;
}
@Override
public String toString() {
return "Master{" +
"name='" + name + '\'' +
", monsterList=" + monsterList +
", monsterMap=" + monsterMap +
", monsterSet=" + monsterSet +
", monsterName=" + Arrays.toString(monsterName) +
", pros=" + pros +
'}';
}
}
<bean id="master01" class="com.hong.spring.beans.Master">
<property name="name" value="太上老君"/>
<!-- 给 bean 对象的 list 集合赋值 -->
<property name="monsterList">
<list>
<ref bean="monster03"/>
<ref bean="monster02"/>
</list>
</property>
<!-- 给 bean 对象的 map 集合赋值 -->
<property name="monsterMap">
<map>
<entry>
<key>
<value>monsterKey01</value>
</key>
<ref bean="monster01"/>
</entry>
<entry>
<key>
<value>monsterKey02</value>
</key>
<ref bean="monster02"/>
</entry>
</map>
</property>
<!-- 给 bean 对象的 properties 集合赋值 -->
<property name="pros">
<props>
<prop key="k1">Java 工程师</prop>
<prop key="k2">前端工程师</prop>
<prop key="k3">大数据工程师</prop>
</props>
</property>
<!-- 给 bean 对象的数组属性注入值 -->
<property name="monsterName">
<array>
<value>银角大王</value>
<value>金角大王</value>
</array>
</property>
<!-- 给 bean 对象的 set 属性注入值 -->
<property name="monsterSet">
<set>
<ref bean="monster01"/>
<bean class="com.hong.spring.beans.Monster">
<property name="monsterId" value="10"/>
<property name="name" value="玉兔精"/>
<property name="skill" value="钻地洞"/>
</bean>
</set>
</property>
</bean>
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<!-- 可以达到数据复用
通过 util 名称空间来创建 list 集合,可以当做创建 bean 对象的工具来使用 -->
<util:list id="myListBook">
<value>三国演义</value>
<value>西游记</value>
<value>红楼梦</value>
<value>水浒传</value>
</util:list>
<bean id="bookStore" class="com.hspedu.spring.beans.BookStore">
<property name="bookList" ref="myListBook"/>
</bean>
</beans>
1.6级联属性赋值
<!-- 级联属性赋值 -->
<bean id="emp" class="com.hong.spring.beans.Emp">
<property name="name" value="jack"/>
<property name="Dept" ref="dept"/>
<!-- 这里我希望dept的name属性指定值[级联属性赋值] -->
<property name="Dept.name" value="Java 开发部"/>
</bean>
<bean id="dept" class="com.hspedu.spring.beans.Dept"/>
1.7通过静态工厂获取对象
复习:
静态代码块;作用就是对类进行初始化,而且它随这类的加载而执行,并且只会执行一次
普通代码块:每创建一次对象就会执行。进行初始化内容
public class MyStaticFactory {
private static Map<String, Monster> monsterMap;
static {
monsterMap = new HashMap<String, Monster>();
monsterMap.put("monster_01", new Monster(100, "黄袍怪", "一阳指"));
monsterMap.put("monster_02", new Monster(200, "九头金雕", "如来神掌"));
}
public static Monster getMonster(String key) {
return monsterMap.get(key);
}
}
<!-- 通过静态工厂来获取 bean 对象 -->
<!-- 1.通过静态工厂获取/配置bean
2.class是静态工厂类的全路径========会用com.hong.spring.factory.MyStaticFactory这个类的getMonster该方法返回monster_01这个的值
3.factory-method表示是指定静态工厂类的哪个方法返回对象
4.constructor-arg表示取出静态工厂的哪个对象
-->
<bean id="my_monster" class="com.hong.spring.factory.MyStaticFactory"
factory-method="getMonster"> <!-- constructor-arg 标签提供 key -->
<constructor-arg value="monster_01"/>
1.8通过实例工厂获取对象
public class MyInstanceFactory {
private Map<String, Monster> monster_map; //非静态代码块
{
monster_map = new HashMap<String, Monster>();
monster_map.put("monster_01", new Monster(100, "猴子精", "吃人"));
monster_map.put("monster_02", new Monster(200, "九头金雕", "如来神掌"));
}
public Monster getMonster(String key) {
return monster_map.get(key);
}
}
<!-- 1.因为实例工厂要进行对象的实例化,所以第一个是配置实例工厂对象-->
<bean id="myInstanceFactory" class="com.hspedu.hong.factory.MyInstanceFactory"/>
<bean id="my_monster2" factory-bean="myInstanceFactory" factory-method="getMonster">
<constructor-arg value="monster_02"/>
</bean>
<!--
1.factory-bean指定使用哪个实例工厂对象返回bean
2.factory-bean指定使用实例工厂对象的哪个方法
3.指定获取到实例工厂中的哪个对象
-->
</beans>
1.9通过FactoryBean获取对象(重点)
public class MyFactoryBean implements FactoryBean<Monster> {
private String keyVal;
private Map<String, Monster> monster_map;
{
monster_map = new HashMap<String, Monster>();
monster_map.put("monster_01", new Monster(100, "黄袍怪", "一阳指"));
monster_map.put("monster_02", new Monster(200, "九头金雕", "如来神掌"));
}
public void setKeyVal(String keyVal) {
this.keyVal = keyVal;
}
@Override
public Monster getObject() throws Exception {
// TODO Auto-generated method stub
return this.monster_map.get(keyVal);
}
@Override
public Class getObjectType() {
// TODO Auto-generated method stub
return Monster.class;
}
@Override
public boolean isSingleton() {
// TODO Auto-generated method stub
return true;
}
}
<!-- 解读
1. 通过 FactoryBean 来获取 bean 对象
2. name="keyVal" 就是 MyFactoryBean 定义的 setKeyVal 方法
3. value="monster_01" ,就是给 keyVal 的值 -->
<bean id="myFactoryBean" class="com.hong.spring.factory.MyFactoryBean">
<property name="keyVal" value="monster_01"/>
</bean>
1.10bean 配置信息重用(继承)
当我们需要一个bean对象和之前存在的bean对象属性相同,我们直接复制这样会造成数据冗余,所以我们可以使用Java基础中的继承来解决这个问题
<bean id="monster10" class="com.hong.spring.beans.Monster">
<property name="monsterId" value="10"/>
<property name="name" value="蜈蚣精"/>
<property name="skill" value="蜇人"/>
</bean>
<!-- parent="monster10" 就是继承使用了 monster10 的配置信息 -->
<bean id="monster11" class="com.hong.spring.beans.Monster"
parent="monster10"/>
<!-- 当我们把某个bean设置为 abstract="true" 这个bean只能被继承,而不能实例化了 -->
<bean id="monster12" class="com.hong.spring.beans.Monster" abstract="true">
<property name="monsterId" value="12"/>
<property name="name" value="美女蛇"/>
<property name="skill" value="吃人"/>
</bean>
<!-- parent="monster12" 就是继承使用了 monster12 的配置信息 -->
<bean id="monster13" class="com.hong.spring.beans.Monster" parent="monster12"/
1.11 bean创建顺序
<bean id="student01" class="com.hspedu.bean.Student" />
<bean id="department01" class="com.hspedu.bean.Department" />
<bean id="student01" class="com.hspedu.bean.Student" depends-on="department01"/>
<bean id="department01" class="com.hspedu.bean.Department" />
1.12bean对象的单例和多例
<!-- 解读
1. 在 spring 的 ioc 容器, 在默认情况下是安装单例创建的,
即配置一个 bean 对象后, ioc 容器只会创建一个 bean 实例。
2. 如果,我们希望 ioc 容器配置的某个 bean 对象,
是以多个实例形式创建(返回一个新的对象)的则可以通过配置 scope="prototype" 来指定 -->
<bean name="car" scope="prototype" class="com.hong.spring.beans.Car"/>
1.13bean的生命周期
public class House {
private String name;
public House() {
System.out.println("House() 构造器");
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("House setName()...");
this.name = name;
}
public void init() {//初始化方法(由程序员自己定义)
System.out.println("House init()..");
}
public void destory() {//销毁方法(由程序员自己定义)
System.out.println("House destory()..");
}
}
<!-- 配置 bean 的初始化方法和销毁方法 -->
<bean id="house" class="com.hong.spring.beans.House"
init-method="init"
destroy-method="destory">
<property name="name" value="北京豪宅"/>
</bean>
public void beanLife () {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
House house = ioc.getBean("house", House.class);
System.out.println(house);
//关闭容器
((ConfigurableApplicationContext) ioc).close();
//因为ConfigurableApplicationContext中有close方法,所以需要转型
}
1.14配置 bean 的后置处理器(进行统一管理)
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* @author 海绵hong
* @version 1.0
* 这是一个后置处理器, 需要实现 BeanPostProcessor接口
*/
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 什么时候被调用: 在Bean的init方法前被调用
* @param bean 传入在IOC容器中创建/配置的Bean
* @param beanName 传入在IOC容器中创建/配置Bean的id
* @return Object 程序员对传入的bean 进行修改/处理【如果有需要的话】 ,返回
* @throws BeansException
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization().. bean="
+ bean + " beanName=" + beanName);
//初步体验案例: 如果类型是House的统一改成 上海豪宅
//对多个对象进行处理/编程==>切面编程
if(bean instanceof House) {
((House)bean).setName("上海豪宅~");
}
return null;
}
/**
* 什么时候被调用: 在Bean的init方法后被调用
* @param bean 传入的在IOC容器中创建/配置Bean
* @param beanName 传入的在IOC容器中创建/配置Bean的id
* @return 程序员对传入的bean 进行修改/处理【如果有需要的话】 ,返回
* @throws BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization().. bean="
+ bean + " beanName=" + beanName);
return bean;
}
}
<!--配置House对象-->
<bean destroy-method="destroy" init-method="init" id="house" class="com.hong.spring.bean.House">
<property value="大豪宅" name="name"/>
</bean>
<bean destroy-method="destroy" init-method="init" id="house02" class="com.hong.spring.bean.House">
<property value="香港豪宅" name="name"/>
</bean>
<!--配置了一个Monster对象-->
<!--配置后置处理器对象
1. 当我们在beans02.xml 容器配置文件 配置了 MyBeanPostProcessor
2. 这时后置处理器对象,就会作用在该容器创建的Bean对象
3. 已经是针对所有对象编程->切面编程AOP -->
<bean id="myBeanPostProcessor" class="com.hong.spring.bean.MyBeanPostProcessor"/>
</beans>
name=\u9EC4\u888D\u602A
id=10
skill=\u72EE\u5B50\u543C
<!--
1. 通过属性文件给 bean 注入值,
2. 需要导入: xmlns:context 名字空间,并指定属性文件路径
3.location="classpath:my.properties表示属性所在的位置
4.提示需要带上classpath
5.我们的属性值通过${属性名}
6.属性名就是my.properties文件中的k=v中的k
-->
<context:property-placeholder location="classpath:my.properties"/>
<bean id="monster100" class="com.hong.spring.beans.Monster">
<property name="monsterId" value="${id}"/>
<property name="name" value="${name}"/>
<property name="skill" value="${skill}"/>
</bean>
1.16基于XML的bean的自动装配
<!--配置OrderDao对象-->
<bean id="orderDao" class="com.hong.spring.dao.OrderDao"/>
<!--配置OrderService对象老师解读
1. autowire="byType" 表示 在创建 orderService时通过类型的方式 给对象属性 自动完成赋值/引用
2. 比如OrderService 对象有 private OrderDao orderDao
3. 就会在容器中去找有没有 OrderDao类型对象
4. 如果有,就会自动的装配, 老师提示如果是按照 byType 方式来装配, 这个容器中,不能有两个的OrderDao类型对象
5. 如果你的对象没有属性, autowire就没有必要写
6. 其它类推..
7. 如果我们设置的是 autowire="byName" 表示通过名字完成自动装配
8. 比如下面的 autowire="byName" class="com.hspedu.spring.service.OrderService"
1) 先看 OrderService 属性 private OrderDao orderDao
2) 再根据这个属性的setXxx()方法的 xxx 来找对象id
3) public void setOrderDao() 就会找id=orderDao对象来进行自动装配
4) 如果没有就装配失败 -->
<bean id="orderService" class="com.hong.spring.service.OrderService" autowire="byName"/>
<!--配置OrderAction-->
<bean id="orderAction" class="com.hong.spring.web.OrderAction" autowire="byName"/>
1.17spring el表达式
<!-- spring el 表达式 -->
<bean id="spELBean" class="com.hong.spring.beans.SpELBean">
<!-- sp el 给字面量 -->
<property name="name" value="#{'海绵hong'}"/>
<!-- sp el 引用其它 bean -->
<property name="monster" value="#{monster01}"/>
<!-- sp el 引用其它 bean 的属性值 -->
<property name="monsterName" value="#{monster02.name}"/>
<!-- sp el 调用普通方法 赋值 -->
<property name="crySound" value="#{spELBean.cry('喵喵的..')}"/>
<!-- sp el 调用静态方法 赋值 -->
<property name="bookName" value="#{T(com.hspedu.spring.beans.SpELBean).read(' 天龙八部')}"/>
<!-- sp el 通过运算赋值 -->
<property name="result" value="#{89*1.2}"/>
</bean>
2.基于注解配置bean
2.1基本介绍
2.2快速入门
package com.hong.spring.component;
import org.springframework.stereotype.Repository;
/**
* * @author 海绵hong
* * @version 1.0
*/
@Repository
public class UserDao {
}
package com.hspedu.spring.component;
import org.springframework.stereotype.Service;
/*** @author 海绵hong
* * @version 1.0
* */
@Service
public class UserService {
}
package com.hong.spring.component;
import org.springframework.stereotype.Controller;
/*** @author
* 海绵hong
* * @version 1.0
* */
@Controller
public class UserAction {
}
package com.hong.spring.component;
import org.springframework.stereotype.Component;
/*** @author 海绵hong
* * @version 1.0
* */
@Component
public class MyComponent {
}
}
配置beans.xml
<!-- 配置自动扫描的包,注意需要加入 context 名称空间
base-package:指定扫描的包 -->
<context:component-scan base-package="com.hspedu.spring.component" />
含义是:当spring容器创建/初始化时,就会扫描这个包下的所以的有注解@Controller / @Service / @Respository / @Component 的类,将其类实例化,生成对象,放入到ioc容器
public void getBeanByAnnotation () {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
UserAction userAction = ioc.getBean(UserAction.class);
System.out.println(userAction);
UserDao userDao = ioc.getBean(UserDao.class);
System.out.println(userDao);
MyComponent myComponent = ioc.getBean(MyComponent.class);
System.out.println(myComponent);
UserService userService = ioc.getBean(UserService.class);
System.out.println(userService);
}
注意事项和细节说明
<context:component-scanbase-package="com.hspedu.spring.component"
resource-pattern="User*.class" />
resource-pattern="User*.class": 表示只扫描满足要求的类(只扫描User打头的类)使用的少,不想扫描,不写注解就可以,
1. context:exclude-filter 指定要排除哪些类
2. type 指定排除方式 annotation表示按照注解来排除
3. expression="org.springframework.stereotype.Service" 指定要排除的注解的全路径
<context:component-scan base-package="com.hspedu.spring.component">
<!-- 排除哪些类 , 以 annotaion 注解为例 -->
<context:exclude-filter
type="annotation"
expression="org.springframework.stereotype.Service"/>
</context>
<!-- 解读
1. use-default-filters="false": 不再使用默认的过滤机制
2. context:include-filter: 表示只是扫描指定的注解的类
3. expression="org.springframework.stereotype.Controller": 注解的全类名
-->
<context:component-scan base-package="com.hong.spring.component" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
@Controller(value="userAction01");
@Controller("userAction01");
2.3手动开发简单的spring基于注解配置的程序
2.3.1需求说明
2.3.3代码实现
package com.hong.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created with IntelliJ IDEA.
*
* @Author: 海绵hong
* @Date: 2022/10/06/15:44
* @Description:
* 1.@Target(ElementType.TYPE)指定我们的HongComponentScan注解可以修饰type程序元素
* 2.@Retention(RetentionPolicy.RUNTIME)指定HongComponentScan注解的保留范围
* 3. String value() default ""; 表示ComponentScan 可以传入 value
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HongComponentScan {
String value() default "";
}
package com.hong.spring.annotation;
/**
* Created with IntelliJ IDEA.
*
* @Author: 海绵hong
* @Date: 2022/10/06/15:54
* @Description:
*这是一个配置类, 作用类似我们原生spring的 beans.xml 容器配置文件
*/
@HongComponentScan(value = "com.hong.spring.component")
public class HongSpringConfig {
}
package com.hong.spring.annotation;
import com.hspedu.spring.hspapplicationcontext.HspApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMa
import java.util.concurrent.ConcurrentHashMap;
/**
* Created with IntelliJ IDEA.
*
* @Author: 海绵hong
* @Date: 2022/10/06/20:53
* @Description: HongSpringApplicationContext类的作用类似spring原生ioc容器
*/
public class HongSpringApplicationContext {
private Class configClass;
//ioc我存放的就是通过反射创建的对象(基于注解方式)
private final ConcurrentHashMap<String, Object> ioc =
new ConcurrentHashMap<>();
//构造器
public HongSpringApplicationContext(Class configClass) {
this.configClass = configClass;
//System.out.println("this.configClass=" + this.configClass);
//获取要扫描的包
//1. 先得到HongSpringConfig配置的的@ComponentScan(value = "com.hong.spring.component")
ComponentScan componentScan =
(ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
//2. 通过componentScan的value=> 即要扫描的包
String path = componentScan.value();
System.out.println("要扫描的包= " + path);
//得到要扫描的包下的所有资源(类 .class)
//1.得到类的加载器
ClassLoader classLoader =
HongSpringConfig.class.getClassLoader();
//2. 通过类的加载器获取到要扫描的包的资源 url=》类似一个路径
path = path.replace(".", "/");//一定要把. 替换成 /
URL resource =
classLoader.getResource(path);
System.out.println("resource=" + resource);
//3. 将要加载的资源(.class) 路径下的文件进行遍历=>io
File file = new File(resource.getFile());
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
System.out.println("=====================");
System.out.println("=" + f.getAbsolutePath());
//D:\hong_spring\spring\out\production\spring\com\hong\spring\component\UserService.class
//获取到 com.hpng.spring.component.UserService
String fileAbsolutePath = f.getAbsolutePath();
//这里我们只处理.class文件
if (fileAbsolutePath.endsWith(".class")) {
//1. 获取到类名
String className =
fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
//System.out.println("className=" + className);
//2. 获取类的完整的路径(全类名)
//老师解读 path.replace("/",".") => com.hspedu.spring.component.
String classFullName = path.replace("/", ".") + "." + className;
//System.out.println("classFullName=" + classFullName);
//3. 判断该类是不是需要注入容器, 就看该类是不是有注解 @Component @Service..
try {
//这时,我们就得到老该类的Class对象
//Class clazz = Class.forName(classFullName)
//这里说一下
//1. Class clazz = Class.forName(classFullName) 可以反射加载类
//2. classLoader.loadClass(classFullName); 可以反射类的Class
//3. 区别是 : 上面方式后调用来类的静态方法, 下面方法不会
//4. aClass.isAnnotationPresent(Component.class) 判断该类是否有 @Component
Class<?> aClass = classLoader.loadClass(classFullName);
if (aClass.isAnnotationPresent(Component.class) ||
aClass.isAnnotationPresent(Controller.class) ||
aClass.isAnnotationPresent(Service.class) ||
aClass.isAnnotationPresent(Repository.class)) {
//这里老师演示一个Component注解指定value,分配id
//老师就是演示了一下机制.
if(aClass.isAnnotationPresent(Component.class)) {
//获取到该注解
Component component = aClass.getDeclaredAnnotation(Component.class);
String id = component.value();
if(!"".endsWith(id)) {
className = id;//替换
}
}
//这时就可以反射对象,并放入到容器中
Class<?> clazz = Class.forName(classFullName);
Object instance = clazz.newInstance();
//放入到容器中, 将类名的首字母小写作为id
//StringUtils
ioc.put(StringUtils.uncapitalize(className) , instance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
//编写方法返回对容器中对象
public Object getBean(String name) {
return ioc.get(name);
}
}
package com.hong.spring.annotation;
import com.hong.spring.Hong;
/**
* Created with IntelliJ IDEA.
*
* @Author: 海绵hong
* @Date: 2022/10/08/10:21
* @Description:
*/
public class HongSpringApplicationContextTest {
public static void main (String[]args){
HongSpringApplicationContext ioc =
new HongSpringApplicationContext(HongSpringConfig.class);
UserAction userAction = (UserAction) ioc.getBean("userAction");
System.out.println("userAction" + userAction);
MyComponent myComponent = (MyComponent) ioc.getBean("myComponent");
System.out.println("myComponent" + myComponent);
UserService userService = (UserService) ioc.getBean("userService");
System.out.println("userService=" + userService);
UserDao userDao = (UserDao) ioc.getBean("userDao");
System.out.println("userDao=" + userDao);
System.out.println("ok");
}
}
2.4自动装配
@Controller
public class UserAction {
//xml配置 ref
//说明 @Autowired
//1)在IOC容器中查找待装配的组件的类型,如果有唯一的bean匹配(按照类型),则使用该bean装配
//2)如待装配的类型对应的bean在IOC容器中有多个,则使用待装配的属性的属性名作为id值再进行查找,
// 找到就装配,找不到就抛异常
//@Autowired
//说明 @Resource
//1) @Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字,
// 而type属性则解析为bean的类型.所以如果使用name属性,则使用byName的自动注入策略,
// 而使用type属性时则使用byType自动注入策略
// 比如@Resource(name = "userService") 表示装配 id=userService对对象
// 比如@Resource(type = UserService.class) 表示按照UserService.class类型进行装配, 这时要求容器中,只能有一个这样类型的对象
//2) 如果@Resource 没有指定 name 和 type ,则先使用byName注入策略,
// 如果匹配不上, 再使用byType策略, 如果都不成功,就会报错
//=================================
//说明: @Autowired + @Qualifier(value = "userService02") 组合也可以完成指定 name/id 来进行自动装配
//指定id进行组装, 也可以使用@Autowired 和 @Qualifier(value = "userService02")
// 这时,是装配的 id=userService02 , 需要两个注解都需要写上
@Resource
private UserService userService;
public void sayOk() {
System.out.println("UserAction 的sayOk()");
System.out.println("userAction 装配的 userService属性=" + userService);
userService.hi();
}
}
2.5泛型依赖注入
但是当我们
@Service
public class PhoneService extends BaseService<Phone> {
}
PhoneService里面没有写
private PhoneDao Phonedao;
但是却注入里面就是基于泛型这套机制完成的
说明泛型基类的依赖关系,被子类自动继承,并根据泛型找到对应的子类。