学习网址:https://www.bilibili.com/video/av21335209?t=1138
学习代码1:https://github.com/Shenpotato/springdemo(配置Bean)
学习代码2:https://github.com/Shenpotato/springaop(AOP)
学习代码3:https://github.com/Shenpotato/springjdbc(连接JDBC与事务代码)
其中标题后的括号对应项目代码的包名
一、Spring概述
为开源框架,为了解决企业应用开发的复杂性而创建的,是一个轻量级的控制反转(IOC)和面向切面的(AOP)的容器框架
控制反转:将控制权交出去,只获得最终的结果
面向切面:实现业务和技术的分离进行内聚性开发
包含并管理应用对象的配置和生命周期,可以理解为容器;也可将简单的组建配置,组合成复杂的应用,可理解为框架。
1.Spring作用
- 容器
- 提供了对多种技术的支持:JMS、MQ支持、UnitTest
- AOP(事务管理,日志)
- 提供了众多方便应用的辅助类(JDBC Template等)
- 对主流应用框架(Hibernate等)提供了良好的支持
2.适用范围
- 构建企业应用(Spring MVC+Spring+Hibernate/ibatis)
- 单独使用Bean容器
- 单独使用AOP进行切面处理(事务,日志)
- 其他的Spring功能,如:对消息的支持,对象和html映射的支持等
- 在互联网中的应用...
二、框架概述
1.框架特点
- 半成品
- 封装了特定的处理流程和控制逻辑
- 成熟的、不断升级改进的软件
2.框架和类库的区别
框架封装了逻辑,是高内聚的,并专注于某一领域;类库是松散的工具组合,更为通用
3.使用框架的原因
- 软件系统日趋复杂
- 重用度高,开发效率和质量提高
- 软件设计人员要专注对领域的了解,使需求分析更充分
- 易于上手、快速解决问题
三、IOC与DI
1.IOC与DI的定义
(1)IOC(Inversion of Control)
控制权的转移,应用程序本身不负责依赖对象的创建和维护,而是由外部容器负责创建和维护。
思想是反转资源获取的方向,应用IOC后,容器主动地将资源推送给它所管理的组件,组件所要做的仅仅是选择一种合适的方式来接受资源,这种行为也被称为查找的被动形式。
tips:接口是一种将实现方法封装,仅由输入产生输出结果的方式。对应java中,接口即声明。在java8后,接口也可以拥有方法体。
(2)DI(Dependency Injetion)
IOC的另一种表述方式:即组件以一些预先定义好的方式(如Setter方式)接受来自如容器的资源注入。
2.IOC的发展史
假设有需求:生成html或者pdf格式的不同类型的报表,实现类分别为PdfReportGenerator()和HtmlReportGenerator(),继承于ReportGenerator接口,存在一个报表服务类ReportService()要生成一个pdf的报表。
(1)分离接口与实现(耦合度最高)
需要知道服务类如何调用ReportGenerator接口类,亦需要知道接口类如何调用实现类PdfReportGenerator()和HtmlReportGenerator()。(需要3条线)
(2)采用工厂设计模式
服务类只需指向接口ReportGenerator和ReportGeneratorFactory代理工厂,工厂负责生成接口的实现类。(需要两条线)
(3)采用反转控制
服务类只需指向接口,由容器将实现类直接注入给服务类。(只需要一条线)
四、配置Bean
1.总述
(1)配置形式:基于XML文件,基于注解的方式
(2)配置方法:通过全类名(反射)、通过工厂方法(静态工厂方法&实例工厂方法)、FactoryBean()
(3)依赖注入方式:属性注入,构造器注入,工厂方式注入
4-10点的内容都通过基于xml文件和全类名反射进行阐述配置中的注意点
11点通过工厂方法进行对Bean进行配置
12点通过基于注解的方式对Bean进行配置
2.Bean容器初始化
在SpringIOC容器读取Bean配置创建Bean实例之前,必须对它进行实例化。只有在实例化之后,才可以从IOC容器中获取Bean实例并使用。
(1)基础:两个包 a.org.springframework.beans b.org.springframework.context
(2)IOC容器实现:
a.BeanFactory提供了配置结构和基本功能,加载并初始化Bean,面向Spring本身,为底层
b.ApplicationContext(通常使用),面向Spring框架开发者,保存了Bean对象并在Spring中被广泛使用:本地文件,ClassPath,Web应用中依赖servlet或Listener。
3.配置形式
(1)通过xml配置Bean
<bean id = "helloWorld" class="com.shenpotato.springdemo.HelloWorld">
<property name="name" value="shenpotato"></property>
</bean>
id:Bean的名称,在IOC容器中必须是唯一的,若id没有指定,Spring自动将授权限定性类名作为Bean的名字
class:class:bean的全类名,通过反射的方式在IOC容器中创建Bean,所以要求Bean中必须有无参构造函数
(2)通过注解配置Bean
4.依赖注入(springdemo)
(1)依赖注入的两种注入方式
a.属性注入
通过Setter方法进行注入,是开发中最常用的方式,需要空的构造器
即在Dao层内定义set方法,在xml文件中使用<property name="xxx" value = "xxx"></property>来配置
b.构造器注入
通过在Dao层内定义构造函数,在xml文件中使用<constructor-arg value="xxx" type = "xxx" index ="xxx"></constructor-arg>
来进行属性注入,index和type分别表示参数的类型以及位置,可用于区分使用不同的重载构造器。
如下代码所示:
<bean id = "car" class="com.shenpotato.springdemo.Car">
<constructor-arg value="Audi" type="java.lang.String"></constructor-arg>
<constructor-arg type="java.lang.String">
<!--如果字面值包含特殊字符,可用<![CDATA[]]>包括起来-->
<!--属性值可以使用value子节点进行配置-->
<value><![CDATA[<ShangHai^>]]></value>
</constructor-arg>
<constructor-arg value="300000" type="double"></constructor-arg>
<constructor-arg type="int">
<value>250</value>
</constructor-arg>
</bean>
<!--使用构造器注入属性值时可指定参数的位置和参数的类型用以区分重载构造器-->
<bean id = "car2" class="com.shenpotato.springdemo.Car">
<constructor-arg value="BMW" type="java.lang.String"></constructor-arg>
<constructor-arg value="ChongQing" type="java.lang.String"></constructor-arg>
<constructor-arg value="300" index="3" ></constructor-arg>
<constructor-arg value="500000" index="2"></constructor-arg>
</bean>
Tips:value属性可作为子节点;若属性值包含特殊字符,可用<![CDATA[]]>将其包裹。
(2)引用注入
当类A中包含类B的定义时,需要通过引用注入的方式,将B的Bean的值注入A的Bean中的某个属性。共有三种不同方式,分为外部Bean通过ref属性或子节点注入,内部Bean自己定义注入,如下图代码所示
<!--引用的不同方式-->
<!--外部Bean的引用,通过ref属性或ref子节点-->
<!-- 通过ref属性
<property name="car" ref="car"></property>
-->
<!--通过ref子节点
<property name="car">
<ref bean="car2"></ref>
</property>
-->
<!--内部Bean引用-->
<property name="car">
<bean class="com.shenpotato.springdemo.Car">
<constructor-arg value="Ford"></constructor-arg>
<constructor-arg value="XiaMen"></constructor-arg>
<constructor-arg value="100000"></constructor-arg>
<constructor-arg value="220"></constructor-arg>
</bean>
</property>
(3)集合属性
当类中某个属性的数量大于一时,在Spring中可以使用内置的xml标签如<list>,<set>,<map>来与类中定义的集合相对应的,以达成配置集合属性的目的。
a.java.util.List
配置List类型的属性,需要指定<list>标签,在标签里包含一些元素,这些标签可以通过<value>指定简单的常量值,也可以通过<ref>指定对其他Bean的引用,也可通过<bean>指定内置Bean定义,也可以通过<null/>指定空元素,也可以内嵌其他集合。
Tips:数组的定义与List一样,都是使用<list>; 配置java.util.Set使用<set>标签。
<bean id ="person2" class="com.shenpotato.springdemo.Person">
<property name="name" value="Tom"></property>
<property name="age" value="21"></property>
<property name="car" >
<!--使用List节点为List属性赋值-->
<list>
<ref bean="car"></ref>
<ref bean="car2"></ref>
</list>
</property>
</bean>
b.java.util.Map
使用Map的<entry>子节点配置成员变量
<bean id = "newPerson" class="com.shenpotato.springdemo.NewPerson">
<constructor-arg value="Joe" index="0"></constructor-arg>
<constructor-arg value="22" index="1"></constructor-arg>
<constructor-arg index="2">
<map>
<entry key="FirstCar" value-ref="car"></entry>
<entry key="SecondCar" value-ref="car2"></entry>
</map>
</constructor-arg>
</bean>
c.java.util.Properties
<bean id ="dataSource" class="com.shenpotato.springdemo.DataSource">
<property name="properties">
<props>
<prop key="username">Shentaotao</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>
Important:使用util进行单例集合的配置,供多个Bean使用
<util:list id ="cars">
<ref bean ="car"/>
<ref bean="car2"/>
</util:list>
(4)使用P命名空间对配置进行简化
<!--通过p命名空间简化Bean的配置-->
<bean id ="person4" class="com.shenpotato.springdemo.Person" p:name="Mike"
p:age="26" p:car-ref="cars"></bean>
5.自动装配(springautowire)
SpringIOC容器可以自动装配Bean,只需在<bean>的autowire属性里指定自动装配的模式
<bean id = "address" class="com.shenpotato.springautowire.Address"
p:city="BeiJing" p:street="HuiLongGuan"></bean>
<bean id="car10" class="com.shenpotato.springautowire.Car"
p:brand="Audi" p:price="300000"></bean>
<!--
可以使用autowire属性指定自动装配的方式,
byName根据bean的名字和当前bean的setter风格的属性名进行自动装配,若无匹配的则不装配赋null
byType根据bean的类型和当前bean的属性的类型进行自动装配,若IOC容器中有1个以上的类型匹配的Bean,则抛出异常
-->
<bean id ="person" class="com.shenpotato.springautowire.Person"
p:name="Shenpotato" autowire="byType"></bean>
自动装配的缺点:
不够灵活,设置自动装配后会装配Bean的所有属性,并且只能选择byName或者byType一种装配方式
6.Bean之间的关系(springrelation)
(1)继承
Spring中被继承的Bean成为父Bean,继承此父Bean的成为子Bean。继承配置包括属性配置,但不包括autowire,abstract等。
父Bean中的class忽略,此时,父Bean一定为抽象Bean,abstract的属性值为true。
<!--
bean的abstract属性为true,bean不能被实例化,只能被继承
若一个bean的class属性没有被指定,该bean一定为一个抽象bean
-->
<bean id = "address" class="com.shenpotato.springautowire.Address"
p:city="BeiJing" p:street="WuDaoKou" abstract="true"></bean>
<!--bean配置的继承,指定继承那个bean的配置,address为父bean-->
<bean id = "address2" parent="address" p:street="DaZhongSi" ></bean>
</beans>
(2)依赖
使用deponds-on属性,来设置在配置此Bean前需要配置的Bean。若有多个前置依赖,用逗号或空格隔开。
<!-- 在配置person时,必须有一个关联的car,即Person这个bean依赖于Car这个Bean-->
<bean id ="car" class="com.shenpotato.springautowire.Car"
p:price="400000" p:brand="BWM"></bean>
<bean id ="person" class="com.shenpotato.springautowire.Person"
p:name="Shenpotato" p:address-ref="address2" depends-on="car"></bean>
7.Bean的作用域(springscope)
主要是单例singleton和原型prototype的区别,如下所示
xml配置文件:
<!--
singleton:默认值,容器初始化时创建bean实例,在整个生命周期内只创建这一个bean,单例的
prototype:原型的,容器初始化时不创建bean的实例,而在每次请求时都创建一个新的bean实例,并返回
-->
<bean id ="singletoncar" class="com.shenpotato.springscope.Car"
p:brand = "BMW" p:price="5000000" scope="singleton">
</bean>
<bean id ="prototypecar" class="com.shenpotato.springscope.Car"
p:brand="Audi" p:price="3000000" scope="prototype">
</bean>
</beans>
生成结果:
singleton在定义容器时就创建,因此生成了无参构造器test,car1与car2相等;prototype在进行实例化的时候创建,car3不等于car4。
8.使用外部属性文件(springproperties)
由于xml文件过于庞大,较难进行修改,因此我们将需要在xml文件中配置,但又有可能发生修改的属性值以外部属性文件的形式存储,在xml中通过导入外部属性文件进行配置。最常见的场景:数据库的配置
在外部创建peoperties文件,其中包含对需要的属性值得定义。在xml文件中引用properties文件,使用${variablename}来使用。
<!-- 导入属性文件-->
<context:property-placeholder location="db.properties"/>
<bean id = "datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
<property name="driverClass" value="${driverclass}"></property>
<property name="jdbcUrl" value="${jdbcurl}"></property>
</bean>
9.SpEL语言的应用(com.shenpotato.springspel)
SpEL是一个支持运行时查询和操作对象图的表达式语言,使用在value属性下
语法:使用#{...}
作用 ;
(1)表示字面量,包括基本数据类型和String,String需要用""或''将内容括号起来
(2)引用其他对象:#{beanname},其中beanname表示引用的对象的id
(3)引用其他对象的属性:#{beanname.property},其中beanname.property表示引用对象的某一属性
(4)调用静态方法和静态属性:#{T(ClassName).Property},调用某一个静态属性.
<bean id ="address" class="com.shenpotato.springspel.Address">
<!-- 使用spel为属性附一个字面值包括基本数据类型和String型-->
<property name="city" value="#{'BeiJing'}"></property>
<property name="street" value="WuDaoKou"></property>
</bean>
<bean id = "car" class="com.shenpotato.springspel.Car">
<property name="brand" value="Audi"></property>
<property name="price" value="300000"></property>
<!--使用spel引用类的静态属性-->
<property name="tyrepremiter" value="#{T(java.lang.Math).PI*100}"></property>
</bean>
<bean id = "person" class="com.shenpotato.springspel.Person">
<!--使用spel来应用其他的Bean-->
<property name="car" value="#{car}"></property>
<!--使用spel来应用其他Bean的属性-->
<property name="city" value="#{address.city}"></property>
<!--在spel中使用运算符-->
<property name="info" value="#{car.price>300000?'金领':'白领'}"></property>
<property name="name" value="Shenpotato"></property>
</bean>
10.IOC容器中Bean的生命周期(springlifecycle)
SpringIOC容器可以管理Bean的生命周期,执行过程如下:
(1)通过构造器或工厂方法创建Bean实例:控制台显示carconstructor test,下同
(2)为Bean的属性设置值和对其他Bean的引用:carsetmethod test
(3)将Bean实例传递给Bean后置处理器的postProcessBeforeInitialization()方法(代码中通过MyBeanPostProcessor实现):postProcessBeforeInitialization:Car{brand='Audi'},car
(4)调用Bean的初始方法(通过init-method属性为Bean指定初始化方法):init test
(5)将Bean实例传递给Bean后置处理器的postProcessAfterInitialization()方法(代码中通过MyBeanPostProcessor实现):
postProcessAfterInitialization:Car{brand='Audi'},car carconstructor test
carsetmethod test
Car{brand='Ford'}
(6)Bean的使用
(7)当容器关闭时(applicationContext.close()),调用Bean的销毁方法(通过destroy-method属性为Bean指定销毁方法):destory test
11.通过工厂方式配置Bean(springbeanfactory)
(1)通过静态工厂方法配置Bean
什么是JAVA的静态工厂方法:https://www.cnblogs.com/dyj-blog/p/8867028.html
静态工厂方法是一个获取实例的方法,用一个静态方法来对外提供自身实例的方法,保存在工厂类中。
public class StaticCarFactory {
public static Map<String,Car> cars = new HashMap<String,Car>();
static{
cars.put("audi",new Car("audi",300000));
cars.put("ford",new Car("ford",200000));
}
//静态工厂方法
public static Car getCar(String name){
return cars.get(name);
}
}
xml文件:因为是生成Car的实例,因此调用工厂包中暴露的getCar()方法作为factory-method,自然,class引用的是工厂类。
<!--通过静态工厂方法配置Bean,注意不是配置静态工厂方法实例,而是Bean(Car)实例-->
<!--
class属性:指定静态工厂方法的全类名
factory-method:指向静态工厂方法的名字
constructor-arg:如果工厂方法需要传入参数,则使用constructor-arg来配置参数
-->
<bean id = "car1" class="com.shenpotato.springbeanfactory.StaticCarFactory"
factory-method="getCar">
<constructor-arg value="audi"></constructor-arg>
</bean>
(2)工厂实例化方法创建Bean
相比于静态工厂方法,工厂实例化方法将Car在工厂类构造函数中实例化
public class InstanceCarFactory {
private Map<String,Car> cars =null;
public InstanceCarFactory(){
cars = new HashMap<String,Car>();
cars.put("audi",new Car("audi",300000));
cars.put("ford",new Car("ford",200000));
}
public Car getCar(String name){
return cars.get(name);
}
}
xml文件:先配置了一个工厂实例,再通过工厂实例配置Car的实例
<!--配置工厂实例-->
<bean id = "carFactory" class="com.shenpotato.springbeanfactory.InstanceCarFactory"></bean>
<!--通过实例工厂方法来配置bean-->
<!--
factory-bean属性:指向实例工厂方法的bean
-->
<bean id ="car2" factory-bean="carFactory" factory-method="getCar">
<constructor-arg value="ford"></constructor-arg>
</bean>
关于静态工厂方式和工厂模式的比较:https://blog.csdn.net/quinnnorris/article/details/66977156
12.基于注解方式配置(springannotation)
(1)理解组件扫描:Spring从classpath下自动扫描,侦测和实例化具有特定注解的组件,特定组件包括:
- @Component:基本注释,标识一个受Spring管理的组件
- @Repository:标识持久层组件
- @Service:标识服务层组件
- @Controller:标识表现层组件
对于扫描到的组件,Spring默认使用非限定类第一个字母为小写的名称作为bean的名称。如类名为Car,则默认bean为car。同时也可以在@component(“carcarcar”)的形式来手动标识bean名称。
(2)在xml文件中的配置
核心节点为<context:component-scan></context:component-scan>。使用base-package属性来指定扫描的包以及其子包,resource-pattern来指定扫描包是在具体那一个注解之下;
<context:exclude-filter>为<context:component-scan>的子节点,用于排除某些bean。其中的type属性来指定是通过annotation注解还是assignable名称来排除;<context:include-filter>为<context:component-scan>的子节点,用于包含某些bean,注意在使用时要将<context:component-scan>的use-defluat-filters属性值置为false。
<!--指定springIOC容器扫描的包-->
<!--可以通过resource-patter指定扫描的资源-->
<!--
<context:component-scan
base-package="com.shenpotato.springannotation"
resource-pattern="repository/*.class">
</context:component-scan>
-->
<!--context:exclude-filter子节点指定排除那些指定表达式组件-->
<!--
<context:component-scan
base-package="com.shenpotato.springannotation">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
-->
<!-- context:include-filter子节点指定包含哪些表达式的组件,该子节点需要user-default-filters配合使用-->
<context:component-scan
base-package="com.shenpotato.springannotation"
use-default-filters="false">
<context:include-filter type="assignable"
expression="com.shenpotato.springannotation.repository.UserRepositoryImpl"/>
</context:component-scan>
(3)使用autowire来自动装配Bean
在程序编写时,常有类之间的互相引用,比如控制层引用服务层,服务层又引用持久层。所以,使用@Autowire的方式可满足在以注解为配置bean方式下的引用问题。
可在类内定义上,set方法上对编写@Autowire。
当一个接口有多个实现类时,可以通过:为实现类上的bean附上姓名,在引用类处指定@Qualifier(“xxx”)注解,其中xxx代表引用类的bean名。@Qualifier可以写在引用类上方,也可以写在set方法上方,也可以写在set方法路径下。
@Autowired
//@Qualifier("userRepositoryImpl")
private UserRepository userRepository;
//@Qualifier("userRepositoryImpl")
public void setUserRepository( @Qualifier("userRepositoryImpl") UserRepository userRepository) {
this.userRepository = userRepository;
}
13.泛型依赖注入(springgeneric)
spring4的新特性中包含:如果泛型BaseService<T>和BaseRepository<T>存在引用关系,UserService和UserRepository两个继承类之间同样存在引用关系。要实现这样的结果,需要在BaseService<T>引用BaseRepository时加上@Autowire自动装配。代码如下:
public class BaseService<T> {
@Autowired
protected BaseRepository<T> repository;
public void add(){
System.out.println("add....");
System.out.println(repository);
}
}
这样UserService和UserRepository两个继承类之间的引用关系就建立起来了。
五、面向切面编程AOP
在进行业务代码编写的时候,当非业务性需求(日志和验证等)加入,会使原先业务代码膨胀导致代码混乱;在编写日志需求时,若日志需求发生变化,将要修改多个模块,这是代码分散带来的影响,我们使用AOP避免这种影响。
1,动态代理简述(springaop.proxydemo)
AOP的本质实则为动态代理,在代码中我们使用这五个类来对比使用动态代理与不使用动态代理的区别。(动态代理为设计模式,其原理见此博客:https://blog.csdn.net/szt292069892/article/details/84454069)。
Main
ArithmeticCalculator
ArithmeticCalculatorImpl
ArithmeticCalculatorLoggingImpl
ArithmeticCalculatorLoggingProxy
2.AOP简述
AOP(Aspect-Oriented Programming,面向切面编程),是对传统OOP(Object-Oriented Programming,面向对象编程)的补充。其主要编程对象是切面,切面中放置的就是横切关注点的方法。
(1)AOP的好处
- 每个事物逻辑位于一个位置,代码不分散,便于维护升级
- 业务代码简洁,只包含核心业务代码
(2)项目代码AOP化图
- 通知:切面所需要完成的任务,如验证参数,前置日志,后置日志
- 目标:被通知的对象,为业务逻辑
- 代理:向目标对象应用通知之后创建的对象
- 连接点:程序执行的某个特定位置,是一个具体的物理存在。由两个信息确定:方法表示程序的执行点,相对点表示执行的方位,如方法执行前或后
- 切点:AOP通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点为查询条件。切点通过org.springframwork.aop.Poincut接口进行描述,使用类和方法作为连接点的查询条件
3.基于注解的AOP案例(springaop.impl)
(1)前期准备
同样是采取maven来管理项目,首先向pom.xml文件中导入以下依赖
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjtools -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.6.RELEASE</version>
</dependency>
</dependencies>
以实现导入以下jar包
Maven: aopalliance:aopalliance:1.0
Maven: org.aspectj:aspectjrt:1.8.9
Maven: org.aspectj:aspectjtools:1.8.9
Maven: org.springframework:spring-aop:4.2.6.RELEASE
Maven: org.springframework:spring-beans:4.2.6.RELEASE
Maven: org.springframework:spring-context:4.2.6.RELEASE
Maven: org.springframework:spring-core:4.2.6.RELEASE
Maven: org.springframework:spring-expression:4.2.6.RELEASE
(2)具体实现
A.把横切关注点的代码抽象到切面的类中,即定义一个切面类(LoggingAspect)以实现日志切面
- 切面首先是一个IOC中的bean,需要加入@Component注解
- 切面还需要通过加入@Aspect注解声明其为切面
- 在切面类的方法上方添加告诉程序何时执行该方法的注解@Before,@After等
LoggingAspect.java:
//将这个类声明为切面:需要把该类放入IOC容器中,再声明为切面
@Aspect
@Component
public class LoggingAspect {
//声明该方法是一个前置通知:在目标方法开始执行之前
@Before("execution(public int com.shenpotato.springaop.impl.ArithmeticCalculatorImpl.*())")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object>args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" begins"+args);
}
}
其中JointPoint是连接点,可访问连接点处的链接细节,包括方法名,传递参数的信息。
Tips:a. public int可用*代替;b. 可以将方法中传递参数可用.*(..)代替
B.配置文件applicationContext.xml
为使配置文件能够识别切面,产生动态代理,还需要在配置文件中加入:<aop:aspectj-autoproxy>节点,如若代理对象定义在实现类,则需将proxy-target-class的属性值置true
<!--配置自动扫描的包-->
<context:component-scan base-package="com.shenpotato.springaop.impl"></context:component-scan>
<!--使AspectJ注解起作用:自动为匹配的类生成代理对象-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
(3)通知分类
根据要进行通知的位置不同,通过以下不同的注解实现,解释代码详见impl包中的LoggingAspect类
- 前置通知(@Before)
- 后置通知(@After)
- 异常通知(@AfterThrowing)
- 返回通知(@AfterReturning)
- 环绕通知(@Around):模拟动态代理,可实现在其中实现以上四种通知的位置
(4)切面优先级
通过在切面类上声明@Order(1)注解,来实现先执行哪个切面的要求,值越小优先级越高。详见ValidateAspect类。
/**
* Created by Shen_potato on 2018/11/26.
*/
@Order(1) //使用order指定切面优先级,值越小优先级越高
@Aspect
@Component
public class ValidateAspect {
@Before("execution(public int com.shenpotato.springaop.impl.ArithmeticCalculatorImpl.*(..))")
public void validateArgs(JoinPoint joinPoint){
System.out.println("Validate"+ Arrays.asList(joinPoint.getArgs()));
}
}
(5)重用切面表达式
//重用切面表达式
//定义一个方法,用于声明切入点表达式,该方法中不添加其他代码,通过@Pointcut注解实现
//后面的其他通知用方法名实现
@Pointcut("execution(public int com.shenpotato.springaop.impl.ArithmeticCalculatorImpl.*(..))")
public void declareLoggingAspectExpression(){
}
//声明该方法是一个前置通知:在目标方法开始执行之前
@Before("declareLoggingAspectExpression()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object>args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" begins"+args);
}
4.基于配置文件的Aop案例(SpringAop_xml)
applicationContext_xml:
<!--配置bean-->
<bean id ="arithmeticCalculatorImpl" class="com.shenpotato.springaop_xml.ArithmeticCalculatorImpl">
</bean>
<!--配置切面的bean-->
<bean id = "loggingAspect"
class="com.shenpotato.springaop_xml.LoggingAspect"></bean>
<bean id ="validateAspect"
class="com.shenpotato.springaop_xml.ValidateAspect"></bean>
<!--配置AOP-->
<aop:config proxy-target-class="true">
<aop:pointcut id="pointcut" expression="execution(public int com.shenpotato.springaop_xml.ArithmeticCalculatorImpl.*(..))"/>
<aop:aspect ref="validateAspect" order="1">
<aop:before method="validateArgs" pointcut-ref="pointcut"></aop:before>
</aop:aspect>
<aop:aspect ref="loggingAspect" order="2">
<aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
</aop:aspect>
</aop:config>
首先要配置好对应的bean容器和切面bean,再配置AOP。
六、通过jdbc实现java与数据库的连接(springjdbc)
配置好applicationContext.xml
<!--导入资源文件-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!--配置C3P0数据源-->
<bean id = "dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
</bean>
<!--配置Jdbc Template-->
<bean id = "jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置namedParameterJdbcTemplate,可以使用具名参数,其没有无参数的构造器,所以必须为其构造器指定参数-->
<bean id ="namedParameterJdbcTemplate"
class = "org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref = "dataSource"></constructor-arg>
</bean>
通过JdbcTemplate和NamedParameterJdbcTemplate对数据库进行操作,位于JDBCTest.java下:
public class JDBCTest {
private ApplicationContext applicationContext = null;
private JdbcTemplate jdbcTemplate = null;
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
{
applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
jdbcTemplate = (JdbcTemplate)applicationContext.getBean("jdbcTemplate");
namedParameterJdbcTemplate =(NamedParameterJdbcTemplate)applicationContext.getBean("namedParameterJdbcTemplate");
}
//测试数据源是否连接成功
@Test
public void testDataSource() throws SQLException {
DataSource dataSource =(DataSource)applicationContext.getBean("dataSource");
System.out.println(dataSource.getConnection());
}
@Test
public void testUpdate(){
String sql = "UPDATE student SET sbirth = ? WHERE sid =? ";
jdbcTemplate.update(sql,19981122,122);
}
//批量操作
@Test
public void batchInsert(){
String sql = "INSERT INTO administor(aid,apassword,alevel) VALUES(?,?,?)";
List<Object[]> batchargs = new ArrayList<>();
batchargs.add(new Object[]{"000003","123456","2"});
batchargs.add(new Object[]{"000004","123456","2"});
batchargs.add(new Object[]{"000005","123456","2"});
jdbcTemplate.batchUpdate(sql,batchargs);
}
/*
* 从数据库获取一条记录,实际获取一个对象
* 应该调用 queryForObject(String sql, RowMapper<T> rowMapper, Object... args)
* 1.RowMapper指定如何去映射结果集的行,常用的实现类为BeanPropertyRowMapper
* 2.使用SQL中列的别名完成列名和类的属性名的映射
* */
@Test
public void testqueryforObject(){
String sql ="SELECT aid, apassword a_password, alevel FROM administor where aid = ?";
RowMapper<Administor> rowMapper = new BeanPropertyRowMapper<>(Administor.class);
Administor administor = jdbcTemplate.queryForObject(sql,rowMapper,"000010");
System.out.println(administor);
}
/*
* 查找类的集合,注意使用query即可
* */
@Test
public void testQueryforList(){
String sql ="SELECT aid, apassword a_password, alevel FROM administor where aid != ?";
RowMapper<Administor> rowMapper = new BeanPropertyRowMapper<>(Administor.class);
List<Administor> administorList = jdbcTemplate.query(sql,rowMapper,"000004");
System.out.println(administorList);
}
/*
*可以为参数起名字,
* 1.好处:若有多个参数,则不用对位子,直接对应参数名,便于维护
* 2.缺点:较为麻烦
* */
@Test
public void testNamedParameterJdbcTemplate(){
String sql = "INSERT INTO administor(aid,apassword,alevel) VALUES(:aid,:apassword,:alevel)";
Map<String,Object> paramMap= new HashMap<>();
paramMap.put("aid","000010");
paramMap.put("apassword","123456");
paramMap.put("alevel","3");
namedParameterJdbcTemplate.update(sql,paramMap);
}
/*
* 使用具名参数时,可以使用update(String sql, SqlParameterSource paramSource)方法进行更新操作
* 1.sql语句中的参数名和类的属性一致
* 2.使用SqlParameterSource 的 BeanPropertySqlParameterSource实现类作为参数
* */
@Test
public void testNamedParameterJdbcTemplate2(){
String sql = "INSERT INTO administor(aid,apassword,alevel) VALUES(:aid,:a_password,:alevel)";
Administor administor = new Administor();
administor.setAid("000008");
administor.setA_password("123456");
administor.setAlevel("4");
SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(administor);
namedParameterJdbcTemplate.update(sql,parameterSource);
}
七、Spring下的事务
1.事务简介
事务就是一系列的动作,他们被单做一个单独的工作单元,这些动作要么全部完成,要么全部不起作用。
事务管理用来确保数据的完整性和一致性。
事务的四个关键属性(ACID):
原子性:事务是一个原子操作,原子性确保这些动作要么全部完成,要么全部不起作用
一致性:一旦所有事务完成,事务就被提交,数据与资源处于一种满足业务规则的一致性状态中
隔离性:可能存在多个事务同时处理相同数据,因此每个事务应与其他事务隔离,防止数据损坏
持久性:一旦事务完成,无论发生什么系统错误,其结果不应受到影响。通常情况下,事务结果写到持久化存储器中。
2.事务传播行为等属性
(1)事务的传播行为通过@Transactional的propagation来指定
@Transactional(propagation=Propagation.REQUIRED)
如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
@Transactional(propagation=Propagation.NOT_SUPPORTED)
容器不为这个方法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW)
不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.MANDATORY)
必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.NEVER)
必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS)
如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务
(2)事物超时设置:@Transactional(timeout=30) //默认是30秒
(3)事务隔离级别:
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED)
读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ)
可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE)
串行化
MYSQL: 默认为REPEATABLE_READ级别
SQLSERVER: 默认为READ_COMMITTED
脏读 : 一个事务读取到另一事务未提交的更新数据
不可重复读 : 在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说,
后续读取可以读到另一事务已提交的更新数据. 相反, "可重复读"在同一事务中多次
读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据
幻读 : 一个事务读到另一个事务已提交的insert数据
3.实例分析
(1)需求
(2)核心代码段
applicationContext.xml中配置事务:配置事务管理器+启用事务注解
<!--配置事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--启用事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"/>
BookShopServiceImpl.java
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
//添加事务注解
//使用propagation指定事务的传播行为,即当前事务方法被另一个事务方法调用时如何使用事务
//默认为REQUIRED,即使用调用方法的事务
//REQUIRES_NEW:事务为自己的事务,调用的事务方法被挂起
@Transactional(propagation= Propagation.REQUIRES_NEW)
@Override
public void purchase(String username, String isbn) {
//1.获取输的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新书的库存
bookShopDao.updateBookStock(isbn);
//3.更新用户余额
bookShopDao.updateUseAccount(username,price);
}
}
在purchase函数上添加Transactional注解,通过将updateBookStock和updateUserAccount绑定为事务,使其中一个函数出错时,其余函数也不执行,以确保事务的原子性。
Cashier.java
@Service("cashier")
public class CashierImpl implements Cashier {
@Autowired
private BookShopService bookShopService;
@Transactional
@Override
public void checkout(String username, List<String> isbns) {
for(String isbn: isbns){
bookShopService.purchase(username,isbn);
}
}
}
Cashier类调用purchase方法,实现一个人买多本书的情况。
在checkout方法中,我们依次调用purchase方法实现买的过程。此时,存在事务的传播行为。
A.当purchase方法的Transacitonal(propagation = Propagation.REQUIED)时,purchase的事务将被包含在checkout事务中,所以,当第二次购买失败,第一次成功时,成功结果仍然会回滚;
B.当purchase方法的Transacitonal(propagation = Propagation.REQUIES_NEW)时,在checkout事务时调用purchase将checkout事务挂起,产生新的事务。
4.事务基于XML文件的实现
applicationContext_xml.xml
<!--配置C3P0数据源-->
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
</bean>
<!--配置Jdbc Template-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置bean-->
<bean id = "bookShopDao" class="com.shenpotato.springtransaction_xml.BookShopDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id = "bookShopService" class="com.shenpotato.springtransaction_xml.BookShopServiceImpl">
<property name="bookShopDao" ref="bookShopDao"></property>
</bean>
<bean id = "cashier" class="com.shenpotato.springtransaction_xml.CashierImpl">
<property name="bookShopService" ref="bookShopService"></property>
</bean>
<!--1.配置事务管理器-->
<bean id = "transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2.配置事务属性-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--3.配置事务切点,以及把事务切入点和事务属性关联起来-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.shenpotato.springtransaction_xml.BookShopService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"></aop:advisor>
</aop:config>
</beans>