spring
1.学习架构
1.1spring框架的概述以及spring中基于xml的ioc配置
1.2spring中基于注解的ioc和ioc的案例
1.3spring中的aop和基于xml以及注解的aop配置
1.4spring中JDBCTemlate以及spring的事务控制
2.spring的概述
2.1spring是什么
全栈式轻量级开源框架,以ioc和aop为内核,还可以整合其他开源框架
全栈式:是一种解决方案,包含了开发框架和运行环境,有了他,无需下载别的软件,包含做一个网站的全部环境
轻量级:占用系统资源小,不依赖于其他类提供的容器
2.2两大核心
ioc和aop
2.3发展历程和优势
1997年提出EJB思想
Rod Johnson(spring之父)
2017年9月发布spring最新版本spring5.0通用版
优势:方便解耦,简化开发
Aop编程的支持
声明式事务的支持
声明式就是通过配置的方式实现对事务的控制
方便程序的测试
方便继承各种优秀的框架
降低java api的使用难度
java源码的经典的学习范例
2.4体系结构
3.程序的的耦合及解耦
耦合与解耦
耦合:程序间的依赖关系
包括类之间和方法之间
解耦:降低程序间的依赖关系
实际开发中,应该做到编译期不依赖,运行期才依赖
解耦的思路:
- 第一步:尽量使用反射的方式创建对象,而避免使用new的方式
- 第二步:通过配置文件来获取要创建对象的全限定类名
案例:通过反射的方式注册驱动来降低类之间的依赖关系
public static void main(String[] args) {
try {
//1.注册驱动
//原先的注册驱动的方式:
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?characterEncoding=utf-8","root","123");
//3.写sql语句
PreparedStatement ps=connection.prepareStatement("select * from user");
//4.遍历结果集
ResultSet rs=ps.executeQuery();
while(rs.next()){
System.out.println(rs.getString("username"));
}
//5.释放资源
rs.close();
ps.close();
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
3.1曾经案例中的问题
删除一个类文件,整个项目就会报错,类之间的依赖关系很大,急迫需要解决
3.2工厂模式解耦
bean工厂:用来创建service和dao层的对象
什么是bean:在计算机英语中有可重用组件的含义
什么是javabean:用java语言写的可重用组件,他不等于实体类,并且比实体类的范围要大
工厂模式如何解耦的:
- 创建一个配置文件来配置service和dao,配置内容应该包含,唯一标识+全限定类名(key-value)
- 读取配置文件中的内容,使用反射的方式创建对象
配置文件可以有两中文件形式,可以是xml和properties
模拟实现工厂模式的小案例
大致思路是:模拟保存用户信息,在三层之间存在类之间的依赖关系,如果删除某一个类,则项目会在编译期就报错,而使用工厂模式(通过创建一个properties问件,然后读文件反射的方式创建对象)之后,编译期不会依赖,在运行期依赖
第一步:创建dao层和service层
//持久层接口
public interface IAccountDao{
void saveAccount();
}
//持久层接口实现类
public class AccountDaoImpl implements IAccountDao{
public void saveAccount(){
System.out.println("保存了账户");
}
}
//业务层接口
public interface IAccountService{
void saveAccount();
}
//业务层实现类
public class AccountServiceImpl implements IAccountService{
//依赖于AccountDaoImpl类
private IAccountDao accountDao = new AccountDaoImpl();
public void saveAccount(){
return accountDao.saveAccount();
}
}
//表现层
public class Client{
//依赖于AccountServiceImpl类
private IAccountService accountService = new AccountServiceImpl();
public static void main(String[] args){
accountService.saveAccount();//保存了账户
}
}
可以看到表现层依赖于业务层,业务层依赖与持久层
第二步:创建properties文件
accountService=com.itblp.service.impl.AccountServiceImpl
accountDao=com.itblp.dao.impl.AccountDaoImpl
第三步:使用工厂模式解耦
//使用工厂模式解耦
public class BeanFactory{
private static Properties properties;
//类初始化时就加载
static{
try{
//实例化对象
properties = new Properties();
//加载配置文件
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
properties.load(in);
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败");
}
}
public static Object getBean(String beanName){
Object bean = null;
try{
//获取要加载的类的全限定类名
String beanPath = properties.getProperty(beanName);
//反射创建对象, newInstance每次都会调用默认构造函数创建对象
bean = Class.forName(beanPath).newInstance();
return bean;
}catch(Exception e){
e.printStackTrace();
}
}
}
第四步:使用传递字符串来创建对象了
//创建AccountServiceImpl对象
IAccountService accountService = BeanFactory.getBean("accountService");
但是通过上述方式创建的对象是多例的,如果我们想创建出来的对象都是单例的,那就需要一个map集合,将properties文件中的对象都存起来,不用每次都重新创建,因为,调用Class.forName(beanPath).newInstance()方法会调用无参构造方法创建对象
第五步:改造工厂类使其创建出来的对象都是单例的
public class BeanFactory{
private static Properties properties;
private Map<String,Object> beans;
static{
try{
//实例化对象
properties = new Properties();
//加载配置文件
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
properties.load(in);
//获取文件中的所有key
Enumeration keys=properties.keys();
//遍历枚举
while(keys.hasMoreElements()){
//取出每隔key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = properties.getProperty(key);
//创建bean对象
Object bean = Class.forName(beanPath).newInstance();
//将key和value存入map中
beans.put(key,bean);
}
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败");
}
}
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
4.ioc的概念和spring中的ioc
IOC(inversion of control) 控制反转,降低代码之间的依赖关系
ioc核心容器就是一个map,map里面封装着我们要用的对象
4.1spring中基于xml的ioc环境搭建
4.1.1 环境搭建
第一步:创建maven工程,导入坐标
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
第二步:创建配置文件bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--把创建对象交给spring来管理-->
<bean id="accountService" class="com.itblp.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.itblp.dao.impl.AccountDaoImpl"></bean>
</beans>
第三步:创建测试类
public class Client{
public static void main(String[] args){
//1.获取核心容器
ApplicationContext ac = new ClassPathXMLApplicationContext("bean.xml");
//2.根据id获取对象
//获取对象的两种方式,一种不用传class字节码,但是需要强制转换,一种是传class字节码,不用强制转换
IAccountService as = (IAccountService) ac.getBean("accountService");
IAccountDao ad = ac.getBean("accountDao",IAccountDao.class);
System.out.println(as);
System.out.println(ad);
}
}
【扩展知识】:
4.1.2获取核心容器的三种方式
- ClassPathXmlApplicationContext:根据类路径加载配置文件,如果配置文件不在类路径下,则无法加载对象
- FileSystemXmlApplicationContext:根据磁盘绝对路径加载配置文件(必须有访问权限)
- AnnotationConfigApplicationContext:用于读注解创建容器
4.1.3对象加载时机
- ApplicationContext:采用立即加载的方式,只要容器一创建,就将对象创建好了
示例:
首先在AccountServiceImpl类中的构造方法中加一句话:
public class AccountServiceImpl implements AccountService{
public AccountServiceImpl(){
System.out.println("对象创建了");
}
}
然后断点观察控制台,执行完
ApplicationContext ac = new ClassPathXMLApplicationContext("bean.xml");//控制台打印:对象创建了
对象就已经创建了。之后再通过唯一标识,也就是id获取对象就不会再创建了。
- BeanFactory:采用延迟加载的方式,什么时候通过唯一标识获取对象,什么时候就创建
示例:
改写Client类中的方法
public class Client{
public static void main(String[] args){
//1.加载配置资源,获取核心容器
Resource resource = new Resource("bean.xml");
BeanFactory beanFactory = new XMLBeanFactory(resource);
//2.根据id获取对象
IAccountService as = beanFactory.getBean("accountService",IAccountService.class);
System.out.println(as);
}
}
依然打断点看到执行到这句之后才会创建对象
IAccountService as = beanFactory.getBean("accountService",IAccountService.class);//控制台打印:对象创建了
4.1.4什么对象适合立即加载,什么对象适合延迟加载
- 单例对象只需要创建一次,,之后再创建都是这一个对象,所以适合立即加载,也就是容器一加载完毕,就立即创建对象
- 多例对象时,每次创建都会是一个新的对象,如果一开始就立即创建,那么下次创建时还是会创建新的对象,所以是由延迟加载的方式。
4.1.5Bean的三种创建方式
-
使用默认构造方法创建bean对象,如果没有默认构造方法,则无法创建对象
<bean id="accountService" class="com.itblp.service.AccountServiceImpl"></bean>
-
使用工厂中的方法创建对象,或者某个类中的方法创建对象
首先创建一个InterfaceFactory类
public class InterfaceFactory{ public IAccountService getAccountService(){ return new AccountService(); } }
配置文件:
<bean id="interfaceFactory" class="com.itblp.factory.InterfaceFactory"></bean> <bean id="accountService" factory-bean="interfaceFactory" factory-method="getAccountService"></bean>
3.使用工厂的静态方法创建对象
首先创建一个工厂
public class StaticFactory{
public static IAccountService getAccountService(){
return new AccountService()
}
}
配置文件
<bean id="accountService" class="com.itblp.factory.StaticFactory" factory-method="getAccountService"></bean>
4.1.6Bean的作用范围
使用的属性是scope
作用:用于指定bean的作用范围
取值:1.singleton:单例 ,也是默认的
2.propertype:多例
单例和多例的区别:创建出相同和不同的对象
//xml文件中:
//<bean id="accountService" class="com.itblp.factory.StaticFactory" factory-method="getAccountService" scope=“propertype”></bean>
//测试类中
ApplicationContext ac = new ClassPathXmlApplication("bean.xml");
IAccountService accountService1 = ac.getBean("accountService",IAccountService.class);
IAccountService accountService2 = ac.getBean("accountService",IAccountService.class);
System.out.println(accountService1 == accountService2);//单例时为true,多例为false
3.request:作用于web应用的请求范围
4.session:作用于web应用的会话范围
5.gloable-session:作用于集群环境的会话范围(全局session)如果不是集群环境,就是一个session
4.1.7Bean的生命周期
-
单例对象
出生:容器创建时对象就创建了
活着:只要容器还在,对象就活着
死亡:容器销毁,对象消亡
总结:与容器共存亡
测试:
//xml 文件 init-method为在创建的对象中定义的初始化方法,destroy-method为销毁方法 //<bean id="accountService" class="com.itblp.service.AccountServiceImpl" init-method="init" destroy-method="destroy" scope=“singleton”></bean> //AccountServiceImpl类 public class AccountServiceImpl{ public void init(){ System.out.println("初始化方法执行"); } public void destroy(){ System.out.println("销毁方法执行"); } } //测试类 public static void main(String[] args){ //创建核心容器,因为ApplicationContext接口中没有关闭容器的方法,所以此时创建容器不使用多态 ClassPathXmlApplication cpa = new ClassPathXmlApplication("bean.xml"); IAccountServiceImpl as = cpa.getBean("accountService",IAccountService.class); //手动销毁容器 cap.close(); //运行结果: //对象创建了 //初始化方法执行 //销毁方法执行 }
-
多例对象
出生:当我们需要使用对象时,spring框架为我们创建
活着:对象只要在使用过程中就一直活着
死亡:当对象长时间不用,或者没有引用指向该对象的时候,java垃圾收集器会将对象销毁
测试:当bean对象的作用范围改为protorype时,再次测试刚才的测试类,即使容器销毁,对象也不会销毁
5.依赖注入(dependency injection(DI))
5.1三类能注入的数据类型
5.1.1String类型和基本数据类型(后面)
5.1.2其他bean类型(后面)
5.1.3复杂类型/集合类型
示例
第一步:创建各种集合的成员变量
public class AccountServiceImpl2 implements IAccountService {
private String[] myStr;
private List<String> myList;
private Set<String> mySet;
private Map<String, String> myMap;
private Properties myproperties;
public void setMyStr(String[] myStr) {
this.myStr = myStr;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyproperties(Properties myproperties) {
this.myproperties = myproperties;
}
public void saveAccount() {
System.out.println(Arrays.toString(myStr));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myproperties);
}
}
第二步:配置xml文件
<!--复杂类型的注入/集合类型的注入
用于给list集合注入的标签:list array set
用于给map结构结合注入的标签:map props
总结一句话:结构相同,可以互换标签
-->
<bean id="accountService" class="com.itblp.service.impl.AccountServiceImpl2">
<property name="myStr">
<array>
<value>aaa</value>
<value>sss</value>
<value>ddd</value>
</array>
</property>
<property name="myList">
<list>
<value>aaa</value>
<value>sss</value>
<value>ddd</value>
</list>
</property>
<property name="mySet">
<set>
<value>aaa</value>
<value>sss</value>
<value>ddd</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="a">
<value>sss</value>
</entry>
<entry key="a" value="ccc">
</entry>
</map>
</property>
<property name="myproperties">
<props>
<prop key="sss">sddddd</prop>
</props>
</property>
</bean>
5.2三种注入的方式
5.2.1使用构造函数提供
示例:
第一步:创建构造函数
public class AccountServiceImpl implements IAccountService{
private String a;
private Integer b;
private Date c;
public AccountServiceImpl(String a,Integer b,Date c){
this.a=a;
this.b=b;
this.c=c;
}
}
【注意】:经常发生改变的值不用依赖注入,使用依赖注入的一般是一个类依赖于另一个类,创建这个类中的依赖类对象的时候使用依赖。
第二步:配置xml
<!--使用构造方法注入使用的是
标签:constructor-arg
出现的位置:bean标签的内部
属性:
name:根据构造方法中的参数名获取参数
index:根据构造方法中下标获取参数,从0开始
type:根据参数类型查找
value:赋值
ref:引用该配置文件中的其他bean对象,例如新创建一个Datebean对象
-->
<bean id="accountService" class="com.itblp.service.impl.AccountServiceImpl">
<constructor-arg name="a" vlaue="aaa"></constructor-arg>
<constructor-arg name="b" vlaue="bbb"></constructor-arg>
<constructor-arg name="c" ref="newDate"></constructor-arg>
</bean>
<!--通过下面的bean对象的配置,spring可以获取到这个类的全限定类名,然后创建对象,并放到ioc容器中,引用或者调用的时候可以通过唯一标识-->
<bean id="newDate" class="java.util.Date"></bean>
使用构造函数注入的优缺点:
- 优点:在获取bean对象时,注入数据时必要的操作,否则对象不会创建成功
- 缺点:改变了对象的实例化方式,使我们在用不到对象的一些数据时,也必须提供。
5.2.2使用set方法提供
示例:
第一步:创建set方法
public class AccountServiceImpl implements IAccountService {
//如果是经常变化的数据,并不适用于注入的方式,适合注入的数据为两个类之间产生了依赖关系,将这个类依赖的的那个类实行注入
private String a;
private Integer b;
private Date c;
public void setName(String a) {
this.a = a;
}
public void setAge(Integer b) {
this.b = b;
}
public void setBirthday(Date c) {
this.c = c;
}
}
第二步:配置xml文件
<!--使用set方法提供
属性:property
位置:bean标签的内部
属性:value:赋值
name:参数
ref:引用该配置文件中的其他bean对象,例如新创建一个Datebean对象
优缺点:
优点:创建对象时没有明确的限制,可以直接使用默认的构造函数
缺点:如果某个成员必须有值,则获取对象时set方法没有执行
-->
<bean id="accountService" class="com.itblp.service.impl.AccountServiceImpl">
<property name="a" value="aa"></property>
<property name="b" value="bb"></property>
<property name="c" value="newDate"></property>
</bean>
5.2.3使用注解提供(后面)
6.spring中的ioc的常用注解
6.1创建bean对象的注解
对应于xml中的bean标签
-
@Component:用于把当前类对象存入容器中
属性:value,当不写时,默认为类名,且首字母小写
-
@Service:业务层
-
@Controller:表现层
-
@Repository:持久层
以上三个注解作用和属性与component是一样的,他们三个是spring框架为我们提供明确使用三层使用的注解,使我们的三层对象更加清晰
6.2依赖注入的注解
对应于bean标签中的property标签,**细节:**在使用注解注入时,set方法就不是必须得了
-
@AutoWried:自动按照类型注入,只要ioc容器中有唯一一个bean对象类型与要注入的类型相同,就可以注入成功,如果没有匹配的就会报错,如果有多个匹配,则需要使变量名与要注入对象的id相同
-
@Qualifier:只能和autoWried配合使用,不能单独使用,用于要创建的对象有多个匹配时定义要注入类名的id
public class AccountServiceImpl implements IAccountService{ @AutoWried @Qualifier("accountDao1")//此时IAccountDao有两个实现类,指定其中一个accountDao1 IAccountDao accountDao; }
-
@Resource:可以单独使用,用于创建的对象有多个匹配时定义要注入类名的id,属性是name
public class AccountServiceImpl implements IAccountService{
@Resource(name="accountDao1")
IAccountDao accountDao;
}
- @Value:用于注入普通类型和String类型,可以使用spel表达式 spel写法 ${表达式}
集合类型对象的创建无法通过注解实现,只能通过xml
6.3bean对象作用范围的注解
对应于bean标签中的scope属性
-
@Scope:用于定义bean对象的作用范围
属性:value
值:singleton,prototype
一个创建出来时单例对象,一个是多例对象
6.4bean对象生命周期的注解
对应于bean标签中的init-method和destroy-method属性
- @PreDestroy
定义销毁方法
- @PostConstruct
定义初始化方法
7.案例:使用xml方式和注解方式实现单表的crud操作
持久层技术选择:对jdbc进行简单封装的工具类:dbutils,开源的jdbc连接池选择c3p0
第一步:导入坐标
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
</dependencies>
第二步:创建接口和实现类,并添加增删改查方法
public class AccountDaoImpl implements IAccountDao {
private QueryRunner queryRunner;
public void setQueryRunner(QueryRunner queryRunner) {
this.queryRunner = queryRunner;
}
public List<Account> findAllAccount() {
try {
return queryRunner.query("select * from account", new BeanListHandler<Account>(Account.class));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Account findAccountById(Integer accountId) {
try {
return queryRunner.query("select * from account where id=?", new BeanHandler<Account>(Account.class), accountId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void saveAccount(Account account) {
try {
queryRunner.update("insert into account(user_name,money) values (?,?)", account.getUser_name(), account.getMoney());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void updateAccount(Account account) {
try {
queryRunner.update("update account set user_name = ?,money = ? where id=?", account.getUser_name(), account.getMoney(), account.getId());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void deleteAccount(Integer accountId) {
try {
queryRunner.update("delete from account where id=?", accountId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
*第三步:创建bean.xml配置文件(重要)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置AccountService对象-->
<bean id="accountService" class="com.itblp.service.impl.AccountServiceImpl">
<property id="accountDao" ref="accountDao"></property>
</bean>
<!--配置AccountDao对象-->
<bean id="accountDao" class="com.itblp.dao.impl.AccountDao">
<property id="queryRunner" ref="queryRunner"></property>
</bean>
<!--配置QueryRunner对象 为保持数据隔离性和一致性,所以创建出的QueryRunner对象应该每一次都是新的,所以为多例-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<!--配置数据源对象-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"></property>
<property name="user" value="root"></property>
<property name="password" value="123"></property>
</bean>
</beans>
第四步:创建测试类进行测试
public class Test{
private ApplicationContext ac;
private IAccountService as;
@Before//每个方法执行前都会执行
public void init(){
ac = new ClassPathXmlApplication("bean.xml");
as = ac.getBean("accountService",IAccountService.class);
}
@Test
public void findAll(){
List<Account> accounts = accountService.findAll();
for(Account account:accounts){
System.out.println(account);
}
}
}
8.改造基于注解的ioc案例,使用纯注解的方式实现
第一步:修改配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启对注解的扫描-->
<context:component-scan base-package="com.itblp"></context:component-scan>
<!-- accountService和accountDao的配置删掉使用注解,QueryRunner的配置不删-->
第二步:删除之前的set方法,添加注解
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
}
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner queryRunner;
}
8.1如何将bean.xml文件删掉
思路:此时bean.xml中还存在的有:
-
开启对包中注解的扫描
-
QueryRunner配置
-
DataSource配置
只需要用一种方式将这几个配置代替,这个xml就可以删掉了
步骤:
第一步:创建配置类
第二步:添加相应注解
第三步:编写方法返回QueryRunner和DataSource对象
@Configuration//这是一个配置类 @ComponentScan("com.itblp")//开启对包中注解的扫描,属性有basePackages和value,作用一样,只有一个参数,可以省略 public class SpringConfig{ //返回QueryRunner对象 @Bean(name="runner")//Bean注解用于创建一个当前方法返回值类型的对象,并把它加入到ioc容器中,属性name用于指定bean的id,默认为方法名 @Scope() public QueryRunner createQueryRunner(DataSource dataSource){ return new QueryRunner(dataSource); } //返回DataSource对象 @Bean(name="dataSource") public DataSource createDataSource(){ try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setDriverClass("com.mysql.jdbc.Driver"); ds.setJdbcUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"); ds.setUser("root"); ds.setPassword("123"); return ds; } catch (Exception e) { throw new RuntimeException(e); } } }
【注意】:
1.当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写
2.当我们使用注解配置方法时,如果方法有参数,spring会去ioc容器里找有没有可用的bean对象,查找的方式与AutoWried相同,如果有唯一一个,则找到,如果没有,则查找失败,如果有多个,则通过变量名来查找key,也就是Bean的id来查找。
8.2spring的一些新注解的使用
@Import:导入其他的配置类
属性:value
当我们使用import注解之后,有import注解的类就是主配置,value中的值就是子配置类
示例:@Import(JDBCConfig.class)
@PropertySource :用于指定property文件的位置,value:指定文件的名称和路径 classpath表示后面的路径是一段类路径,有包就写包,没有就不写。
示例:@PropertySource(“classpath:jdbcConfig.properties”)
【总结】在实际开发中,自己定义的对象使用注解,引用jar包中的对象使用xml
9.spring和junit的整合
1.应用程序的入口:main方法
2.junit单元测试中,没有main方法也能执行,junit集成了一个main方法,该方法会判断当前测试类中哪些方法有@Test注解,junit就让这些有@Test注解的方法执行
3.junit不会管我们是否采用spring框架,在执行测试方法时,junit不知道我们是否使用了spring框架,所以也就不会为我们读取配置文件/配置类创建spring核心容器
4.由以上三点可知,当测试方法执行时,没有ioc容器,就算写了AutoWried,也无法实现注入
实现注入的步骤:
第一步:添加spring整合junit的jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
第二步:使用junit提供的一个注解把原有的main方法替换了,使它可以加载xml文件或者扫描包路径,换成spring提供的
@RunWith
示例:@RunWith(SpringJUnit4ClassRunner.class)
第三步:告知spring的运行器,spring和ioc的创建是基于xml还是注解,并说明位置
@ContextConfiguration
示例:@ContextConfiguration(classes = SpringConfiguration.class)
属性:location:指定xml文件的位置,加上classpath关键字表示再类路径下
classes:指定注解类所在的位置
当我们使用5.x版本的spring-testjar包时,junit的版本必须要在4.12及以上
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=SpringConfiguration.class)
public class SpringTest{
@AutoWired
private IAccountService as;
@Test
public void findAll(){
as.deleteAccount(2);
}
}
10.AOP
10.1完善account案例
添加转账功能:
第一步:IAccountService
void transfer(String sourceName,String targetName,Double money);
第二步:AccountServiceImpl
public void transfer(String sourceName,String targetName,Double name){
//1.根据名称获取转账用户
Account account1 = accountDao.findAccountByName(sourceName);
//2.根据名称获取目标账户
Account account2 = accountDao.findAccountByName(targetName);
//3.转账用户减钱
account1.setMoney(account1.getMoney() - money);
//4.目标账户加钱
account2.setMoney(account2.getMoney() + money);
//5.更新转账账户
accountDao.updateAccount(account1);
//6.更新目标账户
accountDao.updateAccount(account2);
}
第三步:编写根据名称获取账户(接口和实现类,此处省略不写了)
如果在更新转账账户前加一句
int i = 1/0;
就会造成不满足事务的一致性,转账用户的钱减少了,但是目标账户的钱却没有增加
10.2分析案例中的问题
dbutils的queryrunner对象每次都会创建一个新的,并且在执行操作的时候,都会从数据源中拿出一个链接,每执行一个方法就会创建一个连接,他们应该使用一个连接从而实现对事务的控制。
第一步:将连接与线程进行绑定需要创建新的类
1.ConnectionUtils
//连接的工具类,用于从数据源中获取一个连接,并且实现和线程的绑定
public class ConnectionUtils{
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
private DataSource dataSource;
public void SetDataSource(DataSource dataSource){
this.dataSource = dataSource;
}
//获取当前线程上的连接
public Connection getThreadConnection(){
//1.先从Threadlocal上获取
Connection conn = tl.get();
try{
//2.判断当前线程上是否有连接
if(conn == null){
//3.从数据源中获取一个连接,并且存入threadlocal中
conn = dataSource.getConnection();
tl.set(conn);
}catch(Ecxception e){
throw new RuntimeException(e);
}
//4.返回当前线程上的连接
return conn;
}
}
}
2.事务控制工具类 TranscationManager
//和事务管理相关的工具类,包含开启事务,提交事务,回滚事务,释放连接
public class TranscationManager(){
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils){
this.connectionUtils = connectionUtils;
}
//提交
public void beginTranscation(){
try{
//关闭自动提交
connectionUtils.getThreadConnection().setAutoSubmit(false);
}catch(Exception e){
e.printStackTrace();
}
}
//提交
public void commit(){
try{
connectionUtils.getThreadConnection().commit();
}catch(Exception e){
e.printStackTrace();
}
}
//回滚
public void rollback(){
try{
connectionUtils.getThreadConnection().rollback();
}catch(Exception e){
e.printStackTrace();
}
}
//释放连接
public void release(){
try{
//连接并不是真正的关了,而是还回池中,将线程还回池中的时候,是绑着一个连接的,只不过连接被关闭了
connectionUtils.getThreadConnection().close();
}catch(Exception e){
e.printStackTrace();
}
}
}
连接并不是真正的关了,而是还回池中,将线程还回池中的时候,是绑着一个连接的,只不过连接被关闭了
需要在ConnectionUtils类中有一个将线程和连接解绑的方法
//把连接和线程解绑
public void removeConnection(){
tl.remove();
}
然后在TranscationManager中释放连接之后调用线程与连接解绑的方法
//释放连接
public void release(){
try{
//释放连接
connectionUtils.getThreadConnection().close();
//连接和线程解绑
connectionUtils.removeConnection();
}catch(Exception e){
e.printStackTrace();
}
}
第二步:在业务层实现类中调用TransactionManager类
public class AccountServiceImpl implements AccountService{
private TransactionManager transactionManager;
public void setTransactionManager(TransactionManager transactionManager){
this.transactionManager = transactionManager;
}
//转账方法
public void transfer(String sourceName,String targetName,Double money){
try{
//1.开启事务
transactionManager.beginTransaction();
//2.执行方法
// 2.1根据名称查询传出账户
Account account1 = accountDao.findAccountByName(sourceName);
//2.2根据名称查询准入账户
Account account2 = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
if (account1.getMoney() < money) {
throw new RuntimeException("用户余额不足");
}
account1.setMoney(account1.getMoney() - money);
//2.4转入账户加前
account2.setMoney(account2.getMoney() + money);
//2.5更新转出账户
accountDao.updateAccount(account1);
int i = 1 / 0;
//2.6更新转入账户
accountDao.updateAccount(account2);
//3.提交事务
transactionManager.commit();
}catch(Exception e){
//4.回滚事务
transactionManager.rollback();
e.printStackTrace();
}finally{
//5.释放连接
transactionManager.release();
}
}
}
10.3回顾之前的技术——动态代理
实现动态代理
第一步:创建生产厂家接口
public interface IProducer{
//销售产品
void saleProduct(double money);
//售后
void afterService(double money);
}
第二步:创建生产厂家实现类
public class ProducerImpl implement IProducer{
public void saleProduct(double money){
System.out.println("拿到钱销售产品"+money);
}
public void afterService(double money){
System.out.println("提供售后服务,并拿到钱"+money);
}
}
第三步:创建一个消费者
public class Customer{
public static void main(String[] args){
final Producer producer = new Producer();
//动态代理:
//特点:字节码随用随创建,随用随加载
//作用:不修改源码的基础上对方法增强
//分类:1.基于接口的动态代理 2.基于子类的动态代理
//首先是基于接口的动态代理
//涉及的类:Proxy
//提供者:jdk官方
//如何创建代理对象:使用Proxy类中的newProxyInstance方法
//创建代理对象的要求:被代理类最少实现一个接口,如果没有,则不能使用
//newProxyInstance方法的参数:
//1.ClassLoader:类加载器,用于加载代理对象字节码的,写的是被代理对象的类加载器,和被代理对象使用相同的类加载器 固定
//2.Class[]:字节码数组,用于让代理对象和被代理对象有相同的方法,只要实现相同的方法,就可以有相同的方法 固定
//3.InvocationHanler:用于提供增强的代码,含义是让我们如何代理,一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类,谁用谁写
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader,producer.getClass().getInterfaces(),
//下面方法的作用:执行被代理对象的任何接口方法都会经过该方法,该方法就会有拦截的功能
//方法涉及了三个参数:
//1.proxy:代理对象的引用(没什么用)
//2.method:当前执行的方法
//3.args:当前执行方法所需的参数
//返回值:return:和被代理对象有相同的返回值
new InvocationHandler(){
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
Object returnValue = null;
//提供增强的代码
//1.获取方法执行的参数
double money = (Double) args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())){
Object returnValue = null;
//当匿名内部类访问外部成员producer时,外部成员要定义为final
//生产者只能拿80%,剩下20%归代理商
returnValue = method.invoke(producer,money*0.8);
}
return returnValue;
}
});
proxyProducer.saleProduct(10000.0);//打印:拿到钱销售产品8000
}
}
//缺点:当被代理类没有实现任何接口的时候,代理是不可以用的
//接下类就是不实现接口的子类的动态代理,但是要求有第三方jar包的支持
任何接口的动态代理的例子,计算方法运行时间
//动态代理类
public class ProxyObject implements InvocationHandler{
//被代理对象
private Object target;
//通过含参构造方法实例化
public ProxyObject(Object target){
this.target=target;
}
//重写接口中的方法
public Object invoke(Object proxy,Method method,Object[] args){
Long start = System.currentTimeMillis();
Object obj = method.invoke(target,args);
Long end = System.currentTimeMillis();
System.out.println(method.getName()+"方法消耗了"+(end-start)+"毫秒");
return obj;
}
//创建代理对象,static:使用类直接调用,使用泛型返回代理对象
public static <T> T createProxy(Object target,Class<T> targetInterface){
if(!targetInterface.isInterface()){
throw new RuntimeException("不是接口");
}
else if(!targetInterface.isAssignableFrom(target.getClass())){
throw new RuntimeException("该类没有实现该接口");
}
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader,target.getClass().getInterfaces(),new ProxyObject(target));
}
}
//测试类
public class ProxyTest{
public void main(String[] args){
IProducer proxyProducer = new ProxyObject(new ProducerImpl(),IProducer.class);
proxyProducer.saleProduct();
//销售产品拿到钱1000.0
//saleProduct方法用时:2毫秒
}
}
10.4动态代理的另一种实现方式,基于子类的代理
要求有第三方jar包的支持:
第一步:导坐标
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
第二步:此时只需要一个Producer类,不需要实现接口
public class ProducerImpl{
public void saleProduct(double money){
System.out.println("拿到钱销售产品"+money);
}
public void afterService(double money){
System.out.println("提供售后服务,并拿到钱"+money);
}
}
第三步:创建消费者类
public class Customer{
final Producer producer = new Producer();
//动态代理:
//特点:字节码随用随创建,随用随加载
//作用:不修改源码的基础上对方法增强
//分类:1.基于接口的动态代理 2.基于子类的动态代理
//首先是基于子类的动态代理
//涉及的类:Enhancer
//提供者:第三方cglib
//如何创建代理对象:使用Enhancer类中的cteate方法
//创建代理对象的要求:被代理类不能是最终类
//create方法的参数:
//1.Class:代表用于指定一个被代理对象的字节码 固定
//2.Callback:用于提供增强的代码,含义是让我们如何代理,一般都是写一个该接口的实现类,通常情况下都是匿名内部 类,但不是必须的。此接口的实现类,谁用谁写,我们写的都是该接口的字节口实现类,MethodInterceptor(方法拦截)
Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(),new MethodInterceptor(){
//该方法的作用:执行被代理对象的任何方法都会执行该方法
//proxy
//method
//args
//上面三个参数和基于接口的动态代理中invoke方法的参数是一样的
//methodProxy:当前执行方法的代理对象
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object returnValue = null;
//提供增强的代码
//1.获取方法执行的参数
double money = (Double) args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())){
Object returnValue = null;
//当匿名内部类访问外部成员producer时,外部成员要定义为final
//生产者只能拿80%,剩下20%归代理商
returnValue = method.invoke(producer,money*0.8);
}
return returnValue;
}
});
cglibProducer.saleProduct(10000.0);//打印:拿到钱销售产品8000
}
基于任何类的动态代理
/**
* 创建基于子类的代理类
*/
public class ProxyByClass {
private Object target;
public ProxyByClass(Object target) {
this.target = target;
}
public Object createProxyObject() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Long start = System.currentTimeMillis();
Object obj = methodProxy.invokeSuper(o, objects);
Long end = System.currentTimeMillis();
System.out.println(methodProxy.getSuperName() + "方法使用了" + (end - start) + "毫秒");
return obj;
}
});
Object obj = enhancer.create();
return obj;
}
}
//测试类
public class ProxyTest {
public static void main(String[] args) {
ProducerImpl producer = (ProducerImpl) new ProxyByClass(new ProducerImpl()).createProxyObject();
producer.saleProduct(10000.0);
//销售产品拿到钱10000.0
//CGLIB$saleProduct$0方法使用了0毫秒
}
}
10.5解决案例中的问题
本节为使用代理对象解决数据不一致问题
第一步:创建bean工厂,创建出代理对象,使代理对象实现的接口都经过invoke方法,从而实现事务的控制
public class BeanFactory{
private IAccountService accountService;
//使用set方法由spring为我们提供该bean对象
public void setAccountService(IAccountService accountService){
return accountService;
}
//创建代理对象
public IAccountService getAccountService(){
IAccountService proxyAccountService = (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnValue=null;
try{
//代理对象的方法在运行时都会经过该方法
//1.开启事务
transactionManager.beginTransaction();
//2.执行操作
returnValue=method.invoke(accountService,args);
//3.提交事务
transactionManager.commit();
}catch (Exception e){
//4.回滚事务
transactionManager.rollback();
throw new RuntimeException(e);
}finally {
//5.释放连接,并与线程解绑
transactionManager.release();
}
return returnValue;
}
});
return proxyAccountService;
}
}
第二步:配置xml
<!--配置bean工厂-->
<bean id="beanFactory" class="com.itblp.factory.BeanFactory">
<property name="accountService" ref="accountService"></property>
<property name="transactionManager" ref="transactionManager"></property>
</bean>
<!--配置代理对象-->
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
第三步:测试
public class AcountServiceTest{
//此时由两个类型为IAccountService类型的类:accountService 和 proxyAccountService,需要知名用那个,此时我们使用代理类
@Autowired
@Qualifier(value = "proxyAccountService")
private IAccountService accountService1;
}
10.6aop的概念
1.简介aop(aspect oriented programming) :通过预编译的方式和运行期动态代理实现程序功能的统一维护的一种技术,提高程序的可重用性和开发效率。
2.作用:在程序运行期间,在不修改源码的情况下对已有的方法进行增强。
3.优势:
- 减少重复代码
- 提高开发效率
- 维护方便
4.实现方式:使用动态代理的技术
10.7spring中的aop(掌握)
spring中的aop通过配置的方式实现创建代理对象等操作
【补充 20220224】
pom.xml中导入坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
10.8spring中的相关术语
相关术语:
- Joinpoint(连接点):业务层接口中所有的方法
- pointcut (切入点):业务层接口中被增强的方法,所有的切入点都是连接点
- Advice(通知):提供了公共代码的类
- 引介
- Target(目标对象):被代理对象
- Weaving(织入):例如一个类本不支持事务,通过创建代理对象加入了事务,加入事务支持的过程就是织入
- proxy(代理):代理对象
- Aspect(切面):是切入点(哪些方法被增强过)和通知的结合
10.9spring中基于xml的aop配置
在实现aop时,我们需要做的事有
- 编写核心业务代码
- 把公共代码提取出来,制作成通知。(重要)
- 配置文件中声明切入点和通知的关系,即切面。(重要)
spring框架做的事:
- 监控切入点方法的执行
- 使用代理机制,动态创建目标对象的代理对象
- 根据通知的类别,在对应的位置将通知的代码织入,完成完整的代码逻辑
10.9.1基于xml的aop配置
第一步:创建模拟的接口和实现类,以及通知类
//IAccountService 接口
public interface IAccountService{
//存储账户
void saveAccount();
//更新账户
void updateAccount(int id);
//删除账户
int deleteAccount();
}
//接口实现类
public class AccountServiceImpl implements IAccountService{
public void saveAccount(){
System.out.println("保存了账户");
}
public void updateAccount(int id){
System.out.pringln("更新了账户"+id);
}
public int deleteAccount(){
System.out.println("删除了账户");
}
}
//配置通知类
public class Logger{
//设置在切入点之前执行
public void printLog(){
System.out.println("开始打印日志");
}
}
第二步:配置xml
//从官网上获取aop相关的依赖
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置accountService类-->
<bean id="accountService" class="com.itblp.service.impl.AccountServiceImpl"></bean>
<!--spring中基于xml的aop的配置步骤
1.配置通知对象,把通知的bean交给spring管理
2.开启aop的配置 aop:config
3.开启切面配置 aop:aspect
id属性:给切面唯一的标志
ref属性:指定通知bean的id
4.配置通知的类型
method属性:指定logger类中哪个方法是前置通知
pointCut:用于指定切入点表达式,该表达式的含义是对该业务层中哪些方法进行增强
-->
<!--
切入点表达式扩展:
标准写法:访问修饰符 返回值类型 包名.包名.包名..类名.方法名(参数列表)
示例:public void com.itblp.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略
返回值使用通配符表示任意类型 *
包名可以使用统配符表示任意包,但是有几级包就需要写几个*.
包名可以使用..表示当前包及其子包 * *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用*实现通配
参数列表可以直接写名称;引用类型写包名.类名的形式
类型可以使用通配符,表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
全通配写法:* *..*.*(..)
-->
<!--1.配置通知对象,把通知的bean交给spring管理-->
<bean id="logger" class="com.itblp.utils.Logger"></bean>
<!--2.开启aop的配置-->
<aop:config>
<!--3.开启切面配置-->
<aop:aspect id="loggerAdvice" ref="logger">
<!--4.配置通知的类型-->
<aop:before method="printLog" pointCut="execution("public void com.itblp.service.impl.AccountServiceImpl.saveAccount()")"></aop:before>
</aop:aspect>
</aop:config>
</beans>
第三步:测试类
//之前有spring整合junit教程,此时直接使用
//将juit中的main方法替换为可以加载配置文件的类的main方法
@RunWith(SpringJUnit4ClassRunner.class)
//配置文件路径或注解的类字节码
@ContextConfiguration(location = "classpath:bean.xml")
public class AccountServiceTest{
@AutoWired
IAccountService accountService;
@Test
public void TestSave(){
accountService.saveAccount();//打印:开始打印日志 保存了账户
}
}
10.9.2通知的类型
<!--前置通知-->
<aop:before method="beforeAdvice" pointcut="execution(* *..*.*(..))"></aop:before>
<!--前置通知-->
<aop:after-returning method="beforeAdvice" pointcut="execution(* *..*.*(..))"></aop:after-returning>
<!--前置通知-->
<aop:after-throwing method="beforeAdvice" pointcut="execution(* *..*.*(..))"></aop:after-throwing>
<!--前置通知-->
<aop:after method="beforeAdvice" pointcut="execution(* *..*.*(..))"></aop:after>
简化通知的写法
<!--切入点表达式定义在aop:aspect的内部-->
<aop:aspect id="logAspect" ref="logger">
配置切入点表达式,如果在aop:aspect内部:那么只能在此切面中使用,如果定义在外面,则别的切面也可以使用
<aop:pointcut id="biao" expression="ececution(public void com.itblp.service.impl.AccountServiceImpl.saceAccount())"></aop:pointcut>
<aop:before method="beforeAdvice" pointcut-ref="biao"></aop:before>
<aop:after-returning method="afterAdvice" pointcut-ref="biao"></aop:after-returning>
</aop:aspect>
<!--切入点表达式定义在aop:aspect的外部-->
<aop:pointcut id="biaoda" expression="execution(* *..*.*(..))"></aop:pointcut>
<aop:aspect id="logerAdvice" ref="logger">
<aop:before method="beforeAdvice" pointcut-ref="biaoda"></aop:before>
<aop:after-returning method="afterAdvice" pointcut-ref="biaoda"></aop:after-returning>
</aop:aspect>
环绕通知:
/**
* 环绕通知
* 问题:当我们配置了通知方法之后切入点方法没有执行,通知方法执行了
* 分析:通过对比动态代理中的环绕通知代码,发现动态代理中的环绕通知有明确的切入点方法调用,而我们的代码中没有
* 解决问题:spring框架为我们提供了proceedingJoinPoint接口,该接口有一个方法是proceed,此方法就是明确调用切入点方法
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类
* spring中的环绕通知的另一种解释:为我们提供的一种可以在代码中手动控制增强方法何时执行的方式
*/
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
Object rtValue = null;
try {
//得到方法执行所需的参数
Object[] args = proceedingJoinPoint.getArgs();
//此打印为增强的方法,写在什么地方就是什么通知,例如当前时前置通知
System.out.println("环绕通知");
//明确调用业务层方法,也叫切入点方法
rtValue = proceedingJoinPoint.proceed(args);
return rtValue;
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}finally{
}
}
10.10基于注解的aop配置
第一步:首先配置xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context" xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--上面的依赖需要加上注解的-->
<!--配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.itblp"></context:component-scan>
<!--开启对aop注解的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
第二步:修改通知类
//创建bean对象
@Component("logger")
//声明这是一个切面类
@Aspect
public class Logger{
//配置切入点表达式
@Pointcut("execution(* com.itblp.service.impl.*.*(..))")
public void pt1(){}
//配置前置通知,注意包不能导错
@Before("pt1()")
public void beforeAdvice(){
System.out.println("前置通知");
}
@AfterReturning("pt1()")
public void afterAdvice() {
System.out.println("后置通知");
}
@AfterThrowing("pt1()")
public void exceptionAdvice() {
System.out.println("异常通知");
}
@After("pt1()")
public void finalAdvice() {
System.out.println("最终通知");
}
}
第三步:测试类
public class AccountServiceTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService = ac.getBean("accountService",IAccountService.class);
accountService.saveAccount();
}
}
打印结果有问题
前置通知
保存了账户
最终通知
后置通知
最终通知不是最后一个执行的,所以基于注解的aop在运行通知顺序上有问题,推荐使用环绕通知
//创建bean对象
@Component("logger")
//声明这是一个切面类
@Aspect
public class Logger{
//配置切入点表达式
@Pointcut("execution(* com.itblp.service.impl.*.*(..))")
public void pt1(){}
//配置环绕通知
@Around("pt1()")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
Object rtValue = null;
try {
//得到方法执行所需的参数
Object[] args = proceedingJoinPoint.getArgs();
System.out.println("前置通知");
//明确调用业务层方法,也叫切入点方法
rtValue = proceedingJoinPoint.proceed(args);
System.out.println("后置通知");
return rtValue;
} catch (Throwable throwable) {
System.out.println("异常通知");
throw new RuntimeException(throwable);
} finally {
System.out.println("最终通知");
}
}
}
这次的运行顺序就是正确的
前置通知
保存了账户
后置通知
最终通知
也可以将xml文件删除,新建一个配置类,在前面有示例。
//声明这是一个配置类
@Configuration
//开启扫描包
@ComponentScan("com.itblp")
//开启对aop注解的支持
@EnableAspectJAutoProxy
public class SpringConfig {
}
11.spring中的jdbcTemplate对象(会用)
11.1jdbcTemplate的作用
用于和数据库交互的,实现对表的crud操作
11.2如何创建该对象
1.最原始的方法:
public class JdbcTemplateDemo1 {
public static void main(String[] args) {
//准备数据源,spring的内部数据源
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf-8");
ds.setUsername("root");
ds.setPassword("123");
//1.创建jdbctemplate对象
JdbcTemplate jt = new JdbcTemplate();
// 给jt设置数据源
jt.setDataSource(ds);
//2.执行操作
jt.execute("insert into account(user_name,money) values('张三丰',2345.0)");
}
}
2.使用xml配置的方式
//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="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"></property>
<property name="username" value="root"></property>
<property name="password" value="123"></property>
</bean>
//测试类
public class JdbcTemplateDemo2 {
public static void main(String[] args) {
//获取核心容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//调用方法
JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
jt.execute("insert into account(user_name,money) values('lisi',7000.8)");
}
}
11.3对象中的常用方法
查询时有两种方式,一种是实现RowMapper接口,实现对Account对象的封装,另一种是直接使用spring自带的new BeanPropertyRowMapper(Account.class)。
public class JdbcTemplateDemo3 {
public static void main(String[] args) {
//获取核心容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//调用方法
JdbcTemplate jt = ac.getBean("jdbcTemplate", JdbcTemplate.class);
//插入数据(含参)
//jt.update("insert into account(user_name,money) values(?,?)","wang",233333.0);
//更新数据
//jt.update("update account set user_name = ? , money = ? where id = ?","etst",23232.7,3 );
//删除数据
//jt.update("delete from account where id = ?",3);
//查询数据
// String name = "%王%";
// List<Account> accountList = jt.query("select id,user_name as userName,money from account where user_name like ? ", new AccountRowMapper(), name);
// List<Account> accountList = jt.query("select id,user_name as userName,money from account where user_name like ? ",new BeanPropertyRowMapper<Account>(Account.class), name);
// for (Account account : accountList) {
// System.out.println(account);
// }
//查询一个
// List<Account> account = jt.query("select id,user_name as userName,money from account where id = ?", new AccountRowMapper(), 5);
// System.out.println(account.isEmpty()?"没有内容":account.get(0));
//查询返回一行一列
Long count=jt.queryForObject("select count(*) from account where money > ?",Long.class,2000.7);
System.out.println(count);
}
}
//实现对account结果的封装
class AccountRowMapper implements RowMapper<Account> {
/**
* 将结果封装到Account对象中,spring会将Account对象添加至集合中
*
* @param resultSet
* @param i
* @return
* @throws SQLException
*/
public Account mapRow(ResultSet resultSet, int i) throws SQLException {
Account account = new Account();
account.setId(resultSet.getInt("id"));
account.setMoney(resultSet.getDouble("money"));
account.setUserName(resultSet.getString("userName"));
return account;
}
}
11.4jdbcTemplate在dao层的使用(重点)
描述:如果有多个dao都需要使用jdbcTemplate对象,那么我们可以将jdbcTemplate对象抽取出来
第一步:创建公共访问类
public class Template{
private JdbcTemplate jdbcTemplate;
private DataSource dataSource;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setDataSource(DataSource dataSource) {
//判断如果不存在jdbcTemplate对象,则创建一个
if (jdbcTemplate == null) {
jdbcTemplate = createJdbcTemplate(dataSource);
}
}
public JdbcTemplate createJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
第二步:在dao层调用,使用继承的方式,调用时采用super
public class AccountDaoImpl extends Template implements IAccountDao {
public Account findAccountById(Integer id) {
List<Account> accounts = super.getJdbcTemplate().query("select id,user_name as userName,money from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), id);
return accounts.isEmpty() ? null : accounts.get(0);
}
public List<Account> findByName(String name){
return super.getJdbcTemplate().query("select id,user_name as userName,money from account where user_name like ?",new BeanPropertyRowMapper<Account>(Account.class),name);
}
}
第三步:配置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="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"></property>
<property name="username" value="root"></property>
<property name="password" value="123"></property>
</bean>
<!-- 配置accountDao,只需要配置datasource,因为它会为我们创建jdbcTemplate对象-->
<bean id="accountDao" class="com.itblp.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
12.spring基于aop 的事务控制
- 事务处理位于业务层
- 需要的jar包是spring-tx.jar
- spring的事务控制是基于aop的,既可以使用编程实现,也可以使用配置实现
PlatformTranscationManager接口 中有两个方法
- commit
- rollback
TranscationDefinition接口,事务的定义信息
- 获取事务对象名称(String getName())
- 获取事务隔离级别(int getIsolationLevel):四个 默认使用数据库的隔离级别
- 获取事务传播行为(int getPropagationBehavior) 查询没有,增删改有
- 获取事务超时时间(int getTimeOut):多长时间过期
- 获取事务是否只读(boolean idReadOnly):查询方法只读
TrausactionStatus接口,事务状态
13.spring中的事务控制
13.1基于xml的
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--其他bean配置--->
<!-- spring中基于xml的声明式事务配置步骤
1.配置事务管理器
2.配置事务的通知,此时需要导入事务的约束(tx的名称空间和约束,同时也需要aop的),
还需要使用tx:advice标签配置事务通知
属性:id:给事务通知一个唯一标志
transaction-manager:给事务通知提供一个事务管理器的引用
3.配置aop中的通用切入点表达式
4.建立事务通知和切入点表达式的对应关系
5.配置事务的属性:在事务的通知tx:advice标签的内部 我们有一个通知,通知中有提交和回滚方法,对service方法进行增强,需要建立对应关系
-->
<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事务的属性
isolation:用于指定事务的隔离级别,默认值是DEFAULT,表示使用数据库的默认隔离级别
propagation:用于指定事务的传播行为,默认值是REQUIRED,表示一定会有事务(增删改的选择,查询方法可以选择supports)
read-only:用于指定事务是否只读,只有查询方法才能设置为true,默认值为false表示读写
timeout:用于指定事务的超时时间,默认值是永不超时,如果指定了数值,以秒为单位
rollback-for:用于指定一个异常,当产生该异常时事务回滚,产生其他异常时,事务不回滚,没有默认值,表示任何异常都回滚
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚,没有默认值,表示任何异常都回滚
-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<!--查询方法都以find开头,优先级高于上面的*-->
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!-- 配置aop-->
<aop:config>
<!-- 配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.itblp.service.impl.*.*(..))"/>
<!-- 建立切入点表达式和通知的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
13.2基于注解和xml的
bean.xml最重要
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.itblp"></context:component-scan>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"></property>
<property name="username" value="root"></property>
<property name="password" value="123"></property>
</bean>
<!-- spring中基于注解的声明式事务配置步骤
1.配置事务管理器
2.开启spring对注解事务的支持,tx:annotationDriver
-->
<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!-- 配置切入点表达式和通知的关系-->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.itblp.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
13.3纯注解
AccountServiceImpl.class
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
//每个方法都需要配置,事务只有在查询的时候可有可无,并且只读为true
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public Account findAccountByName(String name) {
return accountDao.findAccountByName(name);
}
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void transfer(String sourceName, String targetName, Double money) {
//1.根据名称获取转钱的人
Account source = accountDao.findAccountByName(sourceName);
//2.根据名称获取被转钱的人
Account target = accountDao.findAccountByName(targetName);
//3.转钱的人钱减少
source.setMoney(source.getMoney() - money);
//4.被转钱的人钱增加
target.setMoney(target.getMoney() + money);
//5.更新转钱的人
accountDao.updateAccount(source);
//int i = 1 / 0;
//6.更新被转钱的人
accountDao.updateAccount(target);
}
}
主配置类:
/**
* 配置类
*/
@Configuration
@ComponentScan("com.itblp")
//导入properties文件
@PropertySource("jdbcConfig.properties")
//开启对注解的支持
@EnableTransactionManagement
@Import(value = {JdbcConfig.class, TransactionManagerConfig.class})
public class SpringConfig {
}
数据源配置类
/**
* 数据源配置类
*/
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
//配置jdbcTemplate对象,并添加至spring容器
@Bean(name = "jdbcTemplate")
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
//配置数据源,并把数据源对象加入到spring容器中
@Bean(name = "dataSource")
public DriverManagerDataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
事务管理器配置类
/**
* 事务管理器配置类
*/
public class TransactionManagerConfig {
//获取事务管理器对象,并将其添加至spring容器中
@Bean(name = "dataSourceTractionManager")
public DataSourceTransactionManager getTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
14.spring5的新特性
基于jdk1.8及以上
- 日志方面的记录,spring-jcl(comments logging)
- 核心容器
- 响应式编程风格(响应式非阻塞)
- junit5的支持
- 依赖类库的支持