文章目录
Spring
Spring 是一个开源框架,广泛用于 Java 应用程序的开发。Spring 的核心是提供了一个全面的编程和配置模型,旨在简化 Java 应用的开发和维护。这个框架主要通过以下几个方面来实现这一目标:
- 依赖注入(Dependency Injection, DI):Spring 使用依赖注入来实现松耦合。通过控制反转(Inversion of Control, IoC)的容器,Spring 管理应用组件之间的依赖关系,而不是由组件自行管理。
- 面向切面编程(Aspect-Oriented Programming, AOP):Spring 支持 AOP,允许定义横切关注点(如日志、事务管理等),从而增强模块化。
- 事务管理:Spring 提供了一种抽象层来管理事务,使得单个事务可以跨多个后端资源统一管理。
- 模型视图控制器(MVC):Spring Web MVC 是一个基于 Servlet API 构建的富有表现力的 web 框架,支持 REST API 的构建。
- 数据访问与集成:Spring 提供了对 JDBC、JPA、JMS 等技术的抽象,简化了数据库操作和消息服务的使用。
- 全栈解决方案:Spring Boot 是 Spring 的一个子项目,它通过提供默认配置帮助开发者快速搭建应用,实现“约定大于配置”的原则。
官网:https://spring.io/
1. IoC
IoC,全称Inversion of Control,中文名为“控制反转”,是一种设计思想,主要用于降低代码间的耦合度。
在IoC思想中,对象的创建和生命周期的管理被交给了IoC容器,而代码只需要声明其需要哪些对象(依赖),然后由IoC容器注入这些依赖,从而实现对象之间的解耦。
1.1 快速入门
1)引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.8</version>
</dependency>
2)创建javaBean
package com.lhs.bean;
public class Monster {
public String name;
public Integer id;
public String skill;
public Monster(String name, Integer id, String skill) {
this.name = name;
this.id = id;
this.skill = skill;
}
public Monster() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
@Override
public String toString() {
return "Monster{" +
"name='" + name + '\'' +
", id=" + id +
", skill='" + skill + '\'' +
'}';
}
}
3)创建bean.xml并配置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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Bean definitions go here -->
<bean class="com.lhs.bean.Monster" id="monster">
<property name="id" value="1"></property>
<property name="name" value="牛魔王"></property>
<property name="skill" value="芭蕉扇"></property>
</bean>
</beans>
4)获取bean
@Test
public void test01(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
System.out.println();
Monster monster = context.getBean("monster", Monster.class);
System.out.println(monster);
}
1.2 自定义Bean容器
IoC底层采用ConcurrentHashMap来存储bean对象
模拟实现简化版容器:
public class MyApplicationContext {
// 存储Bean实例的容器
private Map<String,Object> concurrentHashMap = new ConcurrentHashMap();
public MyApplicationContext() throws Exception {
// 获取bean.xml文件路径
String path = this.getClass().getResource("/").getPath();
// 创建SAXReader对象进行XML解析
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new File(path + "/bean.xml"));
Element rootElement = document.getRootElement();
// 遍历bean.xml中的每一个bean元素
Iterator iterator = rootElement.elementIterator();
while (iterator.hasNext()) {
Element element = (Element)iterator.next();
// 获取bean的id和class属性
String id = element.attribute("id").getValue();
String clazz = element.attribute("class").getValue();
// 获取bean元素下的所有property元素
List<Element> elements = element.elements();
// 通过反射创建Bean实例
Class<?> aClass = Class.forName(clazz);
Object o = aClass.newInstance();
// 遍历每一个property元素,获取其name和value属性,并通过反射调用setter方法设置到Bean实例中
for (Element element1 : elements) {
String name = element1.attribute("name").getValue();
Type type = aClass.getField(name).getGenericType();
Method method = aClass.getMethod(getSetterName(name),Class.forName(type.getTypeName()));
String value = element1.attribute("value").getValue();
method.invoke(o,convertStringToType(value,Class.forName(type.getTypeName())));
}
// 将Bean实例存入容器
concurrentHashMap.put(id,o);
}
}
// 通过id获取Bean实例
public Object getBean(String id){
return concurrentHashMap.get(id);
}
// 将属性名转换为对应的setter方法名
private String getSetterName(String name){
char[] charArray = name.toCharArray();
charArray[0] = (char) (charArray[0] - 32);
String name2 = new String(charArray);
return "set" + name2;
}
// 将字符串值转换为对应的类型
private Object convertStringToType(String str, Class<?> type) throws Exception {
if (type == Integer.class) {
return Integer.parseInt(str);
} else if (type == Double.class) {
return Double.parseDouble(str);
} else if (type == Boolean.class) {
return Boolean.parseBoolean(str);
} else if (type == Float.class) {
return Float.parseFloat(str);
} else if (type == Long.class) {
return Long.parseLong(str);
} else if (type == Short.class) {
return Short.parseShort(str);
} else if (type == Byte.class) {
return Byte.parseByte(str);
} else if (type == Character.class) {
// 特别处理字符类型,确保字符串长度为1
if (str.length() != 1) {
throw new IllegalArgumentException("String length must be 1 to convert to Character.");
}
return str.charAt(0);
} else if (type == String.class) {
return str;
}
// 若类型不支持,则抛出异常
throw new IllegalArgumentException("Unsupported type: " + type);
}
}
1.3 获取Bean
通过id获取
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Monster monster = context.getBean("monster", Monster.class);
// 若不传类型则返回Object
Object monster1 = context.getBean("monster");
通过类名获取
Monster bean = context.getBean(Monster.class);
该方法要求 ioc 容器中的同一个类的 bean 只能有一个, 否则会抛出异常。
静态工厂获取对象
1)创建静态工厂:
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);
}
}
2)修改bean.xml:
<bean id="my_monster" class="com.lhs.factory.MyStaticFactory"
factory-method="getMonster">
<!-- constructor-arg 标签提供 key -->
<constructor-arg value="monster_01"/>
</bean>
3)测试:
@Test
public void getBeanByStaticFactory() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster my_monster = ioc.getBean("my_monster", Monster.class);
System.out.println(my_monster);
}
通过实例工厂获取对象
通过静态工厂获取的对象永远都是同一个,而通过不同实例工厂获取到的对象是不同的。
1)创建实例工厂:
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);
}
}
2)修改bean.xml:
<bean id="myInstanceFactory" class="com.lhs.factory.MyInstanceFactory"/>
<bean id="my_monster2" factory-bean="myInstanceFactory"
factory-method="getMonster">
<constructor-arg value="monster_02"/>
</bean>
3)测试:
@Test
public void getBeanByInstanceFactory() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster my_monster = ioc.getBean("my_monster2", Monster.class);
System.out.println(my_monster);
}
通过FactoryBean获取对象
1)创建工厂继承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;
}
}
2)配置bean.xml:
<bean id="myFactoryBean" class="com.lhs.factory.MyFactoryBean">
<property name="keyVal" value="monster_01"/>
</bean>
3)测试:
public void getBeanByFactoryBean() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster monster = ioc.getBean("myFactoryBean", Monster.class);
System.out.println(monster);
}
1.4 基于XML文件配置Bean
通过Setter方法配置bean
<bean class="com.lhs.bean.Monster" id="monster">
<property name="id" value="1"></property>
<property name="name" value="牛魔王"></property>
<property name="skill" value="芭蕉扇"></property>
</bean>
底层会调用该类的setter方法给属性赋值
通过构造器配置bean
方式一:按照构造器参数索引指定参数
<bean class="com.lhs.bean.Monster" id="monster02">
<constructor-arg value="蜘蛛精" index="0"/>
<constructor-arg value="2" index="1"/>
<constructor-arg value="吐口水" index="2"/>
</bean>
方式二:按照类型指定参数
<bean class="com.lhs.bean.Monster" id="monster03">
<constructor-arg value="3" type="java.lang.Integer"/>
<constructor-arg value="白骨精" type="java.lang.String"/>
<constructor-arg value="白骨鞭" type="java.lang.String"/>
</bean>
通过p名称空间配置bean
在bean.xml文件中增加p名称空间配置
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.lhs.bean.Monster" id="monster04"
p:id="4"
p:name="红孩儿"
p:skill="喷火"
/>
</beans>
通过ref来引用bean对象
在ServiceImpl中引入DAOImpl
方式一:
<bean id="memberServiceImpl" class="com.lhs.service.MemberServiceImpl">
<property name="memberDAO" ref="memberDAOImpl"/>
</bean>
<bean id="memberDAOImpl" class="com.lhs.dao.MemberDAOImpl"/>
方式二:内部注入
<bean id="memberServiceImpl02" class="com.lhs.service.MemberServiceImpl">
<property name="memberDAO">
<bean class="com.lhs.dao.MemberDAOImpl"/>
</property>
</bean>
引入集合/数组类型
1)创建Master类:
package com.lhs.bean;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class Master {
private String name;
private List<Monster> monsterList;
private Map<String, Monster> monsterMap;
private Set<Monster> monsterSet;
private String[] monsterName;
private Properties pros;
public Master(String name, List<Monster> monsterList, Map<String, Monster> monsterMap, Set<Monster> monsterSet, String[] monsterName, Properties pros) {
this.name = name;
this.monsterList = monsterList;
this.monsterMap = monsterMap;
this.monsterSet = monsterSet;
this.monsterName = monsterName;
this.pros = pros;
}
public Master() {
}
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;
}
}
2)配置bean.xml:
<!-- 给集合属性注入值-->
<bean id="master01" class="com.lhs.bean.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.lhs.bean.Monster">
<property name="id" value="10"/>
<property name="name" value="玉兔精"/>
<property name="skill" value="钻地洞"/>
</bean>
</set>
</property>
</bean>
通过util名称空间创建list
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" 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/util
http://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.lhs.bean.BookStore">
<property name="bookList" ref="myListBook"/>
</bean>
</beans>
级联属性赋值
spring 的 ioc 容器, 可以直接给对象属性的属性赋值, 即级联属性赋值
1)创建Dept类:
public class Dept {
private String name;
public Dept() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2)创建Emp类:
public class Emp {
private String name;
private Dept dept;
public Emp() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
}
3)bean.xml:
<bean id="emp" class="com.lhs.bean.Emp">
<property name="name" value="jack"/>
<property name="dept" ref="dept"/>
<property name="dept.name" value="Java 开发部"/>
</bean>
<bean id="dept" class="com.lhs.bean.Dept"/>
配置信息的重用
<!-- 继承的方式来实现 bean 配置信息的重用 -->
<bean id="monster10" class="com.lhs.bean.Monster">
<property name="monsterId" value="10"/>
<property name="name" value="蜈蚣精"/>
<property name="skill" value="蜇人"/>
</bean>
<!-- parent="monster10" 就是继承使用了 monster10 的配置信息 -->
<bean id="monster11" class="com.lhs.bean.Monster" parent="monster10"/>
<!-- 当我们把某个bean设置为 abstract="true" 这个bean只能被继承,而不能实例化了 -->
<bean id="monster12" class="com.lhs.bean.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.lhs.bean.Monster" parent="monster12"/>
通过属性文件给 bean 注入值
1)创建 my.properties文件:
name=\u9EC4\u888D\u602A
id=10
skill=\u72EE\u5B50\u543C
2)配置bean.xml:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--
1. 通过属性文件给 bean 注入值
2. 需要导入: xmlns:context 名字空间,并指定属性文件路径
-->
<context:property-placeholder location="classpath:my.properties"/>
<bean id="monster100" class="com.lhs.bean.Monster">
<property name="monsterId" value="${id}"/>
<property name="name" value="${name}"/>
<property name="skill" value="${skill}"/>
</bean>
</beans>
3)测试:
@Test
public void setProByProFile() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster monster100 = ioc.getBean("monster100", Monster.class);
System.out.println(monster100);
}
1.5 bean对象的单例和多例
在spring的ioc容器,在默认是按照单例创建的,即配置一个 bean 对象后,ioc 容器只会创建一个 bean 实例。
如果,我们希望 ioc 容器配置的某个 bean 对象,是以多个实例形式创建的则可以通过配置scope=“prototype” 来指定
bean.xml:
<bean name="car" scope="prototype" class="com.lhs.bean.Car"/>
注意:
1)默认是单例 singleton, 在启动容器时, 默认就会创建 , 并放入到 singletonObjects 集合
2)当 <bean scope="prototype" >
设置为多实例机制后, 该 bean 是在 getBean()时才创建
3)如果是单例 singleton, 同时希望在 getBean 时才创建 , 可以指定懒加载lazy-init="true"
(注意默认是 false)
4)通常情况下, lazy-init 就使用默认值 false , 在开发看来, 用空间换时间是值得的, 除非有特殊的要求
5) 如果 scope=“prototype” 这时的 lazy-init 属性的值不管是 ture, 还是 false 都是在getBean 时候,才创建对象
1.6 bean的生命周期
bean 对象创建是由 JVM 完成的,然后按顺序执行如下方法:
1)执行构造器
2)执行 set 相关方法
3)调用 bean 的初始化的方法(需要配置)
4)使用 bean
5)当容器关闭时候,调用 bean 的销毁方法(需要配置)
1)创建bean:
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()..");
}
}
2)bean.xml:
<!-- 配置 bean 的初始化方法和销毁方法 -->
<bean id="house" class="com.lhs.bean.House"
init-method="init"
destroy-method="destory">
<property name="name" value="北京豪宅"/>
</bean>
3)测试:
@Test
public void beanLife() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
House house = ioc.getBean("house", House.class);
System.out.println(house);
// 执行销毁方法
((ConfigurableApplicationContext) ioc).close();
}
1.7 bean的后置处理器
在 spring 的 ioc 容器,可以配置 bean 的后置处理器,该处理器/对象会在 bean 初始化方法调用前和初始化方法调用后被调用。
我们可以在后置处理器中编写自己的代码,可以对 IOC 容器中所有的对象进行统一处理,比如 日志处理/权限的校验/安全的验证/事务管理。
实例:
1)创建后置处理器继承BeanPostProcessor接口:
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 在 bean 初始化之前完成某些任务
* @param bean : 就是 ioc 容器返回的 bean 对象, 如果这里被替换会修改,则返
回的 bean 对象也会被修改
* @param beanName: 就是 ioc 容器配置的 bean 的名称
* @return Object: 就是返回的 bean 对象
*/
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
// TODO Auto-generated method stub
System.out.println("postProcessBeforeInitialization 被 调 用 " + beanName + " bean= " + bean.getClass());
return bean;
}
/**
* 在 bean 初始化之后完成某些任务
* @param bean : 就是 ioc 容器返回的 bean 对象, 如果这里被替换会修改,则返
回的 bean 对象也会被修改
* @param beanName: 就是 ioc 容器配置的 bean 的名称
* @return Object: 就是返回的 bean 对象
*/
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
System.out.println("postProcessAfterInitialization 被调用 " + beanName + " bean= " + bean.getClass());
return bean;
}
}
2)配置bean.xml:
<!-- 配置 bean 的初始化方法和销毁方法 -->
<bean id="house" class="com.lhs.beans.House"
init-method="init" destroy-method="destory">
<property name="name" value="北京豪宅"/>
</bean>
<!-- bean 后置处理器的配置 -->
<bean id="myBeanPostProcessor" class="com.lhs.bean.MyBeanPostProcessor" />
</beans>
3)测试:
@Test
public void testBeanPostProcessor() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans02.xml");
House house = ioc.getBean("house", House.class);
System.out.println(house);
//关闭容器
((ConfigurableApplicationContext) ioc).close();
}
1.8 bean的自动装配
根据类型进行自动组装
<bean id="orderAction" autowire="byType" class="com.lhs.action.OrderAction" />
<bean id="orderService" autowire="byType" class="com.lhs.service.OrderService"/>
<bean id="orderDao" class="com.lhs.dao.OrderDao"/>
根据id进行自动组装
<!--
1.autowire = "byName" 会自动去找 id 为 setXxxx 后面 Xxxx 的 bean 自动组装.
,如果找到就装配,如果找不到就报错
2. <bean id="orderAction" autowire="byName" class="com.lhs.bean.OrderAction" />
就 会 去 找 OrderAction 类 中 定 义 的 setOrderService 的 id 为 orderService 的
OrderService bean 组装,找到就阻装,找不到就组装失败
-->
<bean id="orderAction" autowire="byName" class="com.lhs.action.OrderAction"/>
<bean id="orderService" autowire="byName" class="com.lhs.service.OrderService"/>
<bean id="orderDao" class="com.lhs.dao.OrderDao"/>
1.9 基于注解配置bean
组件注解的形式有:
@Component
表示当前注解标识的是一个组件@Controller
表示当前注解标识的是一个控制器,通常用于 Servlet@Service
表示当前注解标识的是一个处理业务逻辑的类,通常用于 Service 类@Repository
表示当前注解标识的是一个持久化层的类,通常用于 Dao 类
示例:
1)创建dao层
@Repository
public class UserDao {
}
2)创建service层
@Service
public class UserService {
}
3)创建controller
@Controller
public class UserAction {
}
4)创建普通组件
@Component
public class MyComponent {
}
5)配置bean.xml
<!-- 配置自动扫描的包,注意需要加入 context 名称空间 -->
<context:component-scan base-package="com.lhs.component" />
6)测试
@Test
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);
}
还可以通过@Component(value = “xx”) @Controller(value = “yy”) @Service(value = “zz”)中指定的 value, 给 bean 分配 id
排除指定注解
<context:component-scan base-package="com.lhs.component">
<context:exclude-filter type="annotation"expression="org.springframework.stereotype.Service"/>
</context:component-scan>
注意:
<context:exclude-filter>
放在<context:component-scan>
内,表示扫描过滤掉当前包
的某些类- type=“annotation” 按照注解类型进行过滤.
- expression :就是注解的全类名,比如 org.springframework.stereotype.Service 就是 @Service 注解的全类名,其它比@Controller @Repository 等 依次类推
- 上面表示过滤掉 com.lhs.component 包下,加入了@Service 注解的类
指定扫描注解
<context:component-scan base-package="com.lhs.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>
注意:
- use-default-filters=“false”: 不再使用默认的过滤机制
- context:include-filter: 表示只是扫描指定的注解的类
- expression=“org.springframework.stereotype.Controller”: 注解的全类名
自定义容器
public class MyApplicationContext02 {
// 创建bean容器
private Map<String,Object> concurrentHashMap = new ConcurrentHashMap();
public MyApplicationContext02(){
// 模拟要扫描的包的名称
String packetName = "com/lhs/component";
// 获取当前线程的类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 获取包的URL资源
URL resource = classLoader.getResource(packetName);
// 根据资源路径创建文件对象
File file = new File(resource.getPath());
// 判断文件对象是否为目录
if(file.isDirectory()){
// 获取目录下的所有文件
File[] files = file.listFiles();
// 遍历所有文件
for (File file1 : files) {
try {
// 加载文件对应的类
Class<?> aClass = Class.forName(packetName.replace("/",".")+ "." + file1.getName().replace(".class",""));
// 判断类是否被指定的注解标记
if(aClass.isAnnotationPresent(Primary.class) || aClass.isAnnotationPresent(Service.class)
|| aClass.isAnnotationPresent(Controller.class) || aClass.isAnnotationPresent(Component.class)){
// 如果被标记,则创建类的实例并存入哈希表
concurrentHashMap.put(file1.getName().replace(".class",""),aClass.newInstance());
}
} catch (Exception e) {
// 如果出现异常,抛出运行时异常
throw new RuntimeException(e);
}
}
}
// 打印哈希表内容
System.out.println(concurrentHashMap);
}
// 根据名称获取哈希表中的对象
public Object getBean(String name){
return concurrentHashMap.get(name);
}
}
自动装配
@AutoWired
在 IOC 容器中查找待装配的组件的类型,如果有唯一的 bean 匹配,则使用该 bean 装配
如待装配的类型对应的 bean 在 IOC 容器中有多个,则使用待装配的属性的属性名作为 id 值再进行查找, 找到就装配,找不到就抛异常
示例:
@Controller("userAction01")
public class UserAction {
//自动装配 UserService, 这时是以 id=userService 的 UserService 对象进行组装.
@Autowired
private UserService userService;
public void sayOk(){
System.out.println("UserAction.userService= " + userService);
userService.hi();
}
}
@Autowired
@Qualifier(value = "userService02") //指定 id=userService02 的 UserService 对象进行组装
private UserService userService;
public void sayOk(){
System.out.println("UserAction.userService= " + userService);
userService.hi();
}
@Resource
@Resource 有两个属性是比较重要的,分是 name 和 type,Spring 将@Resource 注解的name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型,所以如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略
如果@Resource 没有指定 name 和 type ,则先使用byName注入策略, 如果匹配不上,再使用 byType 策略, 如果都不成功,就会报错
示例:
1)导入依赖
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
2)完成装配
@Controller
public class UserController {
@Resource
UserService userService;
}
泛型依赖注入
Spring可以根据泛型类型来自动装配bean
假设我们有一个泛型接口:
public interface MyRepository<T> {
T getById(String id);
}
然后我们有两个实现类:
@Repository
public class StringRepository implements MyRepository<String> {
@Override
public String getById(String id) {
return "StringRepository";
}
}
@Repository
public class IntegerRepository implements MyRepository<Integer> {
@Override
public Integer getById(String id) {
return 123;
}
}
我们有两个实现了MyRepository
接口的bean,一个是StringRepository
,另一个是IntegerRepository
。
现在,如果我们在另一个类中需要注入一个MyRepository
的实例,我们可以这样做:
@Service
public class MyService {
@Autowired
private MyRepository<String> stringRepository;
@Autowired
private MyRepository<Integer> integerRepository;
}
Spring会根据泛型类型来自动装配stringRepository
和integerRepository
。也就是说,stringRepository
会被装配为StringRepository
的实例,integerRepository
会被装配为IntegerRepository
的实例。
2. AOP
AOP 的全称(aspect oriented programming) ,面向切面编程,它允许开发者在不修改原有代码结构的情况下,增加额外的功能。
这种编程方式主要通过“切面”来实现,切面可以定义为跨越多个类或方法的关注点(比如日志记录、事务管理等),从而使这些关注点的模块化成为可能。
在AOP中,有以下几个核心概念:
- 切点(Pointcut):定义了何处需要执行切面的代码。它通过匹配方法签名来指定切面可以应用的一个或多个连接点。
- 通知(Advice):切面必须在指定的切点上执行的具体操作。这些操作包括:
- 前置通知(Before advice):在目标方法执行之前执行。
- 后置通知(After advice):在目标方法执行之后执行,无论其结果如何。
- 返回后通知(After-returning advice):仅当目标方法成功完成后执行。
- 异常后通知(After-throwing advice):仅当目标方法通过抛出异常退出时执行。
- 环绕通知(Around advice):在目标方法执行前后执行,能够阻止目标方法的执行。
- 切面(Aspect):通知和切点的结合。切面既包含了具体的操作定义,也规定了这些操作应当在何处执行。
- 目标对象(Target Object):被一个或多个切面所通知的对象。
- 代理(Proxy):为目标对象提供方法拦截并将调用委托到通知的中间代理类。在 Spring AOP 中,代理通常是基于 JDK 动态代理或 CGLIB 代理。
2.1 动态代理
我们来看一个案例:
- 有 Vehicle(交通工具接口, 有一个 run 方法), 下面有两个实现类 Car 和 Ship
- 当运行 Car 对象 的 run 方法和 Ship 对象的 run 方法时,使用动态代理的方式在方法执行前和执行后添加输出语句
代码演示:
1)Vehicle.java
public interface Vehicle {
public void run();
public String fly(String height);
}
2)Car.java 和 Ship.java
public class Car implements Vehicle{
@Override
public void run() {
System.out.println("car is running");
}
@Override
public String fly(String height) {
return "car is flying=" + height;
}
}
public class Ship implements Vehicle{
@Override
public void run() {
System.out.println("ship is running");
}
@Override
public String fly(String height) {
return "ship is flying=" + height;
}
}
3)创建代理:
package com.lhs.Proxy;
import com.lhs.bean.Vehicle;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;
import java.lang.reflect.Method;
public class MyProxy {
public static Vehicle getProxy(Vehicle vehicle) {
Vehicle res = (Vehicle) Proxy.newProxyInstance(MyProxy.class.getClassLoader(),
new Class[]{Vehicle.class}, new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("开始执行");
Object res = method.invoke(vehicle, objects);
System.out.println("结束执行");
return res;
}
});
return res;
}
}
通过调用Proxy.newProxyInstance
方法动态地创建代理实例
newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):
- ClassLoader loader:类加载器用于定义代理类。
- Class<?>[] interfaces:代理类需要实现的接口列表。
- InvocationHandler h:调用处理器实例,用于处理方法调用。
4)测试:
@Test
public void test04(){
Car car = new Car();
Ship ship = new Ship();
Vehicle proxy = MyProxy.getProxy(car);
Vehicle proxy1 = MyProxy.getProxy(ship);
proxy.run();
proxy1.run();
}
2.2 自制简易AOP
1)将上述代理在方法执行前和执行后插入的代码封装成一个方法
public class MyAOP {
public static void before(Object o, Method method, Object[] objects){
System.out.println("执行方法="+method.getName()+"参数="+ Arrays.toString(objects));
}
public static void after(Method method, Object result){
System.out.println("返回结果为="+result);
}
}
2)修改代理类
public class MyProxy {
public static Vehicle getProxy(Vehicle vehicle) {
Vehicle res = (Vehicle) Proxy.newProxyInstance(MyProxy.class.getClassLoader(),
new Class[]{Vehicle.class}, new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
MyAOP.before(o,method,objects);
Object res = method.invoke(vehicle, objects);
MyAOP.after(method,res);
return res;
}
});
return res;
}
}
3)测试
@Test
public void test04(){
Car car = new Car();
Ship ship = new Ship();
Vehicle proxy = MyProxy.getProxy(car);
Vehicle proxy1 = MyProxy.getProxy(ship);
proxy.fly("100");
proxy1.fly("1000");
}
2.3 AOP编程快速入门
1)导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.8</version>
</dependency>
2)配置bean.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"
xmlns:aop="http://www.springframework.org/schema/aop"
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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- Bean definitions go here -->
<context:component-scan base-package="com.lhs" />
<aop:aspectj-autoproxy/>
</beans>
3)创建切面类
@Component
@Aspect
public class MyAspect {
@Before(value = "execution(public String com.lhs.bean.Car.fly(String))")
public void showBeginLog(JoinPoint joinPoint){
System.out.println("前置通知");
}
@AfterReturning(value = "execution(public String com.lhs.bean.Car.fly(String))")
public void showSuccessEndLog(JoinPoint joinPoint){
System.out.println("返回通知");
}
@AfterThrowing(value = "execution(public String com.lhs.bean.Car.fly(String))")
public void showExceptionLog(JoinPoint joinPoint){
System.out.println("异常通知");
}
@After(value = "execution(public String com.lhs.bean.Car.fly(String))")
public void showFinallyEndLog(JoinPoint joinPoint){
System.out.println("最终通知");
}
}
4)测试
@Test
public void test04(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("bean.xml");
Vehicle car = ioc.getBean("car", Vehicle.class);
car.fly("100");
System.out.println(car.getClass());
}
输出结果:
前置通知
car的飞行高度为:100米
返回通知
最终通知
class jdk.proxy2.$Proxy22
2.4 切入表达式
execution用于匹配方法执行:execution(修饰符 放回类型 包名.方法名(参数类型))
表达式:
-
匹配所有公有方法:
execution(public * *(..))
这个表达式匹配所有返回任何类型,名为任何名称,参数为任何类型和数量的公有方法。
-
匹配特定包下的所有方法:
execution(* com.example.service.*.*(..))
这匹配
com.example.service
包下任何类的所有方法,不论返回类型、方法名、参数。 -
匹配特定类的特定方法:
execution(String com.example.service.MyService.*(..))
这匹配
com.example.service.MyService
类中返回类型为String
的所有方法。 -
匹配抛出特定异常的方法:
execution(* *(..) throws java.io.IOException)
这个表达式匹配所有返回任何类型,参数为任何类型和数量,且声明抛出
IOException
的方法。 -
匹配所有setter方法:
execution(public void set*(String))
这匹配所有公有的、返回类型为
void
、名字以 “set” 开始、并接受一个String
类型参数的方法。
注意:切入表达式也可以指向接口的方法, 这时切入表达式会对实现了接口的类/对象生效
2.5 JoinPoint
通过 JoinPoint 可以获取到调用方法的签名
@Before(value = "execution(public String com.lhs.bean.Car.fly(String))")
public void showBeginLog(JoinPoint joinPoint){
joinPoint.getSignature().getName(); // 获取目标方法名
joinPoint.getSignature().getDeclaringType().getSimpleName(); // 获取目标方法所属类的简单类名
joinPoint.getSignature().getDeclaringTypeName(); // 获取目标方法所属类的类名
joinPoint.getSignature().getModifiers(); // 获取目标方法声明类型(public、private、protected)
Object[] args = joinPoint.getArgs(); // 获取传入目标方法的参数,返回一个数组
joinPoint.getTarget(); // 获取被代理的对象
joinPoint.getThis(); // 获取代理对象自己
}
2.6 返回通知获取结果
在returning参数中可以指定返回结果的名称,并在接收时要保证名称一致。
@AfterReturning(value = "execution(public String com.lhs.bean.Car.fly(String))",returning = "res")
public void showSuccessEndLog(JoinPoint joinPoint,Object res){
System.out.println("返回通知");
System.out.println("返回结果为:"+res);
}
2.7 异常通知中获取异常
在throwing参数中可以指定异常的名称,并在接收时要保证名称一致。
@AfterThrowing(value = "execution(public String com.lhs.bean.Car.fly(String))",throwing = "throwable")
public void showExceptionLog(JoinPoint joinPoint,Throwable throwable){
System.out.println("异常通知");
System.out.println("异常信息:"+throwable);
}
2.8 环绕通知
环绕通知可以完成其它四个通知要做的事情
示例:
@Around(value = "execution(public String com.lhs.bean.Car.fly(String))")
public void around(ProceedingJoinPoint joinPoint){
System.out.println("前置通知");
try {
// 执行目标方法
joinPoint.proceed();
System.out.println("返回通知");
} catch (Throwable e) {
System.out.println("异常通知");
throw new RuntimeException(e);
} finally {
System.out.println("最终通知");
}
}
2.9 切入点表达式重用
为了统一管理切入点表达式,可以使用切入点表达式重用技术。
public class MyAspect {
// 创建切入点,指定要切入的方法
@Pointcut(value = "execution(public String com.lhs.bean.Car.fly(String))")
public void myPointcut(){}
// 使用切入点指定方法
@Before(value = "myPointcut()")
public void showBeginLog(JoinPoint joinPoint){
System.out.println("前置通知");
}
@AfterReturning(value = "myPointcut()",returning = "res")
public void showSuccessEndLog(JoinPoint joinPoint,Object res){
System.out.println("返回通知");
System.out.println("返回结果为:"+res);
}
@AfterThrowing(value = "myPointcut()",throwing = "throwable")
public void showExceptionLog(JoinPoint joinPoint,Throwable throwable){
System.out.println("异常通知");
System.out.println("异常信息:"+throwable);
}
@After(value = "myPointcut()")
public void showFinallyEndLog(JoinPoint joinPoint){
System.out.println("最终通知");
}
}
2.10 切面优先级问题
使用
@order(value=n)
可以来控制切面的优先级,n 值越小,优先级越高。通知的调用顺序类似 Filter 的过滤链式调用机制。
Aspect01:
@Component
@Aspect
@Order(1)
public class MyAspect {
@Pointcut(value = "execution(public String com.lhs.bean.Car.fly(String))")
public void myPointcut(){}
@Before(value = "myPointcut()")
public void showBeginLog(JoinPoint joinPoint){
System.out.println("前置通知01");
}
@AfterReturning(value = "myPointcut()",returning = "res")
public void showSuccessEndLog(JoinPoint joinPoint,Object res){
System.out.println("返回通知01");
System.out.println("返回结果01为:"+res);
}
@AfterThrowing(value = "myPointcut()",throwing = "throwable")
public void showExceptionLog(JoinPoint joinPoint,Throwable throwable){
System.out.println("异常通知01");
System.out.println("异常信息01:"+throwable);
}
@After(value = "myPointcut()")
public void showFinallyEndLog(JoinPoint joinPoint){
System.out.println("最终通知01");
}
}
Aspect02:
@Component
@Aspect
@Order(2)
public class MyAspect01 {
@Pointcut(value = "execution(public String com.lhs.bean.Car.fly(String))")
public void myPointcut(){}
@Before(value = "myPointcut()")
public void showBeginLog(JoinPoint joinPoint){
System.out.println("前置通知02");
}
@AfterReturning(value = "myPointcut()",returning = "res")
public void showSuccessEndLog(JoinPoint joinPoint,Object res){
System.out.println("返回通知02");
System.out.println("返回结果02为:"+res);
}
@AfterThrowing(value = "myPointcut()",throwing = "throwable")
public void showExceptionLog(JoinPoint joinPoint,Throwable throwable){
System.out.println("异常通知02");
System.out.println("异常信息02:"+throwable);
}
@After(value = "myPointcut()")
public void showFinallyEndLog(JoinPoint joinPoint){
System.out.println("最终通知02");
}
}
执行后控制台输出:
前置通知01
前置通知02
car的飞行高度为:100米
返回通知02
返回结果02为:car is flying=100
最终通知02
返回通知01
返回结果01为:car is flying=100
最终通知01
2.11 基于XML配置AOP
<?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" xmlns:aop="http://www.springframework.org/schema/aop" 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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.lhs.aop.xml"/>
<!-- 开启基于注解的 AOP 功能 -->
<aop:aspectj-autoproxy/>
<!-- 配置 bean -->
<bean id="smartAnimalAspect" class="com.lhs.aop.xml.SmartAnimalAspect"/>
<!--配置 SmartDog-->
<bean class="com.lhs.aop.xml.SmartDog" id="smartDog"/>
<aop:config>
<!-- 配置统一切入点 -->
<aop:pointcut expression="execution(public float
com.lhs.aop.xml.SmartDog.getSum(float, float))"
id="myPointCut"/>
<aop:aspect ref="smartAnimalAspect" order="1">
<!-- 配置各个通知对应的切入点 -->
<aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
<aop:after-returning method="showSuccessEndLog" pointcut-ref="myPointCut" returning="res"/>
<aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="throwable"/>
<aop:after method="showFinallyEndLog" pointcut-ref="myPointCut"/>
<!-- 还可以配置环绕通知 -->
<!-- <aop:around method=""/> -->
</aop:aspect>
</aop:config>
</beans>