spring-nfjh
Spring Xml 文件报红第一时间检查set 、get 、构造方法
准备工作
- JDK 最低版本17
- 设置Maven
- 见[王鹤老师的笔记]
本套Spring教程与其他Spring教程的区别可总结为以下几点:
第一点:手写Spring框架
第二点:手写组件扫描器
第三点:依赖倒置原则DIP
第四点:CGLIB动态代
§1 Spring启示录
当前项目
dao
UserDao
impl
UserDaoImpl_1
service
UserService
impl
UserServiceimpl_1
web
UserAction
service为了调用dao层方法,有dao层接口的实现类
web为了调用service层方法,有service层接口的实现类
缺点
若用户提出需求,程序员对项目的功能进行拓展时,就需要更改多层已经运行正常的的代码,不符合软件开发的“开闭原则”[OCP],即对扩展开放,对修改关闭
同时,也违背了“依赖倒置原则”
思想与解决方案
违背依赖倒置
上层依赖下层 : 上层因下层的改动而改动
[这样不好,违背依赖倒置原则!!!]
符合依赖倒置:
上不依赖下,"面向接口编程"
依赖倒置原则的目的,降低程序的耦合度,提高扩展力
解决思想:[控制反转]
只保留接口,把创建对象的的权利、以及对象的维护权利交出
- 重点:"让出权利"
- 出现的比较新,没有被纳入GoF23种设计模式中
解决方案:依赖注入[DI]
何如实现控制反转?
- 第一种:set注入(执行set方法给属性赋值)
- 第二种:构造方法注入(执行构造方法给属性赋值)
依赖:A对象和B对象的关系。
注入:是一种手段,通过这种手段,可以让A对象和B对象产生关系。
依赖注入:对象A和对象B之间的关系,靠注入的手段来维护。而注入包括:set注入和构造注入
控制反转是思想。依赖注入是这种思想的具体实现。
Spring是一个实现了IoC思想的容器。
注意术语:
术语 | - | - | 全称 |
---|---|---|---|
OCP | 开闭原则 | 开发原则 | Open Close Principle |
DIP | 依赖倒置原则 | 开发原则 | Dependence Inversion Principle |
IoC | 控制反转思想 | 一种新型的设计模式 | Inversion of Control |
DI | 依赖注入 | 控制反转思想的具体实现方式 | Dependency Injection |
§2 Spring 简述
SSM
Spring
SpringMVC
Mybatis
面向切面编程:[蓉姐]
AOP(Aspect Orient Programming),面向切面编程。
切面:公共的,通用的,重复的功能称为切面,面向切面编程就是将切面提取出来,单独开发,在需要调用的方法中通过动态代理的方式进行织入
1)切面:就是那些重复的,公共的,通用的功能称为切面,例如:日志,事务,权限.
2)连接点:就是目标方法.因为在目标方法中要实现目标方法的功能和切面功能.
3)切入点(Pointcut):指定切入的位置,多个连接点构成切入点.切入点可以是一个目标方法,可以是一个类中的所有方法,可以是某个包下的所有类中的方法.
4)目标对象:操作谁,谁就是目标对象.
5)通知(Advice):来指定切入的时机.是在目标方法执行前还是执行后还是出错时,还是环绕目标方法切入切面功能.
§3 Spring 入门程序
1、代码编程步骤
基本结构
1-创建一个maven项目
补充项目结构
resources 并设置为 资源文件夹
同理补充测试资源文件夹
2-修改pom.xml文件
删除build标签
添加 spring框架需要的核心依赖 spring-context
如果没有jutil 包则添加
刷新pom.xml文件
Spring6未正式发布需要添加Spring提供的仓库地址
Add the following to resolve milestone and RC versions – for example, `6.0.0-M2` or `6.0.0-RC1`:
<repository>
<id>repository.spring.milestone</id>
<name>Spring Milestone Repository</name>
<url>https://repo.spring.io/milestone</url>
</repository>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0-M2</version>
</dependency>
当加入spring context的依赖之后,会关联引入其他依赖:
spring aop:面向切面编程
spring beans:IoC核心
spring core:spring的核心工具包
spring jcl:spring的日志包
spring expression:spring表达式
3-创建项目所需的咖啡豆[无参构造、setXXX方法]
一定得有无参构造,底层使用的是反射机制通过无参构造创建对象
4-applicationContext.xml文件
- 配置文件放在resources类根路径下,可移植性高
- IDEA支持spring配置文件,有模板!!
- 在XML Configuration File → Spring Config
- 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
<bean id="userBean" class="com.nfjh.spring6.bean.User">
</bean>
id是bean的唯一标识,不能重复
class 是 对应bean类的全限定类名
-->
<bean id="" class="">
<property name="" value=""/>
<property name="" ref=""/>
<!--
㊟:value 为简单类型赋值
ref 为引用类型赋值,且要求当前bean工厂中存在该引用类型对象
ref中填写的是bean的id
-->
</bean>
</beans>
㊟:value 为简单类型赋值
ref 为引用类型赋值,且要求当前bean工厂中存在该引用类型对象
5- 创建测试方法
创建工厂
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml路径");
//此处的构造方法是可变长参数,所以如果有多个可以直接加
/*此处有多个重载方法
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, (ApplicationContext)null);
}
若项目为普通项目,不是maven的,此处出现了错误
㊟:检查是否将resources 设置为资源文件夹
*/
//返回Object
User user = (User) applicationContext.getBean("userBean");
/*
另一种写法: 直接返回的就是我们需要的类型
在传入id的同时传入类型
User user = applicationContext.getBean("userBean", User.class);
*/
项目文件的修改与导入
1、删除target文件夹
2、删除 .iml 文件
3、修改pom.xml文件中的坐标
<artifactId>MVC_SpringXml</artifactId>
修改为项目名
4、导入
Project Structure
→ Modules
→ +
→ import Modules
→ 选中项目的根路径
→ import module from external model
→ maven
→ 一直next即可
§4 Spring对IoC的实现
实现依赖注入
set注入是在对象创建之后执行
构造注入是在对象实例化的过程中执行的
set注入
- javaBean中存在符合命名规范的set方法
- applicationContext.xml文件中进行了配置
<bean id="userDaoBean" class="com.nfjh.spring6.dao.UserDao" />
<bean id="userService" class="com.nfjh.spring6.service.UserService">
<property name="userdao" ref="userDaoBean"/>
</bean>
- 启动Spring容器,解析spring.xml文件,并且实例化所有的bean对象,放到spring容器当中。
- 根据bean的id从Spring容器中获取这个对象。
<!--
name 是setXxx()方法的xxx
㊟:value 为简单类型赋值
ref 为引用类型赋值,且要求当前bean工厂中存在该引用类型对象
ref中填写的是bean的id
-->
<bean id="" class="">
<property name="" value=""/>
<property name="" ref=""/>
</bean>
public class UserDao {
private static final Logger logger = LoggerFactory.getLogger(UserDao.class);
public void insert(){
logger.debug("保存信息到数据库!\n");
}
}
public class UserService {
private UserDao userdao;
public void setUserdao(UserDao userdao) {
this.userdao = userdao;
}
public void saveUser(){
userdao.insert();
}
@Test
public void testInsert(){
ApplicationContext applicationContext = (ApplicationContext) new ClassPathXmlApplicationContext("spring-core.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.saveUser();
}
<bean id="userDao" class="com.nfjh.spring6.dao.UserDao" />
<bean id="userService" class="com.nfjh.spring6.service.UserService">
<property name="userdao" ref="userDao"/>
</bean>
构造注入
- javaBean中存在构造方法
<bean id="userDaoBean" class="com.nfjh.spring6.dao.UserDao" />
<bean id="customerServiceBean" class="com.nfjh.spring6.service.CustomerService">
<constructor-arg index="0" ref="userDaoBean"/>
</bean>
- applicationContext.xml文件中进行了配置
- 启动Spring容器,解析spring.xml文件,并且实例化所有的bean对象,放到spring容器当中。
- 根据bean的id从Spring容器中获取这个对象。
set注入的方式
使用set方法进行注入,一定得有无参构造,不要因为写了有参数的构造方法,
让无参数的方法被覆盖了,这时需要手动添加无参构造
注入方式 | 类类型 | 操作记忆 bean[id & class] > | 操作对象 |
---|---|---|---|
外部Bean | 简单 | property[name & value] | 简单类型 attrName; |
引用 | property[name & ref] | bean[id & class] | |
内部Bean | --- | property[name] > bean[class] | bean[id & class] OR 简单类型 attrName; |
级联属性 | 引用 | property[name="注入类的属性名" & ref] + property[name="注入类的属性名.属性名" & value] | bean[id & class] |
数组 | 简单 | property[name] > array > value | 简单类型[] attrName; |
引用 | property[name] > array > ref[bean] | 引用类型[] attrName; | |
List/Set | 简单 | property[name] > list/set > value | List/Set<简单类型> attrName; |
引用 | property[name] > list/set > ref[bean] | List/Set <引用类型> attrName; | |
Map | Map | property[name] > map > entry[key/key-ref &value/value-ref] | Map<类型1,类型2> attrName; |
Perperties | Perperties | property[name] > props > prop[key] | Map<String,String> |
null | --- | property[name] > null单标签 | bean[id & class] OR 简单类型 attrName; |
--- | 对哪个属性注入null值,就啥都不写 | bean[id & class] OR 简单类型 attrName; | |
空字符串 | String | property[name & value=""] | String |
String | property[name] > value单标签 | String | |
特殊符号 | property[name & value="实体符号"] | ||
property[name] > value{ <![CDATA[值]]> } |
注入方式 | 类类型 | 操作记忆 | 操作对象 |
---|---|---|---|
p 命名空间 | 简单 | bean[id & class & p:属性名="属性值" ] | 简单类型 attrName; |
引用 | bean[id & class & p:属性名-ref="bean的id"] | bean[id & class] | |
xmlns:p="http://www.springframework.org/schema/p" |
|||
c 命名空间 | 简单_属性名 | bean[id & class c:属性名="属性值"] | 简单类型 attrName; |
简单_下标 | bean[id & class c:_0="属性值"] |
简单类型 attrName; | |
引用_属性名 | bean[id & class c:属性名-ref="bean的id"] | bean[id & class] OR 简单类型 attrName; | |
引用_下标 | bean[id & class c:_0-ref="bean的id"] |
bean[id & class] OR 简单类型 attrName; | |
xmlns:c="http://www.springframework.org/schema/c" |
|||
util命名空间 | List | bean[id & class] > property[name & ref="util的id"] | util:list[id] > value |
util命名空间 | Set | -- 同List -- | util:set[id] > value |
util命名空间 | Properties | -- 同List -- | util:properties[id] > prop[key] |
外部Bean
就是使用ref属性来引入。这就是注入外部Bean
<!--声明/定义Bean-->
<bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"></bean>
<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
<!--使用ref属性来引入。这就是注入外部Bean-->
<property name="orderDao" ref="orderDaoBean"/>
</bean>
内部bean
<!--内部bean-->
<!--Ctrl + Alt + Shift + C-->
<bean id="osBeanIn" class="com.nfjh.spring6.service.OrderService">
<property name="orderDao">
<bean class="com.nfjh.spring6.dao.OrderDao"/>
</property>
</bean>
内部bean在定义的时候放在 property标签中
property标签中不加 ref
bean标签中不加 id
简单类型注入
<!--注入简单类型-->
<bean id="userBean" class="com.nfjh.spring6.bean.User">
<property name="username" value="zhangsan"/>
<property name="passwd" value="123"/>
<property name="age" value="20"/>
</bean>
哪些是简单类型
双击shift → BeanUtils → Ctrl + F12
isSimpleType()
对于Date是简单类型,但是需要严格按照格式写日期,不急麻烦
在平常使用时直接当成引用类型,使用ref进行注入
- 基本数据类型
- 基本数据类型对应的包装类
- String或其他的CharSequence子类
- Number子类
- Date子类
- Enum子类
- URI
- URL
- Temporal子类
- Locale
- Class
- 另外还包括以上简单值类型对应的数组类型。
简单类型可以用于对数据源信息的注入
级联属性
User
private String username;
private String passwd;
private int age;
以及对应的get set toString方法
Client
private User user;
private String clientName;
以及对应的get set toString方法
<!--级联属性赋值-->
<bean id="userBeanCascade" class="com.nfjh.spring6.bean.User"/>
<bean id="clientBean" class="com.nfjh.spring6.bean.Client">
<property name="clientName" value="QQ"/>
<!--使用级联属性赋值,Client 中要有getUser()方法-->
<property name="user" ref="userBeanCascade"/>
<!--使用级联属性赋值,user必须要有对应的username、passwd、age get方法-->
<property name="user.username" value="张三"/>
<property name="user.passwd" value="123456"/>
<property name="user.age" value="21"/>
</bean>
注入数组
Lseeon
private String [] lessonName ;
以及对应的set toString方法
Student
private String name;
private Lesson [] lessons;
以及对应的set toString方法
<!--
简答类型数组注入
property > array > value
private String [] lessonName ;-->
<bean id="lessonBean" class="com.nfjh.spring6.bean.Lesson">
<property name="lessonName">
<array>
<value>JAVA</value>
<value>MYBATIS</value>
<value>SPRING</value>
</array>
</property>
</bean>
<bean id="lessonBean2" class="com.nfjh.spring6.bean.Lesson">
<property name="lessonName">
<array>
<value>MYSQL</value>
<value>JDBC</value>
<value>AJAX</value>
</array>
</property>
</bean>
<!--
......
Lesson lessonBean = applicationContext.getBean("lessonBean", Lesson.class);
System.out.println(lessonBean);
//Lesson{lessonName=[JAVA, MYBATIS, SPRING]}
-->
<!--引用类型数组注入
private String name;
private Lesson [] lessons;
property > array > ref[bean]
-->
<bean id="studentBean" class="com.nfjh.spring6.bean.Student">
<property name="name" value="张三"/>
<property name="lessons">
<array>
<ref bean="lessonBean"/>
<ref bean="lessonBean2"/>
</array>
</property>
</bean>
<!--
......
Student studentBean = applicationContext.getBean("studentBean", Student.class);
System.out.println(studentBean);
//Student{name='张三', lessons=[Lesson{lessonName=[JAVA, MYBATIS, SPRING]}, Lesson{lessonName=[MYSQL, JDBC, AJAX]}]}
-->
List 和Set集合注入
Person
private List<User> userList;
private Set<User> userSet;
以及对应的set toString方法
<!--
㊟:List中的元素可以重复
Set中的元素如果重复写,不报错,但是相同元素只算1个
-->
<!--List注入
简单类型
property > list > value
引用类型
property > list > ref[bean]
-->
<!-- set注入
简单类型
property > list > value
引用类型
property > list > ref[bean]
-->
<bean id="personBean" class="com.nfjh.spring6.bean.Person">
<property name="userList">
<list>
<ref bean="userBean"/>
<ref bean="userBeanCascade"/>
</list>
</property>
</bean>
<!--
//Person{userList=[User{username='zhangsan', passwd='123', age=20}, User{username='张三', passwd='123456', age=21}], userSet=null}
//通过此程序可以得知
//通过级联属性赋值的bean可以通过 id 实例化,拿到其中的值
-->
<bean id="personBean2" class="com.nfjh.spring6.bean.Person">
<property name="userList">
<list>
<ref bean="userBean"/>
<ref bean="userBeanCascade"/>
</list>
</property>
<property name="userSet">
<set >
<ref bean="userBean"/>
<ref bean="userBeanCascade"/>
</set>
</property>
</bean>
<!--
Person{userList=[User{username='zhangsan', passwd='123', age=20}, User{username='张三', passwd='123456', age=21}], userSet=[User{username='zhangsan', passwd='123', age=20}, User{username='张三', passwd='123456', age=21}]}
-->
Map 注入
还是person
再加上一个属性
private Map<Integer,User> userMap;
以及对应的set toString方法
<!--Map注入
简单类型
property > map > entry[key & value]
引用类型
property > map > enter[key-ref & value-ref]
-->
<bean id="personMapBean" class="com.nfjh.spring6.bean.Person">
<property name="userMap">
<map>
<entry key="1" value-ref="userBean"/>
<entry key="2" value-ref="userBeanCascade"/>
</map>
</property>
</bean>
<!--
......
Person personMapBean = applicationContext.getBean("personMapBean", Person.class);
System.out.println(personMapBean);
//Person{userList=null, userSet=null, userMap={1=User{username='zhangsan', passwd='123', age=20}, 2=User{username='张三', passwd='123456', age=21}}}
-->
Perperties类型注入
/*虽然properties也是Map但是他的注入方式与Map不同*/
还是person
再加上一个属性
Properties properties ;
以及对应的set toString方法
<!--特殊Map类型Properties类型注入
property > props > prop[key](内容)
-->
<bean id="personProBean" class="com.nfjh.spring6.bean.Person">
<property name="properties" >
<props>
<!--注意 prop的key 与值都是String类型-->
<prop key="driver">com.nfjh.hello</prop>
<prop key="url">www.baidu.com</prop>
</props>
</property>
</bean>
<!--
......
Person personProBean = applicationContext.getBean("personProBean", Person.class);
System.out.println(personProBean);
//Person{userList=null, userSet=null, userMap=null, properties={driver=com.nfjh.hello, url=www.baidu.com}}
-->
注入null
User
private String username;
private String passwd;
private int age;
以及对应的get set toString方法
<!--null值的注入-->
<bean id="userNullBean" class="com.nfjh.spring6.bean.User">
<!--第一种注入方式,不注入自动注入null-->
<!--此处不给age 注入值-->
<!--第二中注入方式,手动注入null-->
<property name="username">
<null/>
</property>
<!--错误的注入,这种是注入了"null"字符串,不是注入正真的null-->
<property name="passwd" value="null"/>
</bean>
<!--
......
User userNullBean = applicationContext.getBean("userNullBean", User.class);
System.out.println(userNullBean);
System.out.println(userNullBean.getPasswd().toUpperCase());
/*
User{username='null', passwd='null', age=0}
NULL
对于第三种错的注入的解释:
如果 真的注入的是null的话,此处应该报空指针异常
但运行结果确实"null"字符串被转化成了大写的字母
所以未注入成功null
另外对于age属性,我的却没有注入值
但结果却是0 ,这点需要注意
*/
-->
注入空字符串
还是User
<!--
空字符串的注入
-->
<!--null值的注入-->
<bean id="userStrNullBean" class="com.nfjh.spring6.bean.User">
<!--第一种注入方式 value中不给值即可-->
<property name="username" value=""/>
<!--第二中注入方式,手动标签注入""-->
<property name="passwd">
<value/>
</property>
</bean>
<!--
......
User userStrNullBean = applicationContext.getBean("userStrNullBean", User.class);
System.out.println(userStrNullBean);
//User{username='', passwd='', age=0}
-->
< " ' & > 5个特殊符号的注入
<!--特殊符号的注入-->
<bean id="specialSymbolsBean" class="com.nfjh.spring6.bean.User">
<!--第一种:只用实体符号
这里就之列出一种
> >
<property name="username" value="2 < 3"/>
-->
<property name="username" value="2 > 3"/>
<!--第二种:使用value标签 + <![CDATA[2<3]]>-->
<property name="passwd">
<value> <![CDATA[2<3]]> </value>
</property>
<!--
注意点1:
要使用value标签直接放在value中是不对的!!!!
<property name="passwd" value="<![CDATA[2<3]]>"/>
注意点2:
<value></value>中的内容为注入的内容
所以如果这么写结果为
<property name="passwd">
<value>
<![CDATA[2<3]]>
</value>
</property>
User{username='2 > 3', passwd='
2<3
', age=0}
注意点3:
<![CDATA[]]> 中的字符全部要大写
注意点:
不管是使用实体符还是!<[CDATA[]]>这都是XML的语法
-->
</bean>
<!--
User specialSymbolsBean = applicationContext.getBean("specialSymbolsBean", User.class);
System.out.println(specialSymbolsBean);
//User{username='2 > 3', passwd=' 2<3 ', age=0}
-->
小结
外部bean
<property name="orderDao" ref="orderDaoBean"/>
p命名空间注入
p命名空间注入的底层还是"set"注入
所以仍然需要提供set方法
只不过这种注入方式会让spring配置更加简单
xmlns:p="http://www.springframework.org/schema/p"
User
private String username;
private String passwd;
private int age;
以及对应的get set toString方法
Client
private User user;
private String clientName;
以及对应的get set toString方法
<!--
p命名空间
1、修改头部
添加 xmlns:p="http://www.springframework.org/schema/p"
2、直接使用
简单类型
p:属性名="属性值"
引用类型
p:属性名-ref="bean的id"
-->
<bean id="userBean" class="com.nfjh.spring6.bean.User" p:age="0" p:passwd="haha" p:username="zhangsan"/>
<bean id="clientBean" class="com.nfjh.spring6.bean.Client" p:clientName="QQ" p:user-ref="userBean"/>
<!--
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("p-namespace.xml");
Client clientBean = applicationContext.getBean("clientBean", Client.class);
System.out.println(clientBean);
//Client{user=User{username='zhangsan', passwd='haha', age=0}, clientName='QQ'}
-->
c 命名空间注入
c命名空间注入的底层还是"构造"注入
所以仍然需要提供构造方法
只不过这种注入方式会让spring配置更加简单
修改就不说了,上面改p的这里改成 c 就行了
xmlns:c="http://www.springframework.org/schema/c"
Shop
private String address;
private String name;
private Date businessHours;
以及对应的三个参数构造方法
<bean id="date" class="java.util.Date"/>
<!--
<bean id="shopBean" class="com.nfjh.spring6.bean.Shop" c:_0="天津市" c:_1="小张水果" c:_2-ref="date"/>
-->
<bean id="shopBean" class="com.nfjh.spring6.bean.Shop" c:address="天津市" c:name="小张水果" c:businessHours-ref="date"/>
<!--
对于c命名空间
可以使用参数下标对参数赋值,也可以使用属性名对参数赋值
Shop shopBean = applicationContext.getBean("shopBean", Shop.class);
System.out.println(shopBean);
//Shop{address='天津市', name='小张水果', businessHours=Fri Oct 28 06:35:03 CST 2022}
-->
util命名空间
简化集合 Map ,提高代码复用率
第一步:和p一样修改
第二步:在第三行的引号中追加
使用
<util:TYPE id="">
.....
</util:TYPE>
Book
private List<String> name;
private Set<String> ISBN;
private Properties type;
以及对应的set toString方法
<util:list id="bookList">
<value>三体</value>
<value>活着</value>
</util:list>
<util:set id="bookSet">
<value>1211121</value>
<value>22332232</value>
</util:set>
<util:properties id="bookProp">
<prop key="1">科幻</prop>
<prop key="2">文学</prop>
</util:properties>
<bean id="bookBean" class="com.nfjh.spring6.bean.Book">
<property name="name" ref="bookList"/>
<property name="ISBN" ref="bookSet"/>
<property name="type" ref="bookProp"/>
</bean>
<util:list id="bookList2">
<value>流浪地球</value>
<value>诗经</value>
</util:list>
<bean id="bookBean2" class="com.nfjh.spring6.bean.Book">
<property name="name" ref="bookList2"/>
<property name="ISBN" ref="bookSet"/>
<property name="type" ref="bookProp"/>
</bean>
基于XML的自动装配
不管是使用 autowire="byName"
还是autowire="byType"
底层都是使用set方法
User
private String username;
private String passwd;
private int age;
以及对应的get set toString方法
Client
private User user;
private String clientName;
以及对应的get set toString方法
autowire="byName"
<!--根据名称自动装配
1、添加 autowire属性
2、被装配的bean的id必须是setXxx()方法的xxx
-->
<bean id="clientBean" class="com.nfjh.spring6.bean.Client" autowire="byName">
<property name="clientName" value="QQ"/>
<!--
这里没有了
<property name="user" ref="..."/>
-->
</bean>
<!--这里的id不能随便写了-->
<bean id="user" class="com.nfjh.spring6.bean.User">
<property name="age" value="20"/>
<property name="passwd" value="1221"/>
<property name="username" value="zhangsan"/>
</bean>
autowire="byType"
User 中添加该方法
public void testAutoWire(){
System.out.println("user对象已经实例化");
}
<!--
根据类型进行自动进行装配
1、添加 autowire属性 值为 byType
2、被装配的bean可以没有id,且只能有一个实例,如果有多个就会出现错误
3、如果xml文件中只有一个bean,但是爆红了,不用管,因为Spring扫描的是全部的xml文件
可以降低xml文件的报红的级别,让他不报红,或者直接不管
-->
<!--
<bean class="java.util.Date"/>
Date类型注入失败,为null
怀疑是普通类型,不走byType进行注入
-->
<bean id="user" class="com.nfjh.spring6.bean.User">
<property name="age" value="20"/>
<property name="passwd" value="1221"/>
<property name="username" value="zhangsan"/>
</bean>
<bean id="clientBean2" class="com.nfjh.spring6.bean.Client" autowire="byType">
<property name="clientName" value="QQ"/>
</bean>
<!--
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("autowire.xml");
Client clientBean = applicationContext.getBean("clientBean2", Client.class);
clientBean.getUser().testAutoWire();;
System.out.println(clientBean);
//user对象已经实例化
//Client{user=User{username='zhangsan', passwd='1221', age=20}, clientName='QQ'}
-->
spring引入外部属性配置文件
步骤
1、准备jdbc.properties文件
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:2022/spring6
username=root
password=root
initialSize=5
maxIdle=10
2、在xml文件中添加context命名空间以及约束文件
修改与util命名空间的修改时一样的
只不过把该util的地方该成context即可
3、准备好接收数据的几个属性
这里我创建了一个普通的类,用来接收数据
JDBC
private String driver;
private String url;
private String passwd;
private String initialSize;
private String maxIdle;
以及对应的set和toString方法
3、引入配置文件
<context:property-placeholder location="jdbc.properties"/>
<!--
注意这样写jdbc.properties 文件是放在resources类的根路径下的
-->
4、注入数据
<bean id="jdbcBean" class="com.nfjh.spring6.bean.JDBC" >
<property name="driver" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="passwd" value="${password}"/>
<property name="initialSize" value="${initialSize}"/>
<property name="maxIdle" value="${maxIdle}"/>
</bean>
<!--
JDBC jdbcBean = applicationContext.getBean("jdbcBean", JDBC.class);
System.out.println(jdbcBean);
//JDBC{driver='com.mysql.jdbc.Driver', url='jdbc:mysql://localhost:2022/spring6', username='34838', passwd='root', initialSize='5', maxIdle='10'}
-->
${username}加载错误
需要注意的是Spring 的配置文件中有一个坑!!!
Spring 在加载配置文件的时候,会先加载系统的环境变量
此时如果jdbc.properties文件中存在username就会被加载为电脑的用户名
而不是我们的配置
解决的方式有三种
1、直接更换名字
把username换成 user_name 或者 name 或者其他的
但注意,spring在加载时不区分大小写,所以改成 userName是没用滴
还有当使用数据源时,存在无法更改配置文件中的名称,否则无法成功创
建连接的情况,此时建议使用其他的解决方案
2、添加配置信息local-override="true"
<context:property-placeholder local-override="true" location="jdbc.properties"/>
3、[已过时,但能用]使用另一个标签,并添加p命名空间
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" p:localOverride="true">
<property name="locations" value="classpath:jdbc.properties"></property>
</bean>
§5 Bean的作用域
public class ScopeBean {
public ScopeBean() {
System.out.println("无参构造执行了!");
}
}
@Test
public void testScopeBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean-scope.xml");
System.out.println("-------------");
ScopeBean scopeBean = applicationContext.getBean("scopeBean", ScopeBean.class);
System.out.println(scopeBean);
ScopeBean scopeBean2 = applicationContext.getBean("scopeBean", ScopeBean.class);
System.out.println(scopeBean2);
ScopeBean scopeBean3 = applicationContext.getBean("scopeBean", ScopeBean.class);
System.out.println(scopeBean3);
<!--spring默认bean的构建使用单例模式
单例模式对象给的创建在
new ClassPathXmlApplicationContext()的执行中
-->
<bean id="scopeBean" class="com.nfjh.spring6.bean.ScopeBean" scope="singleton"/>
<!--
/* 单例模式下:
无参构造执行了!
-------------
com.nfjh.spring6.bean.ScopeBean@75f95314
com.nfjh.spring6.bean.ScopeBean@75f95314
com.nfjh.spring6.bean.ScopeBean@75f95314
* */
-->
<!--可以更改为原型模式(每次获取的都是新的实例对象)
此时执行new ClassPathXmlApplicationContext()时不在创建对象
在执行 getBean()方法时创建对象
-->
<bean id="scopeBean" class="com.nfjh.spring6.bean.ScopeBean" scope="prototype"/>
<!--㊟:
prototype ['prәutәtaip]
n. 原型
[计] 样机; 原型
-->
<!--
/*
-------------
无参构造执行了!
com.nfjh.spring6.bean.ScopeBean@15dcfae7
无参构造执行了!
com.nfjh.spring6.bean.ScopeBean@3da05287
无参构造执行了!
com.nfjh.spring6.bean.ScopeBean@1e636ea3
*/
-->
§6 bean的获取方式
构造方法获取bean
简单工厂获取bean
public class Sweet {
public Sweet(){}
public void getType(){
System.out.println("这是一个水果糖");
}
}
public class SweetFactory {
//注意这个方法是静态的
public static Sweet get(){
return new Sweet();
}
}
<!--
1、简单工厂模式给"工厂类"注入
2、简单工厂模式的get方法是静态方法
使用1个bean 标签即可获取
Spring提供的实例化方式,第二种:通过简单工厂模式。你需要在Spring配置文件中告诉Spring框架,调用哪个类的哪个方法获取Bean
factory-method 属性指定的是工厂类当中的静态方法。也就是告诉Spring框架,调用这个方法可以获取Bean。
-->
<bean id="sweetFactory" class="com.nfjh.spring6.factory.SweetFactory" factory-method="get"/>
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("factory.xml");
Sweet sweet = applicationContext.getBean("sweetFactory", Sweet.class);
sweet.getType();
//这是一个水果糖
Factory-bean获取bean
Sweet还是使用上面的那个,原封不动
public class SweetFactor_Bean {
//此方法不在是静态的了
public Sweet get(){
return new Sweet();
}
}
<!--
factory-bean的方式获取对象
需要两个bean标签
Spring提供的实例化方式,第三种:通过工厂方法模式。通过 factory-bean属性 + factory-method属性来共同完成
告诉Spring框架,调用哪个对象的哪个方法来获取Bean。
-->
<bean id="sweetFactoryBean" class="com.nfjh.spring6.factory.SweetFactor_Bean"/>
<bean id="sweetBean" factory-bean="sweetFactoryBean" factory-method="get"/>
<!--
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("factory.xml");
Sweet sweetBean = applicationContext.getBean("sweetBean", Sweet.class);
sweetBean.getType();
//这是一个水果糖
-->
实现FactoryBean接口重写方法获取
/*
对于实现FactoryBean接口重写getObject()的方式
其实就是对第三中方式的简化
*/
还是Sweet不动
FactoryBeanImpl
import org.springframework.beans.factory.FactoryBean;
//该类实现了FactoryBean 所以不需要在配置factory-bean
public class FactroyBeanImpl implements FactoryBean<Sweet> {
//该类重写了getObject ,所以不在需要配置 factory-method
@Override
public Sweet getObject() throws Exception {
return new Sweet();
}
@Override
public Class<?> getObjectType() {
return null;
}
//返回的对象是否为单例对象,TRUE为单例,FALSE为多例
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}
<!--FactoryBeanImpl是一个工厂bean
是实现了FactoryBean接口的特殊bean
而辅助spring实例化其它Bean对象的特殊bean
-->
<bean id="sweetFactoryBeanImpl" class="com.nfjh.spring6.factory.FactroyBeanImpl"/>
<!--
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("factory.xml");
Sweet sweetBean = applicationContext.getBean("sweetFactoryBeanImpl", Sweet.class);
sweetBean.getType();
//这是一个水果糖
-->
Con
private Date date;
private String clientLog;
private String clientType;
以及对应的set和toSring方法
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateFactoryBeanImpl implements FactoryBean<Date> {
private String strDate;
public DateFactoryBeanImpl(String strDate) {
this.strDate = strDate;
}
@Override
public Date getObject() throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.parse(strDate);
}
@Override
public Class<?> getObjectType() {
return null;
}
}
<!--通过factoryBean的方式创建Date对象-->
<bean id="date" class="com.nfjh.spring6.factory.DateFactoryBeanImpl">
<constructor-arg value="2001-09-20"/>
</bean>
<bean id="conBean" class="com.nfjh.spring6.factory.Con">
<property name="date" ref="date"/>
<property name="clientType" value="QQ"/>
<property name="clientLog" value="QQ_Log"/>
</bean>
<!--
Con conBean = applicationContext.getBean("conBean", Con.class);
System.out.println(conBean);
//Condate=Thu Sep 20 00:00:00 CST 2001, clientLog='QQ_Log', clientType='QQ'}
-->
<!--
当然也可以使用其他方式对Con中的date进行注入
比如内部bean等
-->
<bean id="conBean2" class="com.nfjh.spring6.factory.Con">
<property name="date">
<bean class="com.nfjh.spring6.factory.DateFactoryBeanImpl">
<constructor-arg value="2001-09-20"/>
</bean>
</property>
<property name="clientType" value="WeChat"/>
<property name="clientLog" value="WeChat_Log"/>
</bean>
<!-- Con{date=Thu Sep 20 00:00:00 CST 2001, clientLog='WeChat_Log', clientType='WeChat'} -->
bean的生命周期
Student
public class Student {
private int age;
public Student() {
System.out.println("无参构造执行了!!!");
}
public void setAge(int age) {
System.out.println("赋值执行了");
this.age = age;
}
public void init(){
System.out.println("init初始化执行了!!!");
}
public void sleep(){
System.out.println("bean对象正在使用中!!!");
}
public void destroy(){
System.out.println("destroy方法执行了!!!");
}
}
@Test
public void testLifeCycle(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("lifecycle.xml");
Student student = applicationContext.getBean("lifecycleBean", Student.class);
student.sleep();
//销毁 [ApplicationContext没有close这个方法,需要向下转型]
ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
context.close(); //如果不加上这个关闭,destroy方法不会执行
}
5步
<bean id="lifecycleBean" class="com.nfjh.spring6.life_cycle.Student" init-method="init" destroy-method="destroy">
<property name="age" value="21"/>
</bean>
<!--
无参构造执行了!!!
赋值执行了
init初始化执行了!!!
bean对象正在使用中!!!
destroy方法执行了!!!
-->
7步
添加一个类实现BeanPostProcessor接口
重写其中的两个方法
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class LifeCycleAllBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//初始化之前执行
System.out.println("初始化之前执行");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//初始化之后执行
System.out.println("初始化之后执行");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
<!--这个配置针对当前XML中所有的bean有效-->
<bean id="postProcessor" class="com.nfjh.spring6.life_cycle.LifeCycleAllBeanPost"/>
<bean id="lifecycleBean" class="com.nfjh.spring6.life_cycle.Student" init-method="init" destroy-method="destroy">
<property name="age" value="21"/>
</bean>
<!--对于相同的测试程序 7 步的结果为
无参构造执行了!!!
赋值执行了
初始化之前执行
init初始化执行了!!!
初始化之后执行
bean对象正在使用中!!!
-->
10步
5步
无参构造执行了
赋值执行了
initBean初始化执行了
bean对象正在使用中
destroyBean方法执行了
7步 在init的前后添加bean后处理器(BeanPostProcessor)
的postProcessBeforeInitialization
和postProcessAfterInitialization
10步是在
bean后处理器的Before方法前后添加
Aware的相关接口的检查,设置依赖
和InitializingBean接口的检查,调用接口方法
在使用bean之后添加
DisposableBean接口的检查,调用接口方法
Spring容器只对单例(singleton)的Bean进行完整的生命周期管理。
如果是prototype(多例)作用域的Bean,Spring容器只负责将该Bean初始化完毕。等客户端程序一旦获取到该Bean之后,Spring容器就不再管理该对象的生命周期了。
DefaultListableBeanFactory
@Test
public void registerBean(){
//让自己创建完毕的类纳入Spring容器的管理
Student student = new Student();
student.setAge(33);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerSingleton("studentBean",student);
Object studentBean = factory.getBean("studentBean");
System.out.println(student);
}
§7 IoC 注解式开发
Spring注解使用
- 第一步:加入aop的依赖
- 第二步:在配置文件中添加context命名空间
- 第三步:在配置文件中指定扫描的包
- 第四步:在Bean类上使用注解
声明Bean
@Component 声明所有类型 Bean
以下三个注解是@Component 注解的别名,可以使用@Component代替
但是代码的可读性会下降
@Controller 声明界面层的bean
@Service 声明业务逻辑层的bean
@Repository 声明数据访问层的bean
User.java
@Component(value="userBean")
如果注解的属性名是value,那么value是可以省略的@Component("userBean")
如果把value属性彻底去掉,spring会给Bean自动取名吗?会的。并且默认名字的规律是:Bean类名首字母小写即可@Component ==> getBean("user");
如果是多个包怎么办?有两种解决方案:
第一种:在配置文件中指定多个包,用逗号隔开
第二种:指定多个包的共同父包
我的疑问与测试
如果一个类中添加了注解,同时又在XML文件中进行了bean的配置
会不会报错,是两个都能用,还是两个都不能用
如果能用获得的对象是同一个吗[我只测试了单例模式下]
@Component
public class User {
}
<context:component-scan base-package="com.nfjh.bean"/>
<bean id="userBean" class="com.nfjh.bean.User"/>
@Test
@Test
public void testBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("Annotation-test.xml");
Object userBean = applicationContext.getBean("userBean");
Object userBean2 = applicationContext.getBean("user");
System.out.println(userBean);
System.out.println(userBean2);
}
/*
结果为两个都可以用,且获得的对象不是同一个
com.nfjh.bean.User@7756c3cd
com.nfjh.bean.User@2313052e
*/
指定注解类型生效[过滤]
<!--
先设置过滤所有 再确定只扫描的类型
context:component-scan[base-package & use-default-filters="false"] > context:include-filter[type expression]
先所有的都扫描,再设置不扫描的类型
context:component-scan[base-package & use-default-filters="true"] > context:exclude-filter [type expression]
base-package 需要扫描的包
use-default-filters
false表示全过滤 context:include-filter 需要添加的扫描
true表示全扫描 context:exclude-filter 需要过滤的类型 若为true use-default-filters="true" 可以不写
type annotation
expression
org.springframework.stereotype.Repository
org.springframework.stereotype.Service
org.springframework.stereotype.Controller
org.springframework.stereotype.Component
-->
注入数据
@Value
以前这样注入
<!--注入简单类型-->
<bean id="userBean" class="com.nfjh.spring6.bean.User">
<property name="username" value="zhangsan"/>
<property name="passwd" value="123"/>
<property name="age" value="20"/>
</bean>
使用注解这样注入
1、在属性上注入
@Component("userAnnotation")
public class User {
@Value("张三")
private String username;
@Value("123456")
private String passwd;
@Value("21")
private int age;
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", passwd='" + passwd + '\'' +
", age=" + age +
'}';
}
}
/*
㊟:使用@Value的注解方式,可以不用写构造方法和set以及get方法
toString方法只是用做输出看比较方便,也可以省略
User{username='张三', passwd='123456', age=21}
*/
2、在set方法上注入
@Component("userAnnotation")
public class User {
private String username;
private String passwd;
private int age;
@Value("张三")
public void setUsername(String username) {
this.username = username;
}
@Value("123456")
public void setPasswd(String passwd) {
this.passwd = passwd;
}
@Value("23")
public void setAge(int age) {
this.age = age;
}
/*
User{username='张三', passwd='123456', age=23}
*/
3、在构造方法的参数上进行注入
@Component("userAnnotation")
public class User {
private String username;
private String passwd;
private int age;
public User(@Value("李四") String username, @Value("3012") String passwd, @Value("23") int age) {
this.username = username;
this.passwd = passwd;
this.age = age;
}
/*
User{username='李四', passwd='3012', age=23}
*/
自动装配
@AutoWired 类型自动装配
AutoWired根据类型自动装配时,类型需要唯一
autowireTest
dao
UserDao
@Repository
public interface UserDao {
void insert();
}
impl
@Repository
public class UserDaoForJK implements UserDao{
@Override
public void insert() {
System.out.println("计科学生信息插入中");
}
}
service
UserService
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void generate(){
userDao.insert();
}
}
<!--添加包扫描-->
<context:component-scan base-package="com.nfjh.autowireTest"/>
@Test
public void testAnnotationAutoWiredInjection(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("Annotation-test.xml");
Object userService = applicationContext.getBean("userService");
UserService service = (UserService) userService;
service.generate();
}
//计科学生信息插入中
缺点
如何解决同一接口有多个实现类,无法通过类型注入?
如果在impl包下添加实现类
@Repository
public class UserDaoForTX implements UserDao {
@Override
public void insert() {
System.out.println("通信学生信息插入中");
}
}
如果同一个类型有多个实现类,则Spring无法通过AutoWired直接进行装配
例如下面如果UserDao 的实现类除了UserDaoForJK之外还有一个userDaoForTX的话
在运行测试程序时会报错
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'userService': Unsatisfied dependency expressed through field 'userDao'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.nfjh.autowireTest.dao.UserDao' available: expected single matching bean but found 2: userDaoForJK,userDaoForTX
注意:如果UserDao 有多页实现类,但是只有一个实现类添加了@Component以及他的别名,且
其他实现类也没有在XML文件中进行<bean>的配置
这种只算一个,也是可以正常装配的
@AutoWired + Qualifer名称自动装配
对与以上使用类型无法自动装载的问题,可以使用名称自动装载进行解决
在UserService中这样写
@AutoWired
@Qualifer("userDaoForTX")
private UserDao userDao;
注意@Qualifer括号中的内容是bean的id
即可指定UserService中装载的对象为userDaoForTX
@AutoWired出现的位置
以下举例以使用@AutoWired类型装配有多个实现类无法解决
为例,但是注意,当可以单独使用@AutoWired解决时,@AutoWired也是可以出现在这些位置上的
属性上,set方法上,构造方法上,构造方法的参数上,省略
1、属性上上面已经出现了,不再举例
2、set方法上
@Autowired
@Qualifier("userDaoForJK")
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
3、构造方法上
@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
需要注意的是,@Qualifier限定符不适用于构造函数
" @Qualifier is not applicable for constructor"
所以在测试时,我把userDaoForJK类上的@Repository 给注释了
但是虽然@Qualifier无法在构造方法上使用,但是@Autowired可以
4、构造方法的参数上
public UserService(@Autowired @Qualifier("userDaoForJK") UserDao userDao) {
this.userDao = userDao;
}
5、不使用@Autowired
当且仅当有参数的构造方法只有一个,@Autowired注解可以省略
当然如果有多个构造方法,@Autowired不能省略,即使是无参的构造和有参构造,也不能省
public UserService(UserDao userDao) {
this.userDao = userDao;
}
[OS]最好不要省略,会降低代码的可读性!!
@Resource
使用使用这个注解是需要引入jar包的
Spring6支持的javaEE9 所以需要引入的是
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
dao
public interface StudentDao {
void delete();
}
impl
@Repository
public class StudentDaoImpl implements StudentDao {
@Override
public void delete() {
System.out.println("学生信息删除....");
}
}
service
@Service
public class StudentService {
@Resource(name="studentDaoImpl")
private StudentDao studentDaoProp;
public void delStu(){
studentDaoProp.delete();
}
1、@Resource 默认使用名称进行装配
当StudentDaoImpl的注解中value是空的
和上面使用@AutoWired结合@Qualifier一样名称默认是类名的首字母小写
@Resource(name="studentDaoImpl")
当value不为空时填写的
在使用@Resource时name="名称"
2、当名称为空时,属性名做为名称进行查找
impl
@Repository("studentDaoProp")
public class StudentDaoImpl implements StudentDao
service
@Resource
private StudentDao studentDaoProp;
3、当名称为空,且根据属性名无法查找到类时,根据类型进行查找
注意此时根据类型查找时,如果类型有多个,和@Autowired一样是不行的
impl
@Repository
public class StudentDaoImpl implements StudentDao
service
@Resource
private StudentDao studentDaoProp;
如果条件符合以上可以查询出结果
总结一下
注解类型 | 属性上 | set | 构造方法 | 构造方法的参数上 | 来源 |
---|---|---|---|---|---|
@Autowired | ✓ | ✓ | ✓ | ✓ | Spring |
@Qualifier | ✓ | ✓ | × | ✓ | Spring |
@Resource | ✓ | ✓ | × | × | JDK扩展包 |
同时,如果只是用@Autowired说明使用类型注入,被注入的类型是唯一的
@Autowired结合 @Qualifer 说明使用名称注入
全注解式开发
通过使用以上的注解,目前我们的配置文件中只有一个包扫描
<context:component-scan base-package="com.nfjh.bean"/>
现在编写一个类,用于代替这个配置文件
@Configuration
@ComponentScan({"com.nfjh.Resource.dao","com.nfjh.Resource.service"})
public class Spring6Config {
}
注意这里的注解 @Configuration 以及 @ComponentScan ,
看清楚不是@ComponentScans,不是s结尾!!!
注意此时已经没有了xml文件,所以这里的测试程序需要修改
使用"AnnotationConfigApplicationContext",传入“配置类”的类名,类名随意起,在这里传入即可
AnnotationConfigApplicationContext annotationConfigApplicationContext
= new AnnotationConfigApplicationContext(Spring6Config.class);
StudentService studentService = annotationConfigApplicationContext.getBean("studentService", StudentService.class);
studentService.delStu();
§8 jdbcTemplate
这部分看老杜的笔记
比较简单
AOP 面向切面编程
AOP七大术语
关于面向切面编程我的理解:
将与核心业务逻辑无关的代码进行抽离,形成以横向交叉应用于多个项目的"万金油"代码
例如事务管理,日志,安全等,提高代码的复用率
AOP面向切面编程是一种编程思想,而JDK动态代理和GBLIB动态代理就是AOP的实现
连接点[Joinpoint]
可以织入切面的位置
切点[Pointcut]
核心业务逻辑方法,真正织入切面的的方法
一个切点对应多个连接点
通知[Advice]
我们将要织入的增强代码
[前置通知、后置通知、环绕通知、异常通知、最终通知]
切面[Aspect]
切点 + 通知(在什么位置放什么代码)
织入 [Weaving]
把通知应用到目标对象上的过程。
代理对象[Proxy]
一个目标对象被织入通知后产生的新对象。
目标对象[Target]
被织入通知的对象。
切点表达式
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
AspectJ
1、使用Spring+AspectJ的AOP需要引入的依赖如下:
<!--spring context依赖-->
<!--spring aop依赖-->
<!--spring aspects依赖-->
2、Spring配置文件中添加context和aop的命名空间和约束文件
AspectJ框架__注解
5个通知类型
<!--组件扫描-->
<context:component-scan base-package="com.ndjh.spring6.service"/>
<!--交给spring管理,可用注解代替-->
<bean id="logAspect" class="com.ndjh.spring6.service.LogAspect"/>
<bean id="userService" class="com.ndjh.spring6.service.UserService"/>
<!-- 开启自动代理-->
<!-- proxy-target-class="true" 强制使用CGLIB动态代理
proxy-target-class="false" 默认值 ,依情况而定使用哪种代理
有接口使用JDK代理,其他使用CGLIB代理
-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
service
UserService
//Target目标对象,待增强
public class UserService {
public void login(){
System.out.println("系统正在进行身份验证");
}
}
LogAspect
//@Aspect注解给spring检查
@Aspect
public class LogAspect {
//通知+切点
/*
通知就是增强代码
*/
//括号里是切点表达式
//execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
//注意在全限定类名与方法名之间没有空格
//Before注解标注的是前置通知
@Before("execution(* com.ndjh.spring6.service..*(..))")
public void testBefore(){
System.out.println("前置通知");
}
@AfterReturning("execution(* com.ndjh.spring6.service..*(..))")
public void testAfterReturning(){
System.out.println("后置通知");
}
//注意环绕通知方法有参数,且有调用方法
@Around("execution(* com.ndjh.spring6.service..*(..))")
public void testAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("前环绕通知");
joinPoint.proceed();
System.out.println("后环绕通知");
}
//若异常环绕通知执行,则后环绕通知不会执行,而最终环绕通知执行
@AfterThrowing("execution(* com.ndjh.spring6.service..*(..))")
public void testAfterThrowing(){
System.out.println("异常通知");
}
@After("execution(* com.ndjh.spring6.service..*(..))")
public void testAfter(){
System.out.println("最终通知");
}
}
/*----在IDEDA2021.3中运行结果------
前环绕通知
前置通知
系统正在进行身份验证
后置通知
最终通知
后环绕通知
-------------------------------*/
现在修改代码UserService
增加异常
//Target目标对象,待增强
public class UserService {
public void login(){
System.out.println("系统正在进行身份验证");
throw new RuntimeException();
}
}
/*----在IDEDA2021.3中运行结果------
前环绕通知
前置通知
系统正在进行身份验证
异常通知
最终通知
java.lang.RuntimeException
at com.ndjh.spring6.service.UserService.login(UserService.java:8)
at com.ndjh.spring6.service.UserService$$FastClassBySpringCGLIB$$93098a3f.invoke(<generated>)
.........异常信息.............
观察可知,最终通知会执行,但是后环绕通知不在执行
-------------------------------*/
切面的顺序
对于“切面类”可以使用注解@Order进行排序
@Order(整型数字)
数字越小越先执行
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {
int value() default 2147483647;
}
通用切点表达式
@Pointcut("execution(* com.ndjh.spring6.service..*(..))")
public void generalExpression(){}
使用@Pointcut进行标注
当前类进行标注
@Before("generalExpression()")
public void testBefore(){
System.out.println("前置通知");
}
//跨类使用要写全限定类名+方法名()
@Before("com.ndjh.spring6.service.LogAspect.generalExpression()")
public void safetyBefore(){
System.out.println("安全前置通知");
}
AspectJ框架__XML
Spring事务
spring事务失效的12种场景:
访问权限问题
方法用final修饰
方法内部调用
末被spring管理
多线程调用
表不支持事务
未开启事务
错误的传播特性
自己吞了异常
手动抛了别的异常
自定义了回滚异常
嵌套事务回滚多了
spring集成Mybatis
pom文件
- 仓库地址
- spring-context
- spring-jdbc
- mybatis
- mybatis-spring
- jdbc
- druid
- junit
mapper : 接口
pojo : 普通类
service
接口
接口的实现类
归入Spring管理加上注解 @Service @Transactional
添加Mapper中的属性,加上注解 @Autowired,需要时再加上 @Qualifer
resources:
mapper
Mapper.xml 编写SQL语句
jdbc.properties
mybatis-config.xml
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
spring-config.xml
1、组件扫描
2、引入JDBC配置文件
3、数据源
4、sqlSessionFactoryBean配置
mybatis核心配置文件路径
数据源
指定别名 [sql中的resultType]
5、Mapper扫描mapper包
6、事务管理器
7、开启事务
<?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:tx="http://www.springframework.org/schema/tx"
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--1、组件扫描-->
<context:component-scan base-package="com.nfjh.bank"/>
<!--2、引入JDBC属性文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--3、数据源-->
<!--此处对应的Bean不用我们写,阿里巴巴写好了,我们直接用就行-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
</bean>
<!--4、SqlSessionFactoryBean配置-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<!--注入mybatis核心配置文件路径-->
<property name="configLocation" value="mybatis-config.xml"/>
<!--数据源-->
<property name="dataSource" ref="dataSource"/>
<!--指定别名-->
<property name="typeAliasesPackage" value="com.nfjh.bank"/>
</bean>
<!--5、Mapper扫描mapper包,生成代理类-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.nfjh.bank.mapper"/>
</bean>
<!--6、事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务-->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
spring-nfjh的更多相关文章
- 基于spring注解AOP的异常处理
一.前言 项目刚刚开发的时候,并没有做好充足的准备.开发到一定程度的时候才会想到还有一些问题没有解决.就比如今天我要说的一个问题:异常的处理.写程序的时候一般都会通过try...catch...fin ...
- 玩转spring boot——快速开始
开发环境: IED环境:Eclipse JDK版本:1.8 maven版本:3.3.9 一.创建一个spring boot的mcv web应用程序 打开Eclipse,新建Maven项目 选择quic ...
- Spring基于AOP的事务管理
Spring基于AOP的事务管理 事务 事务是一系列动作,这一系列动作综合在一起组成一个完整的工作单元,如果有任何一个动作执行失败,那么事务 ...
- [Spring]IoC容器之进击的注解
先啰嗦两句: 第一次在博客园使用markdown编辑,感觉渲染样式差强人意,还是github的样式比较顺眼. 概述 Spring2.5 引入了注解. 于是,一个问题产生了:使用注解方式注入 JavaB ...
- 学习AOP之透过Spring的Ioc理解Advisor
花了几天时间来学习Spring,突然明白一个问题,就是看书不能让人理解Spring,一方面要结合使用场景,另一方面要阅读源代码,这种方式理解起来事半功倍.那看书有什么用呢?主要还是扩展视野,毕竟书是别 ...
- 学习AOP之深入一点Spring Aop
上一篇<学习AOP之认识一下SpringAOP>中大体的了解了代理.动态代理及SpringAop的知识.因为写的篇幅长了点所以还是再写一篇吧.接下来开始深入一点Spring aop的一些实 ...
- 学习AOP之认识一下Spring AOP
心碎之事 要说知道AOP这个词倒是很久很久以前了,但是直到今天我也不敢说非常的理解它,其中的各种概念即抽象又太拗口. 在几次面试中都被问及AOP,但是真的没有答上来,或者都在面上,这给面试官的感觉就是 ...
- 为什么做java的web开发我们会使用struts2,springMVC和spring这样的框架?
今年我一直在思考web开发里的前后端分离的问题,到了现在也颇有点心得了,随着这个问题的深入,再加以现在公司很多web项目的控制层的技术框架由struts2迁移到springMVC,我突然有了一个新的疑 ...
- Spring之旅(2)
Spring简化Java的下一个理念:基于切面的声明式编程 3.应用切面 依赖注入的目的是让相互协作的组件保持松散耦合:而AOP编程允许你把遍布应用各处的功能分离出来形成可重用的组件. AOP面向切面 ...
- Spring之旅
Java使得以模块化构建复杂应用系统成为可能,它为Applet而来,但为组件化而留. Spring是一个开源的框架,最早由Rod Johnson创建.Spring是为了解决企业级应用开发的复杂性而创建 ...
随机推荐
- 自己写的java教程,免费分享
自己写的一套java教程,主要用于内部培训使用,有需要的可以直接免费下载: http://it.zhenzikj.com/download/java.zip 一共写了3套: java语言基础.网络开发 ...
- 剑指 Offer II 树
我为什么要把代码粘在这里 断更很久了,基于一个错误的观念:我想看题,我到leetcode官网看不就行了吗? 但是若干年后我可能还会到我的博客园看看呀,我有可能上刷题网站吗?而且心得不好写到注释上. 记 ...
- es的核心原理 (一)
搜索引擎 搜索引擎的分类 全文搜索:谷歌.百度.必应:这种搜索过程到底搜索啥,不知道,有可能是个帖子,也有可能是个门户网站 垂直搜索:视频网站.各大电商:这种搜索内容是固定的,比如在懂车帝,你搜索的一 ...
- 提交docker镜像到远程仓库
生成镜像 Docker build 镜像 编辑Dockerfile文件 新建Dockerfile文件,将如下构建脚本复制进去 # Build for ansible envirament FROM c ...
- centos 等保漏洞修复
一.设置密码复杂度 [root@localhost ~]# vi /etc/pam.d/system-auth password requisite pam_pwquality.so try_firs ...
- 【git】3.4 git分支-分支开发工作流
资料来源 (1) https://git-scm.com/book/zh/v2/Git-%E5%88%86%E6%94%AF-%E5%88%86%E6%94%AF%E5%BC%80%E5%8F%91% ...
- COOP/COHP(上)-PROOUT
晶体轨道重叠布居 COOP(crystal orbital overlap population)的一个更为直观的名称是 重叠布居权重的态密度 (overlap population-weighted ...
- 百题计划-4 codeforces 652 div2 D. TediousLee 找规律
https://codeforces.com/contest/1369/problem/D n<=2e6,所以只要找递推式就可以了,不需要找快速幂 /** */ #include<bits ...
- Windows Defender锁定UI
计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows Defender Security Center 看下这个项下的子项有没有被添加值 ...
- 微信小程序笔记_02
在微信小程序中使用Echarts组件 github源码地址:https://github.com/ecomfe/echarts-for-weixin gitcode源码地址:https://gitco ...