Spring框架概述
- 针对bean的生命周期进行管理的轻量级容器
- 开源的JavaEE框架,解决企业开发的复杂性
- 两个主要核心:
- IOC:控制反转,把创建对象的过程交给Spring进行管理
- Aop:面向切面,不修改源代码进行功能增强
- Spring特点
- 方便解耦,简化开发
- Aop编程支持
- 方便程序测试
- 方便和其它框架进行整合
- 方便进行事务操作
- 降低API使用难度
下载地址:https://repo.spring.io/release/org/springframework/spring/
小案例
public class User {
public void add(){
System.out.println("add........");
}
}
<?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">
<!--配置User对象创建-->
<bean id="user" class="com.atgw.spring5.User"></bean>
</beans>
@Test
public void testAdd(){
//1 加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
//2 获取配置创建的对象
User user = context.getBean("user", User.class);
System.out.println(user);
user.add();
}
IOC容器
- 控制反转,把对象创建和对象之间的调用过程交给Spring进行管理
- 使用IOC目的是降低耦合
底层原理
xml解析,工厂模式,反射
接口
-
IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
-
Spring提供IOC容器实现的两种方式
- BeanFactory:是Spring内部使用的接口,不提供开发人员使用;加载配置文件的时候不会创建对象,在获取对象时才创建
- ApplicationContext:BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员使用;加载配置文件时就创建了对象
-
ApplicationContext的两个主要实现类
-
ClassPathXmlApplicationContext //类路径下的配置文件
-
FileSystemXmlApplicationContext //全类名的配置文件
-
IOC的Bean管理
什么是Bean管理(有两个操作)
- Spring创建对象
- Spring注入属性
Bean管理的操作方式
基于xml配置文件方式实现
注入属性的方法
- 创建对象(bean标签)
- id属性:获取对象的唯一标识
- class属性:类全路径
(创建对象的时候,默认也是执行无参数构造方法完成对象创建)
- 注入属性(property标签)–使用set方法
public class Book { private String bname; private String bauthor; public void setBname(String bname) { this.bname = bname; } public void setBauthor(String bauthor) { this.bauthor = bauthor; } public void testDemo(){ System.out.println("bname ="+ bname); } }
<bean id="book" class="com.atgw.spring5.Book"> <!--name:类里面属性名称 vlaue: 向属性注入的值--> <property name="bname" value="平凡的世界"></property> <property name="bauthor" value="路遥"></property> </bean>
- 注入属性(property标签)–有参函数构造方法
public class Order { private String oname; private String oaddress; public Order(String oname, String oaddress) { this.oname = oname; this.oaddress = oaddress; } }
<bean id="order" class="com.atgw.spring5.Order"> <constructor-arg name="oname" value="abc"></constructor-arg> <constructor-arg name="oaddress" value="china"></constructor-arg> </bean>
- 了解,p名称空间注入
- 添加p名称空间在配置文件中
xmlns:p="http://www.springframework.org/schema/p"
- 进行属性注入
<bean id="book" class="com.atgw.spring5.Book" p:bname="平凡的世界" p:bauthor="路遥"> </bean>
xml注入其他类型属性
字面量
null
<property name="bauthor"> <null/> </property>
属性值包含特殊符号 <![CDATA[特殊的内容]]>
<property name="bauthor"> <value><![CDATA[<<南京>>]]]></value> </property>
外部bean对象
public interface UserDao { void update(); }
public class UserDaoImpl implements UserDao { @Override public void update() { System.out.println("update方法"); } }
public class UserService { //创建UserDao类型属性,生成set方法 //准备在配置文件中进行set注入参数配置 private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void add(){ System.out.println("add方法"); userDao.update(); } }
参数注入
<bean id="userservive" class="com.atgw.spring5.service.UserService"> <!--注入userDao对象 ref属性: 创建userDao对象bean标签id值 --> <property name="userDao" ref="userDaoImpl"></property> </bean> <bean id="userDaoImpl" class="com.atgw.spring5.dao.UserDaoImpl"></bean>
测试
@Test public void testAdd(){ //1 加载spring配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml"); //2 获取配置创建的对象 UserService userService = context.getBean("userService", UserService.class); System.out.println(userService); userService.add(); }
内部bean和级联赋值
public class Dept { private String name; public void setName(String name) { this.name = name; } }
public class Empl { private String name; private String gender; //员工属于某一个部门,使用对象形式表示 private Dept dept; public void setName(String name) { this.name = name; } public void setGender(String gender) { this.gender = gender; } public void setDept(Dept dept) { this.dept = dept; } public void add(){ System.out.println(name+"::"+gender+"::"+dept); } }
内部bean配置
<bean id="empl" class="com.atgw.spring5.bean.Empl"> <!--配置两个普通属性--> <property name="name" value="LiMing"></property> <property name="gender" value="男"></property> <!--设置对象类型属性--> <property name="dept"> <bean id="dept" class="com.atgw.spring5.bean.Dept"> <property name="name" value="人力部"></property> </bean> </property> </bean>
测试
@Test public void testEmpl(){ //1 加载spring配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml"); //2 获取配置创建的对象 Empl empl = context.getBean("empl", Empl.class); System.out.println(empl); empl.add(); }
级联配置(第一种)
<bean id="empl" class="com.atgw.spring5.bean.Empl"> <!--配置两个普通属性--> <property name="name" value="LiMing"></property> <property name="gender" value="男"></property> <!--设置对象类型属性--> <property name="dept" ref="dept"></property> </bean> <bean id="dept" class="com.atgw.spring5.bean.Dept"> <property name="name" value="人力部"></property> </bean>
级联配置(第二种)
条件:要生成get方法
<bean id="empl" class="com.atgw.spring5.bean.Empl"> <!--配置两个普通属性--> <property name="name" value="LiMing"></property> <property name="gender" value="男"></property> <!--设置对象类型属性--> <property name="dept.name" value="财务部"></property> </bean> <bean id="dept" class="com.atgw.spring5.bean.Dept"> <property name="name" value="人力部"></property> </bean>
xml注入集合类型的属性
第一种
public class Stu {
//数组类型属性
private String[] courses;
//list集合的属性
private List<String> list;
//map集合类型属性
private Map<String,String> maps;
//set集合
private Set<String> sets;
}
<bean id="stu" class="com.atgw.spring5.collectiontype.Stu">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>java课程</value>
<value>数据库课程</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="sets">
<set>
<value>mysql</value>
<value>redis</value>
</set>
</property>
</bean>
第二种(在集合里面设置对象类型的值)
public class Course {
private String cname;
public void setCname(String cname) {
this.cname = cname;
}
}
//List<Course>集合类型
private List<Course> courseList;
<!--配置List<Course>类型的属性-->
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
<!--创建多个Course对象-->
<bean id="course1" class="com.atgw.spring5.collectiontype.Course">
<property name="cname" value="Spring框架"></property>
</bean>
<bean id="course2" class="com.atgw.spring5.collectiontype.Course">
<property name="cname" value="MyBatis框架"></property>
</bean>
第三种(把集合注入部分提取出来)
public class Book {
private List<String> list;
public void setList(List<String> list) {
this.list = list;
}
}
<!--提取list集合类型属性注入-->
<util:list id="bookList">
<value>计算机网络</value>
<value>计算机组成原理</value>
<value>数据结构</value>
</util:list>
<!--使用提取的list集合-->
<bean id="book" class="com.atgw.spring5.collectiontype.Book">
<property name="list" ref="bookList"></property>
</bean>
FactoryBean
- 普通bean:在配置文件中定义bean类型就是返回类型
- 工厂bean:在配置文件定义bean类型可以和返回类型不一样(实现:定义一个来实现FactoryBean,并实现其中的方法)
public class MyBean implements FactoryBean<Course> {
//获取哪种类型的返回值
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCname("数据挖掘");
return course;
}
}
<bean id="myBean" class="com.atgw.spring5.factorybean.MyBean"></bean>
@Test
public void test3(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
Course course = context.getBean("myBean", Course.class);
System.out.println(course);
}
Bean的作用域
如何设置单实例还是多实例(就是每次获取的对象是否是同一个对象)
- 在配置文件的bean标签有属性(scope)用于设置单实例还是多实例
- scope:
- 默认值singleton–单实例(在加载配置文件的时候就会创建对象)
- prototype–多实例(在getBean()时才会创建对象)
- request
- session
Bean的生命周期
- 通过构造器创建bean实例(无参数构造)
- 为bean的属性设置值和对其他bean引用(调用set方法)
- 调用bean的初始化方法(需要进行配置初始化的方法)
- bean可以使用
- 当关闭容器的时候,调用bean的销毁的方法(需要进行配置销毁的方法)
bean的后置处理器
在第三步的前后,会把bean实例传递bean后置处理器的方法
public class Order {
private String oname;
public Order() {
System.out.println("第一步,执行初始化方法");
}
public void setOname(String oname) {
this.oname = oname;
System.out.println("第二步,调用set方法设置属性值");
}
public void initMethod(){
System.out.println("第三步,执行初始化的方法");
}
public void destoryMethod(){
System.out.println("第五步,执行销毁的方法");
}
}
<bean id="orders" class="com.atgw.spring5.collectiontype.Order" init-method="initMethod" destroy-method="destoryMethod">
<property name="oname" value="手机"></property>
</bean>
@Test
public void test4(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
Order order = context.getBean("orders", Order.class);
System.out.println("第四步,获取创建bean实例对象");
System.out.println(order);
//手动让bean实例销毁
context.close();
}
配置后置处理器
- 创建一个类实现BeanPostProcessor接口,并重写其中的方法
- 在配置文件中进行配置,会对所有的bean对象加上后置处理器
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.atgw.spring5.MyBeanPost"></bean>
xml自动装配
根据指定的装配规则(属性名称或属性类型),Spring自动将匹配的属性值进行注入
<!--实现自动装配
autowire属性常用的两个值:
byName:根据属性名注入,注入值bean的id值和类属性名称一致
byType:根据属性类型注入
-->
<bean id="emp" class="com.atgw.spring5.autowire.Emp" autowire="byName">
<!--<property name="dept" ref="dept"></property>-->
</bean>
<bean id="dept" class="com.atgw.spring5.autowire.Dept"></bean>
引入外部属性文件
连接数据库
jdbc.properties外部配置文件
prop.username=root
prop.password=000519
prop.url=jdbc:mysql://localhost:3306/book?useUnicode=true&characterEncoding=utf-8&useSSL=false&rewriteBatchedStatement=true
prop.driverClass=com.mysql.jdbc.Driver
bean6.xml文件
1.先引入context名称空间
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
2.因为外部属性文件,动态获取
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置连接池-->
<bean id="dataSource" 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>
基于注解方式实现
使用注解目的:简化xml配置
Spring针对Bean管理中创建对象提供了注解
- @Component
- @Service
- @Controller
- @Repository
上面的四个注解功能是一样的,都可以用来创建bean实例,一般分别用在不同的层面
导入jar包:spring-aop-5.2.6.RELEASE.jar
//注解里面的value值可以省略不写
//默认值是类名称,首字母小写
@Component(value = "userService")
public class UserService {
public void add(){
System.out.println("service add 方法");
}
}
组件扫描
<!--开启组件扫描
1.如果扫描多个包,包之间可以用,隔开
2.扫描包的下层目录
-->
<context:component-scan base-package="com.atgw.spring5"></context:component-scan>
组件扫描的作用就是在所添加的包路径下面寻找添加了注解的类,然后会在测试方法中的加载xml配置文件时自动创建类
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
System.out.println(userService);
userService.add();
}
组件扫描配置的细节
<!--示例1-->
<!--use-default-filters="false":表示不使用默认的filter,自己配置filter
context:include-filter:设置扫描哪些内容
下面的表示只扫描包下面的只带有Controller注解的类
-->
<context:component-scan base-package="com.atgw.spring5" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--示例2-->
<!--
context:exclude-filter:与上面的相反,表示除了列出的类,其他的类都要扫描
下面的表示不扫描带有Controller的类,其他的类都扫描-->
<context:component-scan base-package="com.atgw.spring5" use-default-filters="false">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
属性注入
@AutoWired:根据属性类型自动装配
步骤:
- 在service和dao类添加创建对象注解,实现service和dao对象创建
- 在service中添加dao类型属性,在属性上面使用注解
@Service public class UserService { //定义dao类型属性 //不需要添加set方法 //添加注入属性注解 @Autowired private UserDao userDao; public void add(){ System.out.println("service add 方法"); userDao.add(); } }
public interface UserDao { void add(); }
@Repository public class UserDaoImpl implements UserDao { @Override public void add() { System.out.println("dao add方法"); } }
@Qualifier:根据属性名进行注入
- 要和@AutoWired一起使用,因为@AutoWired只是根据类型进行注入,如果一个类型(接口)可能有多个实现类,那就不确定是要注入哪个实现类了
@Repository(value = "userDaoImpl1") public class UserDaoImpl implements UserDao { }
@Autowired//根据类型进行注入 @Qualifier(value = "userDaoImpl1")//根据名称进行注入 private UserDao userDao;
@Resource:可以根据类型注入,也可以根据名称注入
@Resource注解并不是Springh中的注解,而是javax包下的
- 根据类型注入,不要添加name属性的值
- 根据名称进行注入,就要添加要注入属性的实现类(userDaoImpl)所添加注解的名称(userDaoImpl1)与name属性的值一致
@Repository(value = "userDaoImpl1") public class UserDaoImpl implements UserDao { }
//@Resource//根据类型注入 @Resource(name = "userDaoImpl1")//根据名称进行注入 private UserDao userDao;
@Value:注入普通类型属性
@Value(value = "liming") private String name;
完全注解开发
- 创建配置类,代替xml配置文件
@Configuration//作为配置类,替代xml配置文件
@ComponentScan(basePackages = {
"com.atgw.spring5"})
public class SpringConfig {
}
没有配置文件,就不需要再加载配置文件,而是需要加载配置类
@Test
public void test2(){
//加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
System.out.println(userService);
userService.add();
}
AOP
概念
面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- 不通过修改源代码,在主干功能里面添加新的功能
底层原理
底层使用动态代理
有接口的情况,使用JDK动态代理
创建接口实现类代理对象,增强类的方法
没有接口的情况,使用CGLIB动态代理
创建当前类的子类代理对象,增强类的方法
JDK动态代理实现
- 使用JDK动态代理,使用
Proxy
类里面的方法创建代理对象 - 调用
static Object newProxyInstance
( , , )方法,返回指定接口的代理类的实例- 第一个参数:类加载器
- 第二个参数:增强方法所在的类,这个类实现的接口,支持多个接口
- 实现这个接口
InvocationHandler
,创建代理对象,写增强的方法
创建接口,定义方法
public interface UserDao { int add(int a, int b); String update(String id); }
创建接口实现类,实现方法
public class UserDaoImpl implements UserDao{ @Override public int add(int a, int b) { System.out.println("add方法执行了"); return a+b; } @Override public String update(String id) { System.out.println("update方法执行了"); return id; } }
使用Proxy类创建接口代理对象
public class JDKProxy { public static void main(String[] args) { //创建接口实现类代理对象 Class[] inter = { UserDao.class}; UserDaoImpl userDao = new UserDaoImpl(); UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), inter, new UserDaoProxy(userDao)); int result = dao.add(1, 4); System.out.println("result:"+result); } } //创建代理对象的代码 class UserDaoProxy implements InvocationHandler{ //把代理对象传递过来(UserDaoImpl类) //有参数构造器 private Object obj; public UserDaoProxy(Object obj){ this.obj=obj; } //增强的逻辑 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //被增强的方法(就是UserDaoImpl类中的方法)之前 System.out.println("方法之前..下面要执行的方法是:"+method.getName()+";传递的参数:"+ Arrays.toString(args)); //执行原被增强的方法 Object res = method.invoke(obj,args); //被增强的方法之后 System.out.println("方法之后..返回的对象"+obj); return res; } }
分析
我们的目的是将一个接口的实现类中的方法功能增强,要采取下面的方法
- 使用
Proxy.newProxyInstance()
方法来创建这个实现类的代理对象,意思就是这个方法会返回一个这个实现类的对象 Proxy.newProxyInstance()
需要三个参数- 第一个参数类加载器,就是创建代理对象所在的类(JDKProxy)
- 第二个参数是这个实现类,数组形式传入
- 第三个参数是实现了
InvocationHandler
接口的类,在这个类中有invoke()
方法,它会实现要增强的方法,其中的method
参数是被增强的实现类的哪一个被增强的方法,args
参数是被增强方法的参数
- 当得到代理对象后,我们就会得到已经增强的方法
AOP操作术语
-
连接点
类里面哪些方法可以被增强,这些方法称为连接点
-
切入点
实际被真正增强的方法,称为切入点
-
通知(增强)
实际增强的部分的称为通知
通知有多种类型
- 前置通知
- 后置通知
- 环绕通知(在被增强的方法前后)
- 异常通知(有异常,才会执行)
- 最终通知(有异常,不会执行)
-
切面
把通知应用到切入点的过程
AOP操作
Spring框架一般都是基于AspectJ实现AOP操作
AsprctJ不是Spring组成部分,是独立的AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作
- 基于xml配置文件实现
- 基于注解方式实现
引入jar包依赖:
spring-aspects-5.2.6.RELEASE.jar
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
切入点表达式
作用就是知道哪个类里面的哪个方法进行增强
表达式:execution ( [权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]) )
所有的(修饰符,类,方法)用 * 表示
注解方式实现
1 创建类,定义方法
public class User {
public void add(){
System.out.println("add方法.........");
}
}
2 创建增强类(编写增强逻辑代码)
在增强类里面,创建方法,让不同方法代表不同的通知类型
//增强类
public class UserProxy {
//前置通知
public void before(){
System.out.println("before.....");
}
}
3 进行通知的配置
-
在spring配置文件中,开启注解扫描
添加需要的命名空间
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--开启注解扫描--> <context:component-scan base-package="com.atgw.spring5.aopanno"></context:component-scan> </beans>
-
使用注解创建User和 UserProxy对象
@Component public class User { }
//增强类 @Component public class UserProxy { }
-
在增强类上面添加注解 @Aspect,生成代理对象
//增强类 @Component @Aspect//生成代理对象 public class UserProxy { }
-
在spring 配置文件中开启生成代理对象
<!--开启Aspect生成代理对象, 会在上面的包中寻找有Aspect注解的类,生成代理对象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
4 配置不同类型的通知
在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
//前置通知
//Before注解表示作为前置通知
@Before(value = "execution(* com.atgw.spring5.aopanno.User.add(..))")
public void before(){
System.out.println("before.....");
}
测试1
@Test
public void testAopAnno(){
ApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");
User user = context.getBean("user", User.class);
user.add();
}
}
//before.....
//add方法.........
结果表明在add方法之前,before方法会先执行,完成前置通知
测试2
//前置通知
//Before注解表示作为前置通知
@Before(value = "execution(* com.atgw.spring5.aopanno.User.add(..))")
public void before(){
System.out.println("before前置.....");
}
//后置通知
@After(value = "execution(* com.atgw.spring5.aopanno.User.add(..))")
public void after(){
System.out.println("after后置.......");
}
//异常通知(有异常,才会执行)
@AfterThrowing(value = "execution(* com.atgw.spring5.aopanno.User.add(..))")
public void afterThrowing(){
System.out.println("afterThrowing异常.......");
}
//环绕通知
@Around(value = "execution(* com.atgw.spring5.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("around环绕之前.......");
//被增强的方法执行
proceedingJoinPoint.proceed();
System.out.println("around环绕之后......");
}
//最终通知(有异常,不会执行)
@AfterReturning(value = "execution(* com.atgw.spring5.aopanno.User.add(..))")
public void afterReturning(){
System.out.println("afterReturning最终.......");
}
//around环绕之前.......
//before前置.....
//add方法.........
//around环绕之后......
//after后置.......
//afterReturning最终.......
相同切入点提取
//相同切入点抽取
@Pointcut(value = "execution(* com.atgw.spring5.aopanno.User.add(..))")
public void pointDemo(){
}
//前置通知
//Before注解表示作为前置通知
@Before(value = "pointDemo()")
public void before(){
System.out.println("before前置.....");
}
多个增强类对同一个方法进行增强,设置增强类优先级
- 在增强类上面添加注解@Order(数字类型值),数字越小,优先级越高
@Component
@Aspect
@Order(3)
public class PersonProxy {
}
@Component
@Aspect//生成代理对象
@Order(1)
public class UserProxy {
}
UserProxy
的优先级要高于PersonProxy
配置文件实现
public class Book {
public void buy(){
System.out.println("buy......");
}
}
public class BookProxy {
public void before(){
System.out.println("before......");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--创建对象-->
<bean id="book" class="com.atgw.spring5.aopxml.Book"></bean>
<bean id="bookProxy" class="com.atgw.spring5.aopxml.BookProxy"></bean>
<!--配置aop增强-->
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(* com.atgw.spring5.aopxml.Book.buy(..))"/>
<!--切面-->
<aop:aspect ref="bookProxy">
<!--增强作用在具体的方法上-->
<!--下面的语句表示 其中的 前置通知 before方法作用在上面定义的 p 切入点上-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
</beans>
测试
@Test
public void testAopXml(){
ApplicationContext context =
new ClassPathXmlApplicationContext("bean2.xml");
Book book = context.getBean("book", Book.class);
book.buy();
}
全注解开发
需要创建配置类
@Configuration//配置类
@ComponentScan(basePackages = {
"com.atgw.spring5"})//开启注解扫描
@EnableAspectJAutoProxy(proxyTargetClass = true)//开启Aspect生成代理对象
public class ConfigAop {
}
JdbcTemplate
概念
- Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库操作
基本配置
引入jar包
- mysql-connector-java-5.1.7-bin.jar
- spring-jdbc-5.2.6.RELEASE.jar
- spring-orm-5.2.6.RELEASE.jar
- spring-tx-5.2.6.RELEASE.jar
配置连接池
<!--直接配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/user_db"></property>
<property name="username" value="root"></property>
<property name="password" value="000519"></property>
</bean>
配置JdbcTemplate对象,注入DateSource
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
开启组件扫描
<!--开启组件扫描-->
<context:component-scan base-package="com.atgw.spring5"></context:component-scan>
创建Service和Dao对象
@Service
public class BookService {
//注入Dao
@Autowired
private BookDao bookDao;
}
@Repository
public class BookDaoImpl implements BookDao{
//注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
}
添加操作
jdbcTemplate.update()
对应数据表t_book创建实体类
public class Book {
private Integer userId;
private String username;
private String ustatus;
}
从Service层到Dao层的添加方法的实现
BookService程序
//增加操作 public void addBook(Book book){ bookDao.add(book); }
BookDao接口
//添加的操作 void add(Book book);
BookDaoImpl实现类
//添加方法 @Override public void add(Book book) { String sql = "insert into t_book (`user_id`,`username`,`ustatus`) values(?,?,?)"; //调用方法实现 int update = jdbcTemplate.update(sql, book.getUserId(),book.getUsername(),book.getUstatus()); System.out.println("操作了:"+ update+" 条数据"); }
测试
@Test public void testJdbcTemplate(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); BookService bookService = context.getBean("bookService", BookService.class); Book book = new Book(); book.setUserId(1); book.setUsername("liming"); book.setUstatus("A"); bookService.addBook(book); }
修改和删除
jdbcTemplate.update()
BookService程序
//修改操作 public void updataBook(Book book){ bookDao.updateBook(book); } //删除操作 public void deleteBook(Integer id){ bookDao.deleteBook(id); }
BookDao接口
//修改 void updateBook(Book book); //删除 void deleteBook(Integer id);
BookDaoImpl实现类
//修改 @Override public void updateBook(Book book) { String sql = "update t_book set `username`=?,`ustatus`=? where `user_id`=?"; int update = jdbcTemplate.update(sql, book.getUsername(), book.getUstatus(), book.getUserId()); System.out.println("操作了:"+ update+" 条数据"); } //删除 @Override public void deleteBook(Integer id) { String sql = "delete from t_book where `user_id`=?"; int update = jdbcTemplate.update(sql, id); System.out.println("操作了:"+ update+" 条数据"); }
查询
1 查询记录数
jdbcTemplate.queryForObject()
BookService程序
//查询表中的数量 public int findCount(){ return bookDao.selectCount(); }
BookDao接口
//查询表中数量 int selectCount();
BookDaoImpl实现类
//查询表中数量 @Override public int selectCount() { String sql = "select count(*) from t_book"; Integer count = jdbcTemplate.queryForObject(sql, Integer.class); return count; }
2 查询指定对象(返回单个对象)
jdbcTemplate.queryForObject()
//查询返回对象 public Book findOne(Integer id){ return bookDao.selectBookInfo(id); }
Book selectBookInfo(Integer id);
BeanPropertyRowMapper是RowMapper的实现类,完成对相应类的数据封装
@Override public Book selectBookInfo(Integer id) { String sql = "select * from t_book where `user_id`=?"; // BeanPropertyRowMapper是RowMapper的实现类,完成对相应类的数据封装 Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id); return book; }
2 查询指定对象(返回集合对象)
jdbcTemplate.query()
//查询所有记录 public List<Book> findAll(){ return bookDao.selectAllBookInfo(); }
List<Book> selectAllBookInfo();
@Override public List<Book> selectAllBookInfo() { String sql = "select * from t_book"; List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class)); return bookList; }
批量操作
1 批量添加
jdbcTemplate.batchUpdate()
//批量添加 public void batchAdd(List<Object[]> listArgs){ bookDao.batchAddBook(listArgs); }
void batchAddBook(List<Object[]> listArgs);
@Override public void batchAddBook(List<Object[]> listArgs) { String sql = "insert into t_book values(?,?,?)"; int[] ints = jdbcTemplate.batchUpdate(sql, listArgs); System.out.println(Arrays.toString(ints)); }
测试
// 批量添加 List<Object[]> listBook = new ArrayList<>(); Object[] o1 = { 4,"java","A"}; Object[] o2 = { 5,"c++","B"}; Object[] o3 = { 6,"mysql","A"}; listBook.add(o1); listBook.add(o2); listBook.add(o3); bookService.batchAdd(listBook);
2 批量修改
jdbcTemplate.batchUpdate()
//批量修改 public void batchUpdate(List<Object[]> listArgs){ bookDao.batchUpdateBook(listArgs); }
void batchUpdateBook(List<Object[]> listArgs);
@Override public void batchUpdateBook(List<Object[]> listArgs) { String sql = "update t_book set `username`=?,`ustatus`=? where `user_id`=?"; int[] ints = jdbcTemplate.batchUpdate(sql, listArgs); System.out.println(Arrays.toString(ints)); }
3 批量删除
jdbcTemplate.batchUpdate()
//批量删除 public void batchDelete(List<Object[]> listArgs){ bookDao.batchDeleteBook(listArgs); }
void batchDeleteBook(List<Object[]> listArgs);
@Override public void batchDeleteBook(List<Object[]> listArgs) { String sql = "delete from t_book where `user_id`=?"; int[] ints = jdbcTemplate.batchUpdate(sql, listArgs); System.out.println(Arrays.toString(ints)); }
测试
//批量删除 List<Object[]> listBook = new ArrayList<>(); Object[] o1 = { 4}; Object[] o2 = { 5}; Object[] o3 = { 6}; listBook.add(o1); listBook.add(o2); listBook.add(o3); bookService.batchDelete(listBook);
事务
四大特性(ACID)
- 原子性(atomicity):不可分割,一个事务中的操作要么都做,要么都不做
- 一致性(consistency):从一个一致性状态到另一个一致性状态
- 隔离性(isolation):一个事物的执行不能被另一个事务干扰
- 持续性(durability):一个事物一旦提交,它对数据库的数据改变就应该是永久性的
搭建事务操作环境案例
银行转账业务
创建数据库表
CREATE TABLE t_account( `id` VARCHAR(20) PRIMARY KEY NOT NULL, `username` VARCHAR(50), `money` INT ) INSERT INTO t_account VALUES('1','lucy',1000), ('2','mary',1000);
配置xml文件
<!--开启组件扫描--> <context:component-scan base-package="com.atgw.spring5"></context:component-scan> <!--直接配置连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/user_db"></property> <property name="username" value="root"></property> <property name="password" value="000519"></property> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--注入dataSource--> <property name="dataSource" ref="dataSource"></property> </bean>
创建Service,搭建dao,完成对象创建和注入关系
(service注入dao,在dao注入JdbcTemplate,在JdbcTemplate注入DataSource)
public interface UserDao { //少钱 void reduceMoney(); //多钱 void addMoney(); }
//创建对象 @Repository public class UserDaoImpl implements UserDao { //注入数据库连接池 @Autowired private JdbcTemplate jdbcTemplate; @Override public void reduceMoney() { String sql = "update t_account set money=money-? where username=?"; jdbcTemplate.update(sql,100,"lucy"); } @Override public void addMoney() { String sql = "update t_account set money=money+? where username=?"; jdbcTemplate.update(sql,100,"mary"); } }
//创建对象 @Service public class UserService { //注入dao @Autowired private UserDao userDao; public void accountMoney(){ userDao.reduceMoney(); userDao.addMoney(); } }
测试
@Test public void testAccount(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); UserService userService = context.getBean("userService", UserService.class); userService.accountMoney(); }
使用事务来保证数据的可靠
Spring事务管理介绍
- 事务添加到Service层(业务逻辑层)
- 在Spring进行事务管理操作
- 编程式(try-catch来捕获异常,没有异常就提交,有异常就回滚)
- 声明式(使用)
- 声明式事务管理
- 基于注解方式
- 基于xml配置文件方式
- 在Spring进行声明式事务管理,底层使用AOP原理
- 提供了一个接口
PlatformTransactionManager
,代表事务管理器,实现类DataSourceTransactionManager
声明式(注解方式)
1 在Spring配置文件中配置事务管理器
<!--创建事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean>
2 开启事务注解
添加命名空间 xmlns:tx="http://www.springframework.org/schema/tx" http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
<!--开启事务注解--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
3 在service层类上面(或者里面的方法上面)添加事务注解
@Transactional
- 加在类上面,就对类里面的所有方法起作用
- 加在方法上面,就对该方法起作用
@Transactional//添加事务 public class UserService{ }
声明式参数配置
在@Transactional注解中有参数
- propagation:事务传播行为
- isolation:事务隔离级别
- timeout:超时时间
- readOnly:是否只读
- rollbackFor:回滚
- noRollBackFor:不回滚
事务传播行为
事务传播行为(方法之间的调用在有无事务时不同的处理)
@Transactional public void add(){ update(); } public void update(){ }
- REQUIRED
- 如果add()方法有事务,则update()方法在add()的同一个事务中执行
- 如果add()方法没有事务,则会创建新的事务
- REQUIRED_NEW
- 不论add方法是否有事务,都会创建新的事务
@Transactional(propagation = Propagation.REQUIRED)//添加事务 public class UserService{ }
隔离级别
不考虑隔离性,会出现三个问题
- 脏读:一个未提交的事务读到了另一个未提交事务的数据
- 不可重复读:一个未提交的事务读到了另一个事务修改前和修改后的数据,是不同的
- 幻读
通过设置事务隔离性,解决上面三个问题
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
READ UNCOMMITTED(读未提交) | 有 | 有 | 有 |
READ COMMITTED(读已提交) | 无 | 有 | 有 |
REPEATABLE READ(可重复读) | 无 | 无 | 有 |
SERIALIZABLE | 无 | 无 | 无 |
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)//添加事务
其他参数
超时时间timeout
-
事务需要在一定时间内进行提交,如果不提交就进行回滚
-
默认值是-1,表示不回滚
是否可读readOnly
- 默认值false,表示可以查询,可以增删改
- true,表示只能查询
回滚rollbackFor
- 设置出现哪些异常时进行事务回滚
不回滚noRollbackFor
- 设置出现哪些异常时不进行事务回滚
声明式(XML文件方式)
<!--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="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--3 配置切入点,切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.atgw.spring5.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
完全注解方式
创建配置类
@Configuration//配置类 @ComponentScan(basePackages = "com.atgw")//组件扫描 @EnableTransactionManagement//开启事务 public class TxConfig { //创建数据库连接池 @Bean public DruidDataSource getDruidDateSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/user_db"); dataSource.setUsername("root"); dataSource.setPassword("000519"); return dataSource; } //创建JdbcTemplate对象 @Bean public JdbcTemplate getJdbcTemplate(DataSource dataSource){ //到IOC容器中根据类型找到dataSource JdbcTemplate jdbcTemplate = new JdbcTemplate(); //注入dataSource jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } //创建事务管理器 @Bean public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){ DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } }
测试
@Test public void testAccount1(){ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class); UserService userService = context.getBean("userService", UserService.class); userService.accountMoney(); }
Spring5新功能
基于JDK8,兼容JDK9
- 自带了通用的日志封装
引入log4j的jar包
- log4j-api-2.11.2.jar
- log4j-core-2.11.2.jar
- log4j-slf4j-impl-2.11.2.jar
- slf4j-api-1.7.30.jar
创建log4j2.xml文件(固定文件名)
<?xml version="1.0" encoding="UTF-8"?> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出--> <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>
public class UserLog { private static final Logger log = LoggerFactory.getLogger(UserLog.class); public static void main(String[] args) { log.info("hello log4j2"); log.warn("hello log4j2"); } }