# Spring概述
1、Spring是轻量级开源JavaEE框架
2、Spring可以解决企业应用开发的复杂性
3、组成核心IOC、Aop
-
IOC:控制反转,把创建对象过程交给Spring进行管理
-
Aop:面向切面,不修改源代码进行功能增强
4、Spring特点
- 方便解耦,简化开发
- Aop编程支持
- 方便程序测试
- 方便集成各种优秀框架
- 方便进行事务管理
- 降低java api的使用难度
*此文档版本为Spring5
IOC
什么是IOC
(1)控制反转,把对象的创建和对象之间调用的过程,交给Spring进行管理
(2) 使用IOC的目的:为了降低耦合
(3) 做入门案例就是IOC的实现
IOC底层原理
(1) xml解析、工厂模式、反射
IOC过程 (进一步降低耦合度)
第一步 xml配置文件,配置创建的对象
<bean id=\"dao\" class=\"com.atguigu.UserDao\"></bean>
第二步 有service类和dao类 创建工厂类
class UserFactory{
public static UserDao getDao(){
String classValue = class属性值;//1 xml解析
Class class = Class.forName(classValue);//2 通过反射创建对象
return (UserDao)class.newInstance();//返回对象
}
}
IOC接口
-
IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
-
Spring提供IOC容器实现两种方式:(两个接口)
- BeanFactory :IOC容器基本实现,是Spring内部的使用接口,不提供给开发人员使用
- 加载配置文件时不会创建对象,获取\\使用对象时才会创建对象
- ApplicationContext :BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员使用
- 加载配置文件时就会把配置文件中对象创建(服务器启动时创建)
ApplicationContext接口有实现类
IOC操作 Bean管理
什么是Bean管理
包含以下两个操作
- Spring创建对象
- Spring注入属性
Bean管理操作
1 bean创建对象
1 基于xml配置文件方式
<!--配置User对象创建-->
<bean id=\"user\" class=\"com.atguigu.spring5.User\"></bean>
(1)在Spring配置文件中,使用bean标签,标签里添加对应属性,就可以实现对象创建
(2)在bean标签中有很多属性:
-
id属性:唯一标识(不能加特殊符号)
-
class属性:类全路径(包类路径)
-
name:类似id(可添加特殊符号)
(3)创建对象的时候,默认也是执行无参构造方法
2 基于注解方式
2 基于xml注入属性
(1) DI:依赖注入,就是注入属性(DI是IOC的一种具体实现,在创建对象的基础之上进行)
第一种注入方式:使用set方法进行注入
第一步:创建类、创建属性、创建对应的set方法
public class Book {
//创建属性
private String bname;
private String bauthor;
//创建属性对应的set方法
public void setBname(String bname) {
this.bname = bname;
}
public void getBauthor(String bauthor) {
this.bauthor = bauthor;
}
}
第二步:在Spring配置文件配置对象创建,配置属性注入
<!--2 set方法注入属性-->
<bean id=\"book\" class=\"com.atguigu.spring5.Book\">
<!--使用property完成属性注入
name:类里面属性名称
value:向属性注入的值
-->
<property name=\"bname\" value=\"张三日记\"></property>
<property name=\"bauthor\" value=\"法外狂徒张三\"></property>
</bean>
public class Book {
//创建属性
private String bname;
private String bauthor;
//创建属性对应的set方法
public void setBname(String bname) {
this.bname = bname;
}
public void setBauthor(String bauthor) {
this.bauthor = bauthor;
}
}
第二种注入方法:使用有参构造进行注入
第一步:创建类 ,定义属性,创建属性对应有参构造方法
/**
* 使用有参构造注入
* */
public class Orders {
private String oname;
private String address;
public Orders(String oname,String address){
this.oname = oname;
this.address = address;
}
}
第二步:在Spring的配置文件中进行配置
<!--3 使用有参构造注入属性-->
<bean id=\"orders\" class=\"com.atguigu.spring5.Orders\">
<!-- constructor-arg标签用于有参构造注入属性-->
<constructor-arg name=\"oname\" value=\"电脑\"></constructor-arg>
<constructor-arg name=\"address\" value=\"China\"></constructor-arg>
</bean>
第三种注入方式:P名称空间注入(底层使用的还是set方法注入)
使用p名称空间注入,可以简化基于xml配置方式
- 添加p名称空间在配置文件中
-
进行属性注入,在bean标签里面进行操作
<!--4 使用p名称空间注入--> <bean id=\"book\" class=\"com.atguigu.spring5.Book\" p:bname=\"张三的一生\" p:bauthor=\"罗翔\"> </bean>
xml注入其他类型属性
字面量:固定值
-
null值
<!--null值--> <property name=\"address\"> <null/> </property> </bean>
-
属性包含特殊符号
<!--注入特殊符号 1 拔尖括号进行转义 <> 2 把特殊符号内容写到CDATA --> <property name=\"address\"> <value><![CDATA[<<南京>>]]></value> </property>
注入属性-外部bean
-
创建两个类service类和dao类
-
在service调用dao里面的方法
-
在Spring配置文件中进行文件配置
<!--1 service和Dao对象创建--> <bean id=\"userService\" class=\"com.atguigu.spring5.service.UserService\"> <!--注入userDao对象 name属性值:类里面的属性名称 ref属性:创建userDao对象bean标签id值 --> <property name=\"userDao\" ref=\"userDaoImpl\"></property> </bean> <bean id=\"userDaoImpl\" class=\"com.atguigu.spring5.dao.UserDaoImpl\"></bean>
public class UserService { //创建UserDao类型属性,生成set方法 private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void add(){ System.out.println(\"service add............\"); userDao.update(); //原始方式:创建UserDao对象 // UserDao userDao = new UserDaoImpl(); // userDao.update(); } }
注入属性-内部bean
-
一对多关系,部门和员工
一个部门有多个员工,一个员工属于一个部门。 部门是一 员工是多
-
在实体类之间表示一对多关系
员工表示所属部门,使用对象类型进行表示
//部门类 public class Dept { private String dname; public void setDname(String dname) { this.dname = dname; } }
//员工类 public class Emp { private String ename; private String gender; //员工属于某一个部门,使用对象形式表示 private Dept dept; public void setDept(Dept dept) { this.dept = dept; } public void setEname(String ename) { this.ename = ename; } public void setGender(String gender) { this.gender = gender; } }
-
在Spring配置文件中进行配置
<!--内部bean--> <bean id=\"emp\" class=\"com.atguigu.spring5.bean.Emp\"> <!--设置两个普通属性--> <property name=\"ename\" value=\"lucy\"></property> <property name=\"gender\" value=\"女\"></property> <!--设置对象类型属性--> <property name=\"dept\"> <bean id=\"dept\" class=\"com.atguigu.spring5.bean.Dept\"> <property name=\"dname\" value=\"安保部门\"></property> </bean> </property> </bean>
注入属性-级联赋值
第一种写法
<!--级联赋值-->
<bean id=\"emp\" class=\"com.atguigu.spring5.bean.Emp\">
<!--设置两个普通属性-->
<property name=\"ename\" value=\"lucy\"></property>
<property name=\"gender\" value=\"女\"></property>
<!--级联赋值-->
<property name=\"dept\" ref=\"dept\"></property>
</bean>
<bean id=\"dept\" class=\"com.atguigu.spring5.bean.Dept\">
<property name=\"dname\" value=\"财务部\"></property>
</bean>
第二种写法 类中需要写get方法
//员工类
public class Emp {
private String ename;
private String gender;
//员工属于某一个部门,使用对象形式表示
private Dept dept;
//生成dept的get方法
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
public void setEname(String ename) {
this.ename = ename;
}
public void setGender(String gender) {
this.gender = gender;
}
public void add(){
System.out.println(ename+\"::\"+gender+\"::\"+dept);
}
}
<!--级联赋值-->
<bean id=\"emp\" class=\"com.atguigu.spring5.bean.Emp\">
<!--设置两个普通属性-->
<property name=\"ename\" value=\"lucy\"></property>
<property name=\"gender\" value=\"女\"></property>
<!--级联赋值-->
<property name=\"dept\" ref=\"dept\"></property>
<property name=\"dept.dname\" value=\"技术部\" ></property>
</bean>
<bean id=\"dept\" class=\"com.atguigu.spring5.bean.Dept\">
<property name=\"dname\" value=\"财务部\"></property>
</bean>
xml注入集合属性
-
注入数组类型属性
-
注入List集合属性
-
注入Map集合类型属性
-
注入set集合类型属性
创建类,定义数组、list、map、set类型属性,生成对应set方法
public class Stu { //1 数组类型属性 private String[] courses; //2 list集合类型属性 private List<String> list; //3 map集合类型属性 private Map<String,String> maps; //4 set集合类型属性 private Set<String> set; public void setSet(Set<String> set) { this.set = set; } public void setCourses(String[] courses) { this.courses = courses; } public void setList(List<String> list) { this.list = list; } public void setMaps(Map<String, String> maps) { this.maps = maps; } }
在Spring配置文件进行配置
<!--1 集合类型属性注入--> <bean id=\"stu\" class=\"com.atguigu.spring5.collectiontype.Stu\"> <!--数组类型属性注入--> <property name=\"courses\" > <array> <value>java课程</value> <value>sql课程</value> </array> </property> <!--list集合属性注入--> <property name=\"list\"> <list> <value>张三</value> <value>小三</value> </list> </property> <!--map类型属性注入--> <property name=\"maps\"> <map> <entry key=\"JAVA\" value=\"java\"></entry> <entry key=\"PHP\" value=\"php\"></entry> </map> </property> <!--set类型属性注入--> <property name=\"set\"> <set> <value>MySQL</value> <value>Redis</value> </set> </property> </bean>
-
在集合里设置对象类型值
<!--注入list集合类型,值是对象--> <property name=\"courseList\"> <list> <ref bean=\"course1\"></ref> <ref bean=\"course2\"></ref> </list> </property>
<!--创建多个course对象--> <bean id=\"course1\" class=\"com.atguigu.spring5.collectiontype.Course\"> <property name=\"cname\" value=\"Spring5框架\"></property> </bean> <bean id=\"course2\" class=\"com.atguigu.spring5.collectiontype.Course\"> <property name=\"cname\" value=\"Mybatis框架\"></property>
-
把集合注入部分提取出来
- 在Spring配置文件中引入空间名称util
<beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:util=\"http://www.springframework.org/schema/util\" xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd\"> </beans>
-
使用util标签完成list集合注入提取
<!--1 提取list集合类型属性注入--> <util:list id=\"bookList\" > <!--若引入对象使用ref标签--> <value>张三日记</value> <value>张三的悔改</value> <value>法外狂徒</value> </util:list> <!--2 提取list集合类型属性注入使用--> <bean id=\"book\" class=\"com.atguigu.spring5.collectiontype.Book\"> <property name=\"list\" ref=\"bookList\"></property> </bean>
FactorBean
Spring里有两种bean,一种普通的bean。另外一种是FactoryBean(Spring内置的)
普通bean
Spring配置文件中定义bean类型即为返回类型
FactoryBean
Spring配置文件中定义bean类型可与返回类型不同
-
创建类,让这个类作为工厂bean,实现接口FactoryBean
-
实现接口里面的方法,在实现的方法中定义返回的bean类型
public class MyBean implements FactoryBean <Course>{ //定义返回bean @Override public Course getObject() throws Exception { Course course = new Course(); course.setCname(\"abc\"); return course; } @Override public Class<?> getObjectType() { return null; } @Override public boolean isSingleton() { return false; } } //测试方法 @Test public void testCollection3(){ ApplicationContext context=new ClassPathXmlApplicationContext(\"bean3.xml\"); Course course=context.getBean(\"myBean\",Course.class); System.out.println(course); }
配置文件
<bean id=\"myBean\" class=\"com.atguigu.spring5.factorybean.MyBean\"> </bean>
bean的作用域
-
在Spring里,默认设置下,bean是单实例对象
@Test public void testCollection2(){ ApplicationContext context=new ClassPathXmlApplicationContext(\"bean2.xml\"); Book book1=context.getBean(\"book\", Book.class); Book book2=context.getBean(\"book\", Book.class); //book.test(); System.out.println(book1); System.out.println(book2); }
输出显示地址相同为单实例对象:
-
如何设置单实例或多实例
(1)在Spring配置文件bean标签里面有用于设置的属性(scope)
(2)scope属性值
- 默认值:singleton,表示单实例对象
- prototype,表示多实例对象
<!--2 提取list集合类型属性注入使用--> <bean id=\"book\" class=\"com.atguigu.spring5.collectiontype.Book\" scope=\"prototype\"> <property name=\"list\" ref=\"bookList\"></property> </bean>
两对象地址不同:
-
singleton和prototype区别
- singleton表示单实例,prototype多实例
- 设置scope值是singleton时,加载Spring配置文件时就会创建单实例对象
- 设置scope值是prototype时,不是在加载Spring配置文件时创建对象,在调用getBean方法时创建多例对象
Bean生命周期
从对象的创建到生命的销毁的过程
bean生命周期:
-
通过构造器创建bean实例(无参数构造)
-
为bean的属性设置值和对其他bean的引用(调用set方法)
把bean实例传给bean后置处理器的方法postProcessBeforeInitialization(bean的后置处理器BeanPostProcessor,bean共有7步)
-
调用bean初始化的方法(需要进行配置)
把bean实例传给bean后置处理器的另外一个方法postProcessAfterInitialization(bean的后置处理器BeanPostProcessor,bean共有7步)
-
bean可以使用了(对象获取到了)
-
当容器关闭时,调用bean的销毁的方法(需要进行配置销毁的方法)
<bean id=\"orders\" class=\"com.atguigu.spring5.bean.Orders\" init-method=\"initMethod\" destroy-method=\"destoryMethod\"> <property name=\"oname\" value=\"手机\"> </property> </bean>
public class Orders { //1 无参构造 public Orders(){ System.out.println(\"第一步 执行无参构造方法创建bean实例\"); } private String oname; public void setOname(String oname) { this.oname = oname; System.out.println(\"第二步 调用set方法设置属性值\"); } //3 创建执行的初始化方法 public void initMethod(){ System.out.println(\"第三步 执行初始化方法\"); } //5 创建执行的销毁方法 public void destoryMethod(){ System.out.println(\"第五步 执行销毁方法\"); } } //测试类 @Test public void testBean3(){ // ApplicationContext context=new ClassPathXmlApplicationContext(\"bean4.xml\"); ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext(\"bean4.xml\"); Orders orders=context.getBean(\"orders\",Orders.class); System.out.println(\"第四步 获取创建bean实例对象\"); System.out.println(orders); //手动让bean实例销毁 context.close(); }
添加后置处理器之后:
-
创建类,实现接口BeanPostProcessor,创建后置处理器
public class MyBeanPost implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println(\"在初始化之前执行的方法\"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println(\"在初始化之后执行的方法\"); return bean; } }
<!--配置后置处理器--> <bean id=\"myBeanPost\" class=\"com.atguigu.spring5.bean.MyBeanPost\"></bean>
IOC操作Bean管理(xml自动装配)
什么是自动装配?
根据指定装配规则(属性名称或属性类型),Spring自动将匹配的属性值进行注入
演示自动装配过程:
根据属性名称自动注入
<!--实现自动装配
bean标签属性autowire,配置自动装配
autowire属性常用的两个值:
byName根据属性名称,注入值bean的id和类属性名称一样
byType根据属性类型注入
-->
<bean id=\"emp\" class=\"com.atguigu.spring5.autowire.Emp\" autowire=\"byName\">
<!-- <property name=\"dept\" ref=\"dept\"></property>-->
</bean>
<bean id=\"dept\" class=\"com.atguigu.spring5.autowire.Dept\"></bean>
</beans>
根据属性类型自动注入
<!--实现自动装配
bean标签属性autowire,配置自动装配
autowire属性常用的两个值:
byName根据属性名称,注入值bean的id和类属性名称一样
byType根据属性类型注入
-->
<bean id=\"emp\" class=\"com.atguigu.spring5.autowire.Emp\" autowire=\"byType\">
<!-- <property name=\"dept\" ref=\"dept\"></property>-->
</bean>
<bean id=\"dept\" class=\"com.atguigu.spring5.autowire.Dept\"></bean>
</beans>
IOC操作Bean管理(外部属性文件)
-
直接配置数据库信息
1.配置德鲁伊连接池(Druid连接池,阿里巴巴)
2.引入德鲁伊连接池依赖jar包
<!--直接配置连接池--> <bean id=\"database\" class=\"com.alibaba.druid.pool.DruidDataSource\"> <property name=\"DriverClassName\" value=\"com.mysql.jdbc.Driver\"></property> <property name=\"url\" value=\"jdbc:mysql://locahost:3306/userDb\"></property> <property name=\"username\" value=\"root\"></property> <property name=\"password\" value=\"root\"></property> </bean>
-
引入外部属性文件配置数据库连接池
(1)创建外部属性文件,properties格式文件,写数据库信息
properties文件存储key:value格式配置
prop.driverClass=com.mysql.jdbc.Driver prop.url=jdbc:mysql://localhost:3306/userDb prop.userName=root prop.password=root
(2)把外部properties属性文件引入到Spring配置文件中
-
引入context名称空间
<beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:context=\"http://www.springframework.org/schema/context\" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd\"> </beans>
-
在Spring配置文件标签引入外部属性文件
<!--引入外部属性文件--> <context:property-placeholder location=\"classpath:jdbc.properties\"/> <!--配置连接池--> <bean id=\"database\" class=\"com.alibaba.druid.pool.DruidDataSource\"> <property name=\"DriverClassName\" value=\"${prop.driverClass}\"></property> <property name=\"url\" value=\"${prop.url}\"></property> <property name=\"username\" value=\"${prop.userName}\"></property> <property name=\"password\" value=\"${prop.password}\"></property> </bean>
-
IOC操作Bean管理(基于注解方式)
什么是注解?
-
注解是代码特殊标记,格式:@注解名称(属性名称=属性值,属性名称=属性值。。。)
-
使用注解,注解作用在类、方法、属性上面
-
使用注解的目的:简化xml配置
Spring针对Bean管理中创建对象提供注解:
-
@Component
-
@Service
-
@Controller
-
@Repository
*四个注解功能相同,都可以用来创建bean对象
基于注解方式实现对象的创建:
-
引入依赖
-
开启组件扫描
<!--1 开启组件扫描 1 扫描多个包时,使用逗号隔开 2 扫描包上层目录可扫描包下多个包 --> <context:component-scan base-package=\"com.atguigu\"></context:component-scan>
-
创建类,在类上面添加创建对象注解
//在注解里面value属性值可以不写 //默认值是类名称,首字母小写 //UserService.class---userService @Service(value = \"userService\")//<bean id=\"userService\" class=\"..\">相同 public class UserService { public void add(){ System.out.println(\"service add....\"); } }
-
开启组件扫描的细节配置
<!--示例1 use-default-filters=\"false\"表示不使用默认filter(扫描所有子目录),自己配置filter context:include-filter,设置扫描哪些内容 --> <context:component-scan base-package=\"com.atguigu\" use-default-filters=\"false\"> <context:include-filter type=\"annotation\" expression=\"org.springframework.stereotype.Service\"/> </context:component-scan> <!--示例2 下面示例扫描包下所有内容 context:exclude-filter ,设置哪些不扫描 --> <context:component-scan base-package=\"com.atguigu\"> <context:exclude-filter type=\"annotation\" expression=\"org.springframework.stereotype.Service\"/> </context:component-scan>
-
基于注解方式实现属性注入
-
@AutoWired:根据属性类型进行自动装配
①把service和dao对象创建,在service和dao类添加创建对象注解
②在service注入dao对象,在service类添加dao类型属性,在属性前使用注解
@Service(value = \"userService\")//<bean id=\"userService\" class=\"..\">相同 public class UserService { //定义dao类型属性 //不需要添加set方法(spring已经封装了这一步) //添加注入属性的注解 @Autowired private UserDao userDao; public void add(){ System.out.println(\"service add....\"); userDao.add(); } }
-
@Qualifier:根据属性名称进行注入
此注解的使用需要与@Autowired一起使用
@Repository(value = \"userDaoImpl1\") public class UserDaoImpl implements UserDao{ @Override public void add() { System.out.println(\"dao add...\"); } }
@Service(value = \"userService\")//<bean id=\"userService\" class=\"..\">相同 public class UserService { //定义dao类型属性 //不需要添加set方法(spring已经封装了这一步) //添加注入属性的注解 @Autowired //根据类型进行注入 @Qualifier(value = \"userDaoImpl1\") private UserDao userDao; public void add(){ System.out.println(\"service add....\"); userDao.add(); } }
-
@Resource:(javax中的注解,jdk11之后移除)可以根据类型注入,可以根据名称注入
// @Resource//默认根据类型注入 @Resource(name = \"userDaoImpl1\") private UserDao userDao;
-
@Value:注入普通类型属性
@Value(value = \"abc\") private String name;
-
-
完全注解开发
(1)创建配置类,替代xml配置文件
@Configuration//作为配置类,替代xml配置文件 @ComponentScan(basePackages = {\"com.atguigu\"}) public class SpringConfig { }
(2)编写测试类
@Test public void testService2(){ ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); UserService userService = context.getBean(\"userService\",UserService.class); System.out.println(userService); userService.add(); }
AOP
什么是AOP?
面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,同时提高开发效率。
通俗描述:可不修改源代码,在主干功能里添加新的功能
例子:在原登录基础上添加权限判断
AOP底层原理
AOP底层使用动态代理
有两种情况的动态代理
-
有接口的情况 ,使用JDK动态代理
创建接口实现类代理对象,增强类的方法
-
没有接口的情况 ,使用CGLIB动态代理
创建子类的代理对象,增强类的方法
AOP底层原理(JDK动态代理)
使用JDK动态代理,使用Proxy类里面的方法创建代理对象
调用newProxyInstans方法
方法有三个参数:
- 类加载器
- 增强方法所在的类,这个类实现的接口,支持多个接口
- 实现这个接口InvocationHandler,创建代理对象,写增强方法
编写JDK动态代理代码:
-
创建接口,定义方法
public interface UserDao { public int add(int a,int b); public String update(String id); }
-
创建接口实现类,实现方法(原功能)
public class UserDaoImpl implements UserDao{ @Override public int add(int a, int b) { return a+b; } @Override public String update(String id) { return id; } }
-
使用Proxy类创建接口代理对象(加新功能)
public class JDKProxy { public static void main(String[] args){ // //创建接口实现类代理对象 Class[] interfaces = {UserDao.class}; // Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() { // @Override // public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // return null; // } // }); UserDaoImpl userDao = new UserDaoImpl(); UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao)); int result = dao.add(1,2); System.out.println(\"result:\"+result); } } //创建代理对象代码 class UserDaoProxy implements InvocationHandler{ //1 把创建的是谁的代理对象,把谁传递进来 //有参数的构造 private Object obj; public UserDaoProxy(Object obj){ this.obj = obj; } //增强的逻辑 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //方法之前 System.out.println(\"方法之前执行。。。。\"+method.getName()+\":传递的参数。。。\"+ Arrays.toString(args)); //被增强的方法执行 Object res = method.invoke(obj,args); //方法之后 System.out.println(\"方法之后执行。。。。\"+obj); return res; } }
AOP(术语)
-
连接点:类里面可以被增强的方法
-
切入点:类里面实际被增强的方法
-
通知(增强):实际增强的逻辑部分(新加的部分)
*假如add()方法被增强
- 前置通知:add()之前执行的增强
- 后置通知:add()之后执行的增强
- 环绕通知:add()之前之后都执行的增强
- 异常通知:add()异常时执行的增强
- 最终通知:return之后执行的增强,后置通知之后,有异常时不执行
-
切面(动作)
把通知应用到切入点的过程
AOP操作(准备)
1、Spring框架中一般基于AspectJ实现AOP操作
AspectJ:AspectJ不是Spring组成部分,独立AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作
2、基于AspectJ实现AOP操作有两种方式:
- 基于xml配置文件使用
- 基于注解方式实现(使用)
3、在项目工程里引入AOP相关依赖
4、切入点表达式
-
切入点表达式的作用:知道对哪个类里面哪个方法进行增强
-
语法结构:
execution([权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]))
例1:对com.atguigu.dao.BookDao类里面的add方法进行增强
execution(public void com.atguigu.dao.BookDao.add(int a,int b))
权限修饰符可省略,默认public
返回类型可以用*号表示全类型
execution(* com.atguigu.dao.BookDao.add(int a,int b))
例2:对com.atguigu.dao.BookDao类里面的所有方法进行增强
execution(* com.atguigu.dao.BookDao.*(..))
例3:对com.atguigu.dao包里的所有类,类里的所有方法进行增强
execution(* com.atguigu.dao..(..))
AOP(AspectJ注解)
演示:
-
创建类,在类里定义方法
public class User { public void add(){ System.out.println(\"add...\"); } }
-
创建增强类
-
在增强类里面,创建方法,让不同方法代表不同通知类型
//增强的类 public class UserProxy { //前置通知 public void before(){ System.out.println(\"before...\"); } }
-
-
进行通知的配置
-
在Spring配置文件中,开启注解的扫描
<beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:context=\"http://www.springframework.org/schema/context\" xmlns:aop=\"http://www.springframework.org/schema/aop\" xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd\"> <!--开启注解扫描--> <context:component-scan base-package=\"com.atguigu.spring5.aopanno\"></context:component-scan> </beans>
-
使用注解创建User和UserProxy对象
-
在增强类上面添加注解@Aspect
//增强的类 @Component @Aspect //生产代理对象 public class UserProxy { //前置通知 public void before(){ System.out.println(\"before...\"); } }
-
在Spring配置文件中开启生成代理对象
<!--开启Aspect生成代理对象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
配置不同类型通知
-
在增强类的里面,在通知方法上面添加通知类型的注解,使用切入点表达式配置
-
@Before
-
@After
-
@AfterReturning
-
@AfterThrowing
-
@Around
//增强的类 @Component @Aspect //生产代理对象 public class UserProxy { //前置通知 //@Before注解表示前置通知 @Before(value = \"execution(* com.atguigu.spring5.aopanno.User.add(..))\") public void before(){ System.out.println(\"before...\"); } @After(value = \"execution(* com.atguigu.spring5.aopanno.User.add(..))\") public void after(){ System.out.println(\"after...\"); } @AfterReturning(value = \"execution(* com.atguigu.spring5.aopanno.User.add(..))\") public void afterReturning(){ System.out.println(\"afterReturning...\"); } @AfterThrowing(value = \"execution(* com.atguigu.spring5.aopanno.User.add(..))\") public void afterThrowing(){ System.out.println(\"afterThrowing...异常\"); } @Around(value = \"execution(* com.atguigu.spring5.aopanno.User.add(..))\") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ System.out.println(\"around...之前\"); //被增强方法 proceedingJoinPoint.proceed(); System.out.println(\"around...之前后\"); } }
-
-
公共切入点
//相同切入点抽取 @Pointcut(value = \"execution(* com.atguigu.spring5.aopanno.User.add(..))\") //切入点注解 public void pointdemo(){ } //前置通知 //@Before注解表示前置通知 @Before(value = \"pointdemo()\") public void before(){ System.out.println(\"before...\"); }
-
多个增强类对同一个切入点增强时,可设置优先级
在增强类上面加一个注释@Order(数字类型值),数字类型值越小它的优先级越高(值从0开始)
-
完全使用注解开发
创建配置类(config),不需要创建xml配置文件
@Configuration @ComponentScan(basePackages = \"com.atguigu\") //开注解扫描 @EnableAspectJAutoProxy(proxyTargetClass = true) //开启Aspect生成代理对象 public class ConfigAop { }
-
AOP(AspectJxml配置文件)
-
创建两个类:增强类和被增强类,创建方法
-
在Spring配置文件中创建两个类的对象
-
在Spring配置文件中配置切入点
<!-- 创建两个类的对象--> <bean id=\"book\" class=\"com.atguigu.spring5.aopxml.Book\"></bean> <bean id=\"bookProxy\" class=\"com.atguigu.spring5.aopxml.BookProxy\"></bean> <!-- 配置切入点--> <aop:config> <!-- 切入点--> <aop:pointcut id=\"buy\" expression=\"execution(* com.atguigu.spring5.aopxml.Book.buy()) \"/> <!-- 配置切面(把增强应用到切入点的过程)--> <aop:aspect ref=\"bookProxy\"> <!-- 增强作用在具体方法上--> <aop:before method=\"before\" pointcut-ref=\"buy\"></aop:before> </aop:aspect> </aop:config>
JdbcTemplate
Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库操作
JdbcTemplate使用准备工作
-
引入相关jar包
-
在Spring配置文件中配置数据库连接池
<!--数据库连接池--> <bean id=\"dataSource\" class=\"com.alibaba.druid.pool.DruidDataSource\" destroy-method=\"close\"> <property name=\"name\" value=\"jdbc:mysql:///user_db\"/> <property name=\"username\" value=\"root\"/> <property name=\"password\" value=\"root\"/> <property name=\"driverClassName\" value=\"com.mysql.jdbc.Driver\"/> </bean>
-
配置JdbcTemplate对象,注入Datasource
<!--JdbcTemplate对象--> <bean id=\"jdbcTemplate\" class=\"org.springframework.jdbc.core.JdbcTemplate\"> <!--注入DataSource--> <property name=\"dataSource\" ref=\"dataSource\"></property> </bean>
-
创建service类,创建dao类,在dao注入JdbcTemplate对象
-
<!--1 开启组件扫描--> <context:component-scan base-package=\"com.atguigu\"></context:component-scan>
-
@Service public class BookService { //注入Dao @Autowired private BookDao bookDao; }
-
@Repository public class BookDaoImpl implements BookDao{ //注入JdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; }
-
JdbcTemplate操作数据库(添加)
-
对应数据库创建
-
编写service和dao
-
在dao进行数据库添加操作
jdbcTemplate.update();
-
调用JdbcTemplate中的update()实现添加操作
有两个参数:
1. sql语句 2. 可变参数设置sql语句中的值
@Repository public class BookDaoImpl implements BookDao{ //注入JdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; //添加方法 @Override public void add(Book book) { //1 创建sql语句 String sql=\"insert into t_book value(?,?,?)\"; //2 调用方法实现 Object[] args = {book.getUser_id(),book.getUsername(),book.getUstatus()}; int update = jdbcTemplate.update(sql,args); System.out.println(update); } }
-
测试类
public class TestBook { @Test public void testJdbcTemplate(){ ApplicationContext context= new ClassPathXmlApplicationContext(\"bean1.xml\"); BookService bookServcie = context.getBean(\"bookService\", BookService.class); Book book = new Book(); book.setUser_id(1); book.setUsername(\"张三\"); book.setUstatus(\"a\"); bookServcie.addBook(book); } }
-
JdbcTemplate操作数据库(修改和删除)
- 方法的实现
//修改的方法
@Override
public void updateBook(Book book) {
//1 创建sql语句
String sql=\"update t_book set username=?,ustatus=? where user_id=?\";
//2 调用方法实现
Object[] args = {book.getUsername(),book.getUstatus(),book.getUser_id()};
int update = jdbcTemplate.update(sql,args);
System.out.println(update);
}
//删除的方法
@Override
public void deleteBook(String id) {
//1 创建sql语句
String sql=\"delete from t_book where user_id=?\";
//2 调用方法实现
int update = jdbcTemplate.update(sql,id);
System.out.println(update);
}
-
测试
public class TestBook { @Test public void testJdbcTemplate(){ ApplicationContext context= new ClassPathXmlApplicationContext(\"bean1.xml\"); BookService bookServcie = context.getBean(\"bookService\", BookService.class); // //添加 // Book book = new Book(); // book.setUser_id(1); // book.setUsername(\"张三\"); // book.setUstatus(\"a\"); // bookServcie.addBook(book); //修改 Book book = new Book(); book.setUser_id(1); book.setUsername(\"javaupup\"); book.setUstatus(\"atguigu\"); bookServcie.updateBook(book); // //删除 // bookServcie.deleteBook(\"1\"); }
JdbcTemplate操作数据库(查询)
查询返回某个值
-
场景:查询表里有多少条记录,返回是某个值
-
使用JdbcTemplate实现查询返回某个值
jdbcTemplate.queryForObject(sql,Integer.class);
有两个参数:
- sql语句
- 返回类型Class
//查询表中的记录数 @Override public int selectCount() { String sql=\"SELECT COUNT(*) FROM t_book\"; Integer count = jdbcTemplate.queryForObject(sql, Integer.class); return count; }
查询返回对象
-
场景:查询图书详情
-
JdbcTemplate实现查询返回对象
jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper(Book.class), id);
三个参数:
-
sql语句
-
RowMapper,是接口,返回不同类型的数据,使用这个接口里面的实现类完成数据封装
-
传递sql语句中?(占位符)的值
@Override public Book findBookInfo(int id) { String sql=\"SELECT * FROM t_book where user_id=?\"; //调用方法 Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id); return book; }
-
查询返回集合
-
场景:查询图书列表分页
-
调用JdbcTemplate实现查询返回集合
jdbcTemplate.query(sql, new BeanPropertyRowMapper(Book.class));
三个参数:
-
sql语句
-
RowMapper,是接口,返回不同类型的数据,使用这个接口里面的实现类完成数据封装
-
传递sql语句中?(占位符)的值
*第三个参数也可不写
@Override public List<Book> findAllBook() { String sql = \"select * from t_book\"; List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class)); return bookList; }
-
JdbcTemplate操作数据库(批量)
-
批量添加
两个参数:
-
sql语句
-
List集合,添加多条记录数据
//批量添加 @Override public void batchAddBook(List<Object[]> batchArgs) { String sql = \"insert into t_book value(?,?,?)\"; int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs); System.out.println(Arrays.toString(ints)); }
-
-
批量修改
@Override public void batchUpdateBook(List<Object[]> batchArgs) { String sql = \"update t_book set username=?,ustatus=? where user_id=?\"; int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs); System.out.println(Arrays.toString(ints)); }
测试
//批量修改 List<Object[]> batchArgs = new ArrayList<>(); Object[] objects1 = {\"cc00\",\"ccc00\",\"3\"}; Object[] objects2 = {\"dd00\",\"ddd00\",\"4\"}; Object[] objects3 = {\"ee00\",\"eee00\",\"5\"}; batchArgs.add(objects1); batchArgs.add(objects2); batchArgs.add(objects3); bookServcie.batchUpdate(batchArgs);
-
批量删除
@Override public void batchDeleteBook(List<Object[]> batchArgs) { String sql=\"delete from t_book where user_id=?\"; int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs); System.out.println(Arrays.toString(ints)); }
测试
List<Object[]> batchArgs = new ArrayList<>(); Object[] objects1 = {\"3\"}; Object[] objects2 = {\"4\"}; Object[] objects3 = {\"5\"}; batchArgs.add(objects1); batchArgs.add(objects2); batchArgs.add(objects3); bookServcie.batchDelete(batchArgs);
事务操作
1、事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败则全失败
典型场景:银行转账:
- lucy转账100元给mary
- lucy少100,mary多100
2、事务的四个特性(ACID):
(1)原子性(atomicity):成功都成功,失败都失败
(2)一致性(consistency):操作之前和操作之后总量不变
(3)隔离性(isolation):多事务操作时,之间不会产生影响
(4)持久性(durability):提交之后表中数据就会发生变化
事务操作(搭建事务操作环境)
典型场景:银行转账:
转账环境:
- 创建数据库,添加记录
-
创建service,搭建dao,完成对象创建和注入关系
-
service注入dao,在dao注入JdbcTemplate,在JdbcTemplate注入DateSource
-
@Service public class UserService { //注入Dao @Autowired private UserDao userDao; }
-
@Repository public class UserDaoImpl implements UserDao{ @Autowired private JdbcTemplate jdbcTemplate; }
-
<!--1 开启组件扫描--> <context:component-scan base-package=\"com.atguigu\"></context:component-scan> <!--数据库连接池--> <bean id=\"dataSource\" class=\"com.alibaba.druid.pool.DruidDataSource\" destroy-method=\"close\"> <property name=\"url\" value=\"jdbc:mysql:///user_db\"/> <property name=\"username\" value=\"root\"/> <property name=\"password\" value=\"root\"/> <property name=\"driverClassName\" value=\"com.mysql.jdbc.Driver\"/> </bean> <!--JdbcTemplate对象--> <bean id=\"jdbcTemplate\" class=\"org.springframework.jdbc.core.JdbcTemplate\"> <!--注入DataSource--> <property name=\"dataSource\" ref=\"dataSource\"></property> </bean>
-
-
在dao创建两个方:多钱和少钱,在service创建转账方法
-
以上代码如果正常执行是没有问题的,但是如果以上代码出现异常,有问题,则应符合原子性和一致性,账户数据都不变
事务管理(Spring事务管理介绍)
- 事务一般添加到JavaEE三层结构里的service层(业务逻辑层)
- 在Spring进行事务管理操作有两种方式
- 编程式事务管理
- 声明式事务管理(常用)
- 声明式事务管理
- 基于注解方式实现(常用)
- 基于xml配置文件方式
- 在Spring进行声明式事务管理,底层使用AOP原理
- Spring事务管理API
- 提供了一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
事务操作(注解声明式事务管理)
-
在Spring配置文件配置事务管理器
<!-- 创建事务管理器--> <bean id=\"dataSourceTransactionManager\" class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\"> <!-- 注入数据源--> <property name=\"dataSource\" ref=\"dataSource\"></property> </bean>
-
在Spring配置文件中开启事务注解
-
在Spring配置文件中引入名称空间tx
xmlns:tx=\"http://www.springframework.org/schema/tx\"
-
开启事务注解
<!-- 开启事务注解--> <tx:annotation-driven transaction-manager=\"dataSourceTransactionManager\"></tx:annotation-driven>
-
-
在service类上面添加事务注解
@Transactional
- 如果添加到类上面,则类中所有方法都添加事务
- 如果添加到方法上面,则只给此方法添加事务
事务操作(声明式事务管理参数配置)
-
在service类上面添加@Transactional,在这个注解里可以配置事务相关的参数
-
propagation:事务传播行为
*多事务方法直接进行调用,这个过程中事务是如何进行管理的
-
isolation:事务隔离级别
默认值:@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
*事务有特性称为隔离性,多事务操作之间不会产生影响,不考虑隔离性会产生很多问题
*有三个读的问题:脏读、不可重复读、虚(幻)读
- 脏读:一个提交事务读取到了另一个未提交事务的数据
- 不可重复度:一个未提交事务读取到另一提交事务修改数据
- 虚读:一个未提交事务读取到另一提交事务添加****数据
解决:通过设置事务隔离级别就能解决读问题
-
timeout:超时时间
- 事务需要在一定时间内提交,如果不提交就会进行回滚
- 默认值:-1(不超时)
- 设置以秒为单位
-
readOlay:是否只读
- 读:查询操作;写:添加修改删除操作
- 默认值:false(表示可以读也可以写)
- 设置成true时只能读不能写
-
rollbackFor:回滚
- 设置出现哪些异常进行事务回滚
-
noRollbackFor:不回滚
- 设置出现哪些异常不进行事务回滚
-
事务操作(xml声明式事务管理)
-
在Spring管理文件中进行配置
-
配置事务管理器
-
配置通知
-
配置切入点和切面
<!--1 创建事务管理器--> <bean id=\"transactionManager\" class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\"> <!-- 注入数据源--> <property name=\"dataSource\" ref=\"dataSource\"></property> </bean> <!--2 配置通知--> <tx:advice id=\"txadvice\"> <!--配置事务参数--> <tx:attributes> <!--指定在哪种规则的方法上面添加事务--> <tx:method name=\"accountMoney\" propagation=\"REQUIRES_NEW\" isolation=\"REPEATABLE_READ\" read-only=\"false\"/> <!-- <tx:method name=\"account*\"/>--> </tx:attributes> </tx:advice> <!--3 配置切入点和切面--> <aop:config> <!-- 切入点--> <aop:pointcut id=\"pt\" expression=\"execution(* com.atguigu.spring5.service.UserService.*(..))\"/> <!-- 切面--> <aop:advisor advice-ref=\"txadvice\" pointcut-ref=\"pt\"/> </aop:config>
-
事务操作(完全注解声明式注解管理)
创建配置类,使用配置类代替xml配置文件
@Configuration //配置类
@ComponentScan(basePackages = \"com.atguigu\") //组件扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
//1. 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(\"com.mysql.jdbc.Driver\");
druidDataSource.setUrl(\"jdbc:mysql:///user_db\");
druidDataSource.setUsername(\"root\");
druidDataSource.setPassword(\"root\");
return druidDataSource;
}
//2. 创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
//到IOC容器中根据类型找到DateSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入DateSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//3. 创建事务管理器对象
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
Spring5框架新功能
整个Spring5框架基于java8,运行时兼容JDK9,许多不建议使用的类和方法在代码库中删除
Spiring5框架自带了通用的日志框架
- Spring5已经移除了Log4jConfigListener,官方建议使用Log4j2
- Spring5框架整合Log4j2
Log4j2使用:
1. 引入jar包
-
创建Log4j2.xml配置文件
<?xml version=\"1.0\" encoding=\"UTF-8\"?> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出--> <?import org.apache.logging.log4j.core.layout.PatternLayout?> <configuration status=\"INFO\"> <!--先定义所有的appender--> <appenders> <!--输出日志信息到控制台--> <console name=\"Console\" target=\"SYSTEM_OUT\"> <!--控制日志输出的格式--> <PatternLayout pattern=\"%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n\"/> </console> </appenders> <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效--> <!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出--> <loggers> <root level=\"info\"> <appender-ref ref=\"Console\"/> </root> </loggers> </configuration>
Spring5框架核心容器支持@Nullable注解
@Nullable注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空
Spring5核心容器支持函数风格GenericApplicationContext(lambda表达式)
public void testGenericApplicationContext(){
//创建GenericApplicationContext对象
GenericApplicationContext context = new GenericApplicationContext();
//调用context方法注册对象
context.refresh();
context.registerBean(\"user1\",User.class,()-> new User());
//获取Spring注册的对象
// User user = (User) context.getBean(\"com.atguigu.spring5.test.User\");
User user = (User) context.getBean(\"user1\");
System.out.println(user);
}
Spring5支持整合JUnit5
-
整合JUnit4
-
引入Spring测试相关的依赖
-
@RunWith(SpringJUnit4ClassRunner.class) //指定单元测试框架的版本 @ContextConfiguration(\"classpath:bean1.xml\") //加载配置文件 public class JTest4 { @Autowired private UserService userService; @Test public void test1(){ userService.accountMoney(); } }
-
-
整合JUnit5
- 引入JUnit5的jar包
-
创建测试类,使用注解完成
package com.atguigu.spring5.test; import com.atguigu.spring5.service.UserService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @ExtendWith(SpringExtension.class) @ContextConfiguration(\"classpath:bean1.xml\") //加载配置文件 public class JTest5 { @Autowired private UserService userService; @Test public void test1(){ userService.accountMoney(); } }
复合注解@SpringJUnitConfig(locations = \"classpath:bean1.xml\")
//@ExtendWith(SpringExtension.class)
//@ContextConfiguration(\"classpath:bean1.xml\") //加载配置文件
@SpringJUnitConfig(locations = \"classpath:bean1.xml\")
public class JTest5 {
@Autowired
private UserService userService;
@Test
public void test1(){
userService.accountMoney();
}
}
SpringWebFlux
webflux介绍
-
基本概念
-
Spring5添加的新的模块,用于web开发的,功能与SpringMVC相似,webflux使用一种响应式编程出现的框架
-
使用传统的web框架,比如SpringMVC,这些基于Servlet容器,webflux是一种异步非阻塞的框架,异步非阻塞的框架在servlet3.1之后才支持,核心是基于Reactor的相关API实现的
-
-
响应式编程
-
webflux执行流程和核心API
-
Springwebflux(基于注解编程模型)
-
Springwebflux(基于函数式编程模型)
来源:https://www.cnblogs.com/hanyk/p/16202135.html
本站部分图文来源于网络,如有侵权请联系删除。