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 {@Testpublic void getMonster() {//创建容器ApplicationContext//该容器和容器配置文件关联//建议用接口接收,ApplicationContext ioc =new ClassPathXmlApplicationContext("beans.xml");//拿到容器是为拿到容器关联配置文件中的Java对象//可以通过getbean获取对应的对象,通过id,默认返回的类型是object,运行类型是monster/*这里的运行类型可以改为monter,因为getclass方法获取到该对象的运行类型为monterObject 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的idString[] 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<>();//构造器//接收一个容器的配置文件,该文件默认在srcpublic 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));//得到rootdocumentElement 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>
@Testpublic 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
测试文件
@Testpublic 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;}@Overridepublic 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;}@Overridepublic 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 {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {// 在bean初始化之前可以进行的操作System.out.println("Before initialization: " + beanName);return bean; // 返回bean本身或修改后的bean}@Overridepublic 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/beanshttp://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 {@Testpublic 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//所以这一要创建一个mapprivate 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得到valueString 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 分配idif(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();//类的小写首字母作为idioc.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/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.component"/></beans>
在IOC容器中查找待装配的组件的类型,如果有唯一的bean匹配,则使用该bean装配。
@Controller
public class UserAction {@Autowiredprivate 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/contexthttp://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.创建一个InvocationHandlerInvocationHandler 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();//如果执行方发时,出现异常,就会进入catchSystem.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>