Spring01
spring框架
spring是轻量级的容器框架
spring framework
1、Spring核心学习内容 IOC、AOp, jdbcTemplate,声明式事务
2、IOC:控制反转,孚以管理部8号对象
3.AOP:切面编程4.JDBCTemplate:是spring提供一套访问数据库的技术,应用性强,相对好理解5.声明式事务:基于ioc/aop实现事务管理,理解有需要小伙伴花时间6.IOC, AOP 是重点同时难点
重要概念
1.Spring可以整合其他的框架(老韩解读: Spring是管理框架的框架)
2.Spring有两个核心的概念: IOC 和 AOP
3.IOC [Inversion Of Control反转控制]。传统的开发模式[JdbcUtils/反射]程序---->环境//程序读取环境配置,然后自己创建对象
1.spring根据配置文件xml注解,创建对象,并放入容器中,并且可以完成对象之间的依赖.
spring可以整合其他框架,是管理其他框架的框架
spring中有两个核心概念:ioc控制反转 aop 切面编程文件
在传统开发中:程序读取环境配置信息{可能会用到new,或者反射}然后自己创建对象
在ioc开发模式:容器中创建好对象---->被程序使用
容器创建对象的方法:1.xml配置文件,2.注解
1.spring根据配置文件或者配置文件创建对象,并放入到容器中;并完成对象之间的依赖
2.当需要某个对象实例的时候,可以直接从容器中获取
3.编写人员可以更加关注对象完成相应的业务
4.DI{dependency injection}依赖注入
5.Spring最大的价值,通过配置提供给程序使用
实例
package com.Test;
import com.bean.Monster;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @Author: zs
* @Description: TODO
* @DateTime: 2024/9/9 13:56
**/
public class MonsterTest {
@Test
public void getMonster() {
//创建容器ApplicationContext
//该容器和容器配置文件关联
//建议用接口接收,
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans.xml");
//拿到容器是为拿到容器关联配置文件中的Java对象
//可以通过getbean获取对应的对象,通过id,默认返回的类型是object,运行类型是monster
/*这里的运行类型可以改为monter,
因为getclass方法获取到该对象的运行类型为monter
Object monster01 = ioc.getBean("monster01");*/
Monster monster01 = (Monster) ioc.getBean("monster01");
//完成向下转型之后可以分别获取属性
//输出
System.out.println("monster01="+ monster01 + monster01.getClass());
System.out.println(monster01.getName()+monster01.getSkill()+monster01.getMonsterid());
System.out.println("ok");
//可以在获取的时候直接指定class类型
Monster monster2 = ioc.getBean("monster01", Monster.class);
System.out.println(monster2.getName()+monster2.getSkill()+monster2.getMonsterid());
}
}
配置文件编写
<?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">
<!-- 配置monster对象Javabean-->
<!-- 在beans中可以配置多个bean-->
<!--bean表示一个对象-->
<!-- class创建Java对象类的全路径-->
<!-- Spring 底层使用反射创建的-->
<!-- id表示Java对象在spring容器中的id,通过id可获得对象 id是唯一的-->
<!-- <property name="name" value="牛哈"/>用于给对象属性赋值,如果不改就是默认值-->
<bean class="com.bean.Monster" id="monster01">
<property name="monsterid" value="100"/>
<property name="name" value="牛哈"/>
<property name="skill" value="叫"/>
</bean>
<!-- 完成Java对象的创建-->
</beans>
package com.bean;
/**
* @Author: zs
* @Description: TODO
* @DateTime: 2024/9/9 13:26
**/
public class Monster {
private Integer monsterid;
private String name;
private String skill;
public Monster(Integer monsterid, String name, String skill) {
this.monsterid = monsterid;
this.name = name;
this.skill = skill;
}
public Monster() {
}
//无参构造器一定要写,因为反射要用
public Integer getMonsterid() {
return monsterid;
}
public void setMonsterid(Integer monsterid) {
this.monsterid = monsterid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
}
classpathxmlapplicationcontext 为什么可以读取到beans.xml
public void classPath() { File file = new File(this.getClass().getResource("/").getPath()); System.out.println(file); //结果 /*D:\javaprogram\Spring\out\production\Spring*/ //读取路径是out目录 }
debug时会发现:beandefinitionmap会保存类的信息,但是不创建对象
当需要对象时,通过反射创建对象时用;
IOC容器
底层机制
当代码执行到
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans.xml");
时,容器已经创建完毕
在beanfactory下的beandefinitionmap中就会保存配置文件中的信息,通过集合的方式 大小512
图一
ConcurrentHashMap类型时集合,存bean节点配置信息
在beanDefinitionMap中有属性table
table为数组 类型为ConcurrentHashMap$node
因为是数组类型,可以存放很多bean对象信息,就是bean.xml配置文件
初始化为512, 会自动扩容
图二
通过hash算法monster01对象信息就保存在index = 217 位置
保存是以 ConcurrentHashMap$node类型
key值就是beans.xml文件中的id值
value 就是monster01对象的[属性/属性值/是否为懒加载]
属性值保存在propertyValueList,就是记录monster01对象的属性值
beandefinitionmap只保存了类信息,并不保存对象
类的对象用concurrenthashmap$node的类型保存,类型为基于泛型的,
因为保存类的对象时,会默认为单例所以存在这个位置
在beanfactory中的另一个属性singletonObjects
类型为concurrentHashMap
其中有个属性table 类型是concurrenthashmap$node
如果在beans.xml文件中配置的对象时单例的 就是初始化放在里边
当我们getbean(“id”)时,首先会在底层查beandifinitionmap中按照id 名字查询是为单例,如果为单例会在singletonobject[类似单例池的作用]查找并返回,若不是单例会通过动态反射机制创建一个对象放进去.
为了方便我们快速查找定位,直接存入id[就是在beans.xml文件中配置的bean的名称]值,
如果先查看自己定义了多少对象,通过beandefinitionnames方法查找
//查看容器注入了那些bean对象 输出bean的id
String[] beanDefinitionNames = ioc.getBeanDefinitionNames();
for(String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
原理模拟
手动开发一个简单的spring基于XML配置的程序
第一步引入jar包{dom4j}
xml文件用的是上边的deans.xml
类的代码
package com.applicationcontext;
import com.bean.Monster;
import com.sun.org.apache.bcel.internal.generic.NEW;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Author: zs
* @Description: 实现spring的一个简单容器
* 实现bean.xml文件的解析,并生成容器
* @DateTime: 2024/9/10 12:09
**/
public class ApplicationContext {
private ConcurrentHashMap<String, Object> singletonobject =
new ConcurrentHashMap<>();
//构造器
//接收一个容器的配置文件,该文件默认在src
public ApplicationContext(String iocBeanXmlFile) throws DocumentException, ClassNotFoundException, InstantiationException, IllegalAccessException {
//1, 得到类加载路径"
String path = this.getClass().getResource("/").getPath();
// 创建saxreader,它用于读取 XML 文件并将其转换为 Java 对象。
// 这个类是基于 SAX (Simple API for XML) 解析器的,
// 它允许你以流的方式读取 XML 文件,而不需要将整个文件加载到内存中,
SAXReader saxReader = new SAXReader();
//得到document对象
Document document = saxReader.read(new File(path + iocBeanXmlFile));
//得到rootdocument
Element root = document.getRootElement();
//获取bean元素
Element bean = (Element) root.elements("bean").get(0);
//获取id,属性,属性值,类的全路径
String id = bean.attributeValue("id");
String classPath = bean.attributeValue("class");
List<Element> property = bean.elements("property");;
//遍历
Integer monsterId =Integer.parseInt(property.get(0).attributeValue("value"));
String name = property.get(1).attributeValue("value");
String skill = property.get(2).attributeValue("value");
// 使用反射创建对象
Class<?> aclass = Class.forName(classPath);
//这里o对象就是monster,
Monster o =(Monster) aclass.newInstance();
//给对象转化类型,赋值
//1.反射赋值
// Method[] declaredMethods = aclass.getDeclaredMethods();
// for (Method declaredMethod : declaredMethods ) {
// declaredMethod.invoke();
// }
//2.直接赋值
o.setMonsterid(monsterId);
o.setName(name);
o.setSkill(skill);
//将创建的对象放入容器中
singletonobject.put(id, o);
}
public Object getBean(String id) {
//获取容器中的对象
return singletonobject.get(id);
}
}
测试类的编写
package com.applicationcontext;
import com.bean.Monster;
import org.dom4j.DocumentException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @Author: zs
* @Description: TODO
* @DateTime: 2024/9/10 12:18
**/
public class ApplicationContextTest {
public static void main(String[] args) throws DocumentException, ClassNotFoundException, InstantiationException, IllegalAccessException {
ApplicationContext ioc = new ApplicationContext("beans.xml");
Monster monster01 = (Monster)ioc.getBean("monster01");
System.out.println(monster01);
System.out.println(monster01.getClass());
System.out.println(monster01.getName());
System.out.println("ok");
}
}
Spring管理bean-IOC
bean的配置方式1.基于xml文件配置的方式
2.基于注解
bean的管理:1.创建bean对象
2.注入属性
基于xml形式
1.通过类型获取
<!-- 配置monster 通过类型获取-->
<bean class="com.bean.Monster">
<!--
1.当我们给某个bean对象设置属性时
底层使用setter方法完成的
-->
<property name="monsterid" value="100"/>
<property name="name" value="牛哈"/>
<property name="skill" value="叫"/>
</bean>
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans.xml");
//直接传入class类型
Monster bean = ioc.getBean(Monster.class);
System.out.println("bean" + bean);
}
要求类型获取bean,要求容器IOC中的同一个类的bean只能有一个,否则会抛出异常NouUniqueBeanDefinitionException
应用场景:在一个线程中只需要一个对象实例的情况(单例)
2.通过构造器配置bean
<!-- 配置monster对象,通过构造器-->
<bean id="monster03" class="com.bean.Monster">
<!-- index索引表示构造器的第几个参数 从零开始计算-->
<!-- 除了index 还可以通过name / type 来指定
类的构造器,不能有完全相同类型顺序的构造器
-->
<constructor-arg value="200" index="0"/>
<constructor-arg value="猴" index="1"/>
<constructor-arg value="变" index="2"/>
</bean>
@Test
public void setBeanByConstructor(){
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans.xml");
Monster monster03 = ioc.getBean("monster03",Monster.class);
System.out.println("monster03=" + monster03);
}
通过P名称空间配置
//通过p名称空间来设置属性
@Test
public void setBeanByCP(){
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans.xml");
Monster monster06 = ioc.getBean("monster06", Monster.class);
System.out.println(monster06);
}
<!-- 通过P名称空间配置-->
<!-- 在编写代码时.p会报错,p空间没绑定-->
<!-- 解决方法:光标放在爆红的地方,按alt+int 有时需要多试几次-->
<bean id="monster06" class="com.bean.Monster"
p:monsterid="500"
p:name="红孩儿"
p:skill="玩火"
/>
xmlns:p="http://www.springframework.org/schema/p"
引用其他bean
测试文件
@Test
public void setBeanByRef(){
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans.xml");
MemberServiceImpl memberService = ioc.getBean("memberService", MemberServiceImpl.class);
memberService.add();
}
DaoImpl
public class MenberDAOImpl {
//构造器.....
public MenberDAOImpl() {
System.out.println("MenberDAOImpl 构造器");
}
// 完成添加
public void add(){
System.out.println("MemberDaoImpl add()方法");
}
}
ServiceImpl
public class MemberServiceImpl {
private MenberDAOImpl menberDao;
public MenberDAOImpl getMenberDao() {
return menberDao;
}
public void setMenberDao(MenberDAOImpl menberDao) {
this.menberDao = menberDao;
}
public void add(){
System.out.println("service方法被调用");
menberDao.add();
}
}
配置文件
<!-- 配置memberdaoimpl对象-->
<bean class="com.dao.MenberDAOImpl" id="memberDAO"/>
<!-- 配置menmberserviceimpl对象-->
<!-- ref="memdao" 表示 memberserviceimpl对象的属性memberDao引用的对象是id = memberDao
的对象-->
<!-- 体现出spring容器的依赖注入-->
<bean class="com.service.MemberServiceImpl" id="memberService">
<property name="menberDao" ref="memberDAO"/>
</bean>
引用内部bean对象
<!-- 配置memberserviceimpl-使用内部bean-->
<bean class="com.service.MemberServiceImpl" id="memberService2">
<property name="memberDao" >
<bean class="com.dao.MemberDAOImpl"/>
</property>
</bean>
引用集合/数组类型的值赋值
list
<!-- 配置master对象-->
<bean class="com.bean.Master" id="master">
<property name="name" value="老头"/>
<!-- 给list属性赋值-->
<property name="monsterList" >
<list>
<ref bean="monster06"/>
<ref bean="monster03"/>
</list>
</property>
</bean>
<!-- 配置master对象-->
<bean class="com.bean.Master" id="master">
<property name="name" value="老头"/>
<!-- 给list属性赋值-->
<property name="monsterList" >
<list>
<!-- 引用的方式-->
<ref bean="monster06"/>
<ref bean="monster03"/>
<!-- 内部-->
<bean class="com.bean.Monster">
<property name="name" value="老鼠"/>
<property name="monsterid" value="100"/>
<property name="skill" value="toushi"/>
</bean>
</list>
</property>
<!-- 给map属性赋值-->
<property name="monsterMap">
<map>
<entry>
<key>
<value>monster03</value>
</key>
<ref bean="monster03"/>
</entry>
<entry>
<key>
<value>monster06</value>
</key>
<ref bean="monster06"/>
</entry>
</map>
</property>
<property name="monsterSet">
<set>
<ref bean="monster03"/>
<ref bean="monster06"/>
<bean class="com.bean.Monster">
<property name="name" value="男"/>
<property name="monsterid" value="1000"/>
<property name="skill" value="吃"/>
</bean>
</set>
</property>
<!-- array标签中的使用value 还是bean,ref 需要根据实际情况-->
<property name="monsterName">
<array>
<value>怪</value>
<value>女</value>
</array>
</property>
<property name="pros">
<props>
<prop key="username">root</prop>
<prop key="password">123456</prop>
<prop key="ip">127.0.0.1</prop>
</props>
</property>
</bean>
使用util名称空间创建list
<!-- 配置bookstore对象-->
<bean class="com.bean.BookStore" id="bookStore">
<property name="bookList" ref="myBookList"/>
<!-- <list>-->
<!-- <value>三国演义</value>-->
<!-- <value>水浒传</value>-->
<!-- <value>你好</value>-->
<!-- </list>-->
<!-- </property>-->
</bean>
<!-- 定义一个utillist 并指定一个id 可以达到复用的效果-->
<util:list id="myBookList">
<value>三国演义2</value>
<value>水浒传2</value>
<value>你好2</value>
</util:list>
xmlns:util="http://www.springframework.org/schema/util"
级联属性赋值
在编程中,级联属性赋值通常指的是在对象之间设置属性值时,将一个对象的属性值赋给另一个对象的对应属性。这种操作在面向对象编程中很常见,尤其是在处理具有复杂关系的对象时。级联赋值可以简化代码,避免重复编写相同的赋值语句。
以Java为例,假设我们有两个类 Person
和 Address
,其中 Person
类包含一个 Address
类型的属性:
public class Address {
private String street;
private String city;
private String zipCode;
// 构造器、getter和setter省略
}
public class Person {
private String name;
private Address address;
// 构造器、getter和setter省略
}
如果我们想要级联地为 Person
对象的 address
属性赋值,可以这样做:
public class Main {
public static void main(String[] args) {
Address address = new Address();
address.setStreet("123 Main St");
address.setCity("Anytown");
address.setZipCode("12345");
Person person = new Person();
person.setName("John Doe");
person.setAddress(address); // 级联赋值
// 现在person对象有了一个包含地址信息的address属性
}
}
emp类创建
package com.bean;
/**
* @Author: zs
* @Description: TODO
* @DateTime: 2024/9/10 17:01
**/
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;
}
@Override
public String toString() {
return "Emp{" +
"name='" + name + '\'' +
", dept='" + dept + '\'' +
'}';
}
}
dept类创建
package com.bean;
/**
* @Author: zs
* @Description: TODO
* @DateTime: 2024/9/10 17:00
**/
public class Dept {
private String name;
public Dept() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dept{" +
"name='" + name + '\'' +
'}';
}
}
级联配置文件
<!-- 配置dept-->
<bean class="com.bean.Dept" id="dept"/>
<!-- 配置emp-->
<bean class="com.bean.Emp" id="emp">
<property name="name" value="jj"/>
<property name="dept" ref="dept"/>
<!-- 这我希望给dept的name属性赋值-->
<property name="dept.name" value="开发部门"/>
</bean>
通过静态工厂获取对象
创建一个静态工厂类
package com.factory;
import com.bean.Monster;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: zs
* @Description: TODO
* @DateTime: 2024/9/10 17:22
**/
public class MyStaticFactory {
private static Map<String, Monster> monsterMap;
//使用static代码块进行初始化
//static代码块特点:随着类的加载而加载,并且只加载一次
static {
monsterMap = new HashMap<>();
monsterMap.put("monster01", new Monster(1000,"你啦","跑"));
monsterMap.put("monster02",new Monster(100000,"你","走走走"));
}
//提供一个方法,返回monster对象
public static Monster getMonster(String key) {
return monsterMap.get(key);
}
}
配置文件
<!-- 配置一个monster对象-->
<!-- 通过静态工厂类获取/配置bean-->
<!-- class 是静态工厂的全路径-->
<!-- foctory-mehtod 表示是静态工厂用哪个方法返回对象-->
<!-- constructor-arg value 返回静态工厂的那个对象-->
<bean id="my_monster01" class="com.factory.MyStaticFactory"
factory-method="getMonster">
<constructor-arg value="monster02"/>
</bean>
不管获取多少次都是通一个对象
动态实例工厂
<!-- 配置monster对象-->
<bean id="myInstanceFactory" class="com.factory.MyInstanceFactory"/>
<bean id="my_monster02" factory-bean="myInstanceFactory" factory-method="getMonster">
<constructor-arg value="monster03"/>
</bean>
通过factory bean
package com.factory;
import com.bean.Monster;
import org.springframework.beans.factory.FactoryBean;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: zs
* @Description: TODO
* @DateTime: 2024/9/11 12:55
**/
public class MyFactoryBean implements FactoryBean<Monster> {
private String key;//就是配置时,指定获取的对象
private Map<String, Monster> monsterMap;
{
monsterMap = new HashMap<String, Monster>();
monsterMap.put("monster05", new Monster(500,"反复","请求"));
monsterMap.put("monster06", new Monster(600,"ff","qq"));
}
public void setKey(String key) {
this.key = key;
}
public Monster getObject() throws Exception {
return monsterMap.get(key);
}
public Class<?> getObjectType() {
return Monster.class;
}
public boolean isSingleton() {
return true;
}
}
<!-- 配置factorybean-->
<bean id="my_monster05" class="com.factory.MyFactoryBean">
<property name="key" value="monster05"/>
</bean>
bean的重用
<bean id="monster11" class="com.hspedu.spring.beans.Monster" parent="monster10"/>
或者
使用abstract = true 抽象用来继承
但是设置之后本类不能被实例化
bean创建顺序
默认是按照编写的顺序
当有依赖对象时,会先创建被依赖的对象
ioc是一个整体,先创建对象最后完成依赖关系 除depende之外
bean的单例和多实例
在spring容器中,默认按照单例创建的,即配置一个一个bean对象后,ioc容器只会创建一个bean实例
多例设置
添加标签
scope = "prototype"
设置多实例时,实例只会在执行getbean时创建
单例模式默认是lazy-init= false;不是懒加载,所以容器一加载对象就会创建
bean的生命周期
bean对象的创建是由jvm完成的
1,执行构造器
2,执行set方法
3,调用bean的初始化方法
4,使用bean
5,容器关闭时,调用销毁方法
bean的后置处理器
这个对象会在bean初始化方法调用之前和调用之后调用
功能可以操作整个bean文件的对象,就像一个切面
在Spring框架中,Bean后置处理器(Bean Post Processor)是一种特殊的组件,它允许你在Spring容器初始化任何bean之后,但在bean的属性被设置之后进行额外的处理。Bean后置处理器可以用来修改bean的定义、属性值,或者根据特定的逻辑决定是否要返回一个代理而不是原始bean实例。
关键特点:
1.实现接口:Bean后置处理器需要实现 org.springframework.beans.factory.config.BeanPostProcessor
接口。
两个主要方法:
postProcessBeforeInitialization(Object bean, String beanName)
:在bean的初始化方法(如@PostConstruct
注解的方法或InitializingBean
接口的afterPropertiesSet
方法)之前调用。postProcessAfterInitialization(Object bean, String beanName)
:在bean的初始化方法之后调用。
3.作用时机:Bean后置处理器在Spring容器的bean生命周期的特定点被调用,允许开发者在bean完全初始化之前和之后执行自定义逻辑。
4.返回值:这两个方法都可以返回bean本身或一个代理对象。如果返回null,则Spring容器会忽略该bean,不再进行后续处理。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 在bean初始化之前可以进行的操作
System.out.println("Before initialization: " + beanName);
return bean; // 返回bean本身或修改后的bean
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 在bean初始化之后可以进行的操作
System.out.println("After initialization: " + beanName);
return bean; // 返回bean本身或修改后的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后置处理器 -->
<bean id="myBeanPostProcessor" class="com.example.MyBeanPostProcessor"/>
<!-- 定义其他bean -->
<bean id="myService" class="com.example.MyService">
<!-- bean的配置 -->
</bean>
<!-- 其他bean定义 -->
</bean
使用场景:
- 修改bean属性:在bean初始化前后修改其属性值。
- 条件性地返回代理:根据某些条件返回一个代理对象,例如用于AOP(面向切面编程)。
- 资源释放:在bean销毁之前执行清理工作。
- 日志记录:记录bean的创建和销毁过程。
通过过程文件给bean注入值
<!-- 配置monster-->
<context:property-placeholder location="classpath:my.properties"/>
<bean class="com.bean.Monster" id="monster10">
<property name="monsterid" value="${monsterid}"/>
<property name="name" value="${name}"/>
<property name="skill" value="${skill}"/>
</bean>
创建一个配置文件
monsterid=1000
name=jack
skill=heal
自动装配bean
bytype------->通过类型
<!-- 配置orderdao orderservice-->
<bean class="com.dao.OederDao" id="oederDao"/>
<!-- autowire = "bytype" orderservice时通过类型的方式给对象属性自动完成赋值-->
<!-- 作用:在bean文件中寻找有没有orderdao类型的对象,有的化,就会自己完成装配-->
<!-- 注意bean文件中不能有两个orderfao的类型-->
<bean autowire="byType" class="com.service.OrderService" id="orderService"/>
<!-- 手动装配-->
<!-- <property name="oederDao" ref="oederDao"/>-->
<bean autowire="byType" class="com.web.OrderAction" id="orderAction"/>
创建三个文件为:dao,service,selvet
byname---->通过名字完成自动装配
按照setxxx方法set后边的名字
spring EL表达式
界定符#{}
基于注解配置bean
基本使用
在Java编程中,注解(Annotations)是一种用于为代码提供元数据的机制。注解不会直接影响代码的执行逻辑,但可以被编译器、其他工具或运行时环境读取,从而提供额外的信息或指导。注解在很多方面被广泛使用,包括但不限于依赖注入、事务管理、日志记录、安全性检查等。
注解的基本概念:
- 1.声明注解:使用
@interface
关键字声明一个新的注解类型。
java
public @interface MyAnnotation {
String value();
}
- 2.使用注解:在类、方法、变量等元素上使用注解。
java
@MyAnnotation(value = "Example")
public class ExampleClass {
// ...
}
- 3.注解的保留策略:定义注解的生命周期,即它在源代码、字节码或运行时是否可用。通过
@Retention
注解指定。
java
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
// ...
}
- 4.注解的适用目标:通过
@Target
注解指定注解可以应用的目标类型。
java
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Target(ElementType.METHOD)
public @interface MyAnnotation {
// ...
}
- 5.注解的继承:注解默认不会被子类继承,但可以通过
@Inherited
注解指定注解可以被继承。
java
import java.lang.annotation.Inherited;
@Inherited
public @interface MyAnnotation {
// ...
}
常见的注解使用场景:
- 依赖注入:Spring框架使用
@Autowired
、@Qualifier
等注解来实现依赖注入。
java
@Autowired
private MyService myService;
- 事务管理:
@Transactional
注解用于声明方法或类的事务边界。
java
@Transactional
public void performOperation() {
// ...
}
- RESTful Web服务:JAX-RS和Spring MVC使用注解来定义资源和路由。
java
@RestController
@RequestMapping("/api")
public class MyController {
@GetMapping("/hello")
public String sayHello() {
return "Hello, World!";
}
}
- 单元测试:JUnit和TestNG使用注解来标记测试方法。
java
import org.junit.Test;
import static org.junit.Assert.*;
public class ExampleTest {
@Test
public void testAddition() {
assertEquals(2, 1 + 1);
}
}
- 日志记录:使用如
@Log
或@Log4j
等注解来简化日志记录。
java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyClass {
private static final Logger logger = LoggerFactory.getLogger(MyClass.class);
public void myMethod() {
logger.info("This is an info message");
}
}
注解处理:
- 编译时处理:注解处理器可以在编译时生成额外的源代码、资源文件或进行其他操作。
- 运行时处理:在运行时,可以通过反射API读取注解信息并根据这些信息执行特定的逻辑。
基于注解的方式配置bean,主要是项目开发中的组件,比如Controller、Service、和Dao。
组件注解的形式有
- 1.
@Component
表示当前注解标识的是一个组件。 - 2.
@Controller
表示当前注解标识的是一个控制器,通常用于Servlet。 - 3.
@Service
表示当前注解标识的是一个业务逻辑的类,通常用于service类。 - 4.
@Repository
表示当前注解标识的是一个持久化层的类,通常用于Dao类。
spring框架的注解需要引入的jar包
1.spring-aop-5.3.8.jar
仿写注解
package com.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author: zs
* @Description: TODO
* @DateTime: 2024/9/12 13:29
**/
//规定当前自定义的的注解可以作用于那些类型
@Target(ElementType.TYPE)
//指定注解的作用范围
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
//表示我们的注解可以传入一个value属性
String value() default "";
}
package com.annotation;
import org.springframework.stereotype.Component;
/**
* @Author: zs
* @Description: TODO
* @DateTime: 2024/9/12 16:01
**/
@ComponentScan(value = "com.component")
public class SpringConfig {
}
package com.annotation;
import com.applicationcontext.ApplicationContext;
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 javax.security.auth.login.Configuration;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Author: zs
* @Description: TODO
* @DateTime: 2024/9/13 12:46
**/
public class SpringApplicationContext {
//这个类的作业是模仿容器
//需要拿到class类型,因为要使用反射
//定义一个属性
private Class configClass;
//因为在框架内,bean的信息要放入容器的hashMap
//所以这一要创建一个map
private final ConcurrentHashMap<String, Object> ioc =
new ConcurrentHashMap<>();
//构造器,在初始化容器时,选用传入beans.xml文件
//
public SpringApplicationContext(Class configClass) {
//配置类不止在构造器使用,跨方法使用
//拿到配置类的class类型,就可以到的注解
//就可以拿到注解的value里的路径信息
this.configClass = configClass;
// System.out.println(this.configClass);
//获取要扫描的包
//1.先得到配置类的注解
ComponentScan componentScan =
(ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
//通过comonentScan得到value
String path = componentScan.value();
//获得value下的所有class的信息
//1.得到类的加载器
ClassLoader classLoader = ApplicationContext.class.getClassLoader();
//2.通过加载器获得要扫描包的资源路径,class文件的信息不是在src而是在out
//Java中解析路径识别不出. 所有要把查找出来com. 的路径转化为com/
path = path.replace('.', '/');
URL resource = classLoader.getResource(path);
//3.将要加载的资料(.class)路径下的文件进行遍历
//注意:如果要有子目录,需要进行递归
File file = new File(resource.getFile());
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
System.out.println(f.getAbsolutePath());
String fileAbsolutePath = f.getAbsolutePath();
//过滤,只处理.class文件
if (fileAbsolutePath.endsWith(".class")) {
//获取全类名,反射对象,放入容器中
//1.获取类名
String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.lastIndexOf(".class"));
//2.获取类的路径
String classFullName = path.replace("/", ".") + "." + className;
//判断文件是不是需要注入容器中
try {
//这时我们得到该类的class对象
//forname 可以通过反射加载类对象
//loadclass 也可以
//但是forname会调用该类的静态方法,
Class<?> aClass = classLoader.loadClass(classFullName);
if(aClass.isAnnotationPresent(Component.class) || aClass.isAnnotationPresent(Controller.class)
|| aClass.isAnnotationPresent(Service.class) ||aClass.isAnnotationPresent(Repository.class)) {
//指定value 分配id
if(aClass.isAnnotationPresent(Component.class)) {
Component component= aClass.getDeclaredAnnotation(Component.class);
String id = component.value();
if (!"".endsWith(id)) {
className = id;
}
}
//这里为什么用forname 会调用静态方法,会把类的联系加载进去
Class<?> aClazz = Class.forName(classFullName);
Object instance = aClazz.newInstance();
//类的小写首字母作为id
ioc.put(StringUtils.uncapitalize(className), instance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
//编写方法返回容器对象
public Object getBean(String beanName) {
return ioc.get(beanName);
}
}
注解自动装配
1.基于注解配置bean,也可实现自动装配,使用的注解是:@AutoWired 或者 @Resource
@AutoWired 的规则说明:
-
在IOC容器中查找待装配的组件的类型,如果有唯一的bean匹配,则使用该bean装配。
-
如待装配的类型对应的bean在IOC容器中有多个,则使用待装配的属性名作为id值再进行查找,找到就装配,找不到就抛异常。
-
操作流程
<?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.component"/> </beans>
在IOC容器中查找待装配的组件的类型,如果有唯一的bean匹配,则使用该bean装配。
@Controller
public class UserAction {
@Autowired
private UserService userService;
public void sayOk() {
System.out.println("UserAction");
userService.hi();
}
}
@Service
public class UserService {
public void hi(){
System.out.println("hi");
}
}
如待装配的类型对应的bean在IOC容器中有多个,则使用待装配的属性名作为id值再进行查找,找到就装配,找不到就抛异常。
<?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.component"/>
<bean class="com.component" id="userService200"/>
<bean class="com.component" id="userService300"/>
</beans>
3.@Resource 的规则说明:
- @Resource有两个属性是比较重要的,分别是name和type。Spring将@Resource注解的name属性解析为bean的名字,而type属性解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略;如果使用type属性,则使用byType的自动注入策略。
使用type时,对象必须是单例的]
- 如果@Resource没有name和type,则先使用byName注入策略,如果匹配不上,再使用byType策略,如果都不成功,就会报错。
建议,不管是@AutoWired 还是 @Resource,都应谨慎使用,确保理解其自动装配的规则和潜在的异常情况,以避免运行时错误。
@Controller
public class UserAction {
@Resource(name = "userService200")
private UserService userService;
public void sayOk() {
System.out.println("UserAction");
System.out.println("useraction自动装配的" + userService);
userService.hi();
}
}
配置文件不变
泛型依赖注入
泛型:
- 1.普通成员可以使用泛型(属性、方法)
- 2.使用泛型的数组,不能初始化
- 3.静态方法中不能使用类的泛型
- 4.泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
- 5.如果在创建对象时,没有指定类型,默认为Object
aop
动态代理
动态代理是 Java 中一种非常强大的特性,它允许在运行时创建一个实现了某个接口的代理对象,这个代理对象可以作为目标对象的替代品。动态代理在很多场景下非常有用,比如:
- 1.日志记录:在方法调用前后添加日志记录。
- 2.事务管理:在方法调用前后管理数据库事务。
- 3.安全检查:在方法调用前进行权限验证。
- 4.缓存:缓存方法的返回结果,避免重复计算。
- 5.延迟加载:实现对象的延迟加载机制。
- 6.远程方法调用:在远程方法调用(RMI)中,动态代理可以用来封装远程调用的细节。
动态代理的实现
在 Java 中,动态代理主要通过 java.lang.reflect.Proxy
类和 java.lang.reflect.InvocationHandler
接口来实现。
Proxy
类用于生成动态代理实例。InvocationHandler
接口定义了代理实例上的方法调用处理程序。
package com.aop;
import java.lang.reflect.*;
import java.util.Arrays;
/**
* @Author: zs
* @Description: TODO
* @DateTime: 2024/9/14 15:22
**/
public class ProxyProvider {
//创建一个目标对象,这个对象要实现接口
private SmartAnimal smart_traget;
public ProxyProvider(SmartAnimal smart_traget) {
this.smart_traget = smart_traget;
}
//创建一个代理对象,我们要用代理对象如执行目标对象的方法
public SmartAnimal getProxy() {
//1.类加载器
ClassLoader classLoader = smart_traget.getClass().getClassLoader();
//2.目标接口信息
Class<?>[] interfaces = smart_traget.getClass().getInterfaces();
//3.创建一个InvocationHandler
InvocationHandler invocationHandler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
//横切关注点
System.out.println("方法执行前" + method.getName() + "参数" + Arrays.asList(args));
//返回执行结果
//使用反射方法调用方法
result = method.invoke(smart_traget, args);
//横切接入点
System.out.println("方法执行后" + method.getName() + "结果" + result);
} catch (Exception e) {
e.printStackTrace();
//如果执行方发时,出现异常,就会进入catch
System.out.println("方法" + method.getName() + "执行异常 结果" + e.getClass().getName());
} finally {
//最中都会执行到这里
//横切关注点,最终通知
System.out.println("方法" + method.getName() + "名");
}
return result;
}
};
//创建代理对象----动态代理类型
SmartAnimal proxy = (SmartAnimal)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}
面向切面编程(Aspect-Oriented Programming,AOP)是一种编程范式,旨在将横切关注点(cross-cutting concerns)从业务逻辑中分离出来,以提高模块化。横切关注点是指那些影响多个类的问题,比如日志记录、事务管理、安全性等。AOP 通过创建切面(aspects)来实现这一点,切面可以定义在何处以及如何将额外的行为插入到程序代码中。
AOP 的关键概念:
- 1.切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是切面的一个典型例子。
- 2.连接点(Join Point):在程序执行过程中某个特定的点,比如方法的调用或异常的抛出。在Spring AOP中,连接点总是方法的执行点。
- 3.通知(Advice):在切面的某个特定的连接点上执行的动作。不同类型的通知包括“前置通知”(在方法调用之前)、“后置通知”(在方法成功执行之后)、“异常通知”(在方法抛出异常后)、“最终通知”(无论方法执行成功还是失败都会执行)和“环绕通知”(围绕方法执行的通知)。
- 4.引入(Introduction):允许我们向现有的类添加新的方法或属性。
- 5.织入(Weaving):把切面应用到目标对象并创建新的代理对象的过程。织入可以在编译时、加载时或运行时完成。
AOP 在 Spring 中的实现:
Spring AOP 是 Spring 框架的一部分,它使用代理模式来实现 AOP。Spring AOP 只支持方法级别的连接点,这意味着它只能在方法执行时进行拦截。
- 使用 @Aspect 注解:在 Spring 中,你可以使用
@Aspect
注解来定义一个切面类。 - 定义通知:使用
@Before
、@After
、@AfterReturning
、@AfterThrowing
和@Around
等注解来定义不同类型的通知。 - 配置 AOP:在 Spring 配置中启用 AOP 支持,并指定哪些类或方法应用切面。
<context:component-scan base-package="com.asp"/>
<!-- 开启基于注解的AOP-->
<aop:aspectj-autoproxy/>
切入表达式:
通过表达式的方式定位一个或多个具体的连接点
execution[[权限修饰符] [返回类型] [简单类名/全类名] [方法名] [参数列表] ]
切入表达式的注意细节
1.切入表达式也可以指向类的方法,这时切入表达式会对该类/对象生效。
2.切入表达式也可以指向接口的方法,这时切入表达式会对实现了接口的类/对象生效。
3.切入表达式也可以对没有实现接口的类,进行切入.
动态代理机制1.jdk的proxy 面向接口 2.CGlib 是面向父类
两个动态代理的区别
- 1.JDK动态代理是面向接口的,只能增强实现类中接口中存在的方法。CGlib是面向父类的,可以增强父类的所有方法。
- 2.JDK得到的对象是JDK代理对象实例,而CGlib得到的对象是被代理对象的子类。
在类和切面类在一个包下可以简写类名
JoinPoint
public void beforeMethod(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.This(); // 获取代理对象自己
}
Aop切面优先级
@order(value=n) 来控制 n 值越小,优先级越高。
运行原理类似filter
基于xml配置Aop
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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 使用xml,完成aop编程-->
<bean class="com.aopxml.SmartAnimalAspect" id="animalAspect"/>
<!-- 配置一个smartdog-->
<bean class="com.aopxml.SmartDog" id="smartDog"/>
<!-- 配置切面类-->
<aop:config>
<!-- 这里指定切面对象-->
<!-- <aop:aspect ref="animalAspect" order="10"/>-->
<!--配置切入点-->
<aop:pointcut id="myPointCut" expression="execution(public float com.aopxml.SmartDog.getSum(float,float))"/>
<!-- 这里指定切面对象-->
<aop:aspect ref="animalAspect" order="10">
<!--配置前置通知-->
<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>