一、体系结构
spring的体系结构
一、核心容器Core Container
Beans模块:提供了BeanFactory,Spring将管理对象称为Bean
Core模块:提供了Spring框架的基本组成部分,包括IOC和DI功能
Context模块:建立在Core和Beans模块的基础之上,它是访问定义和配置的任何对象的媒介。
Expression模块:模块提供了强大的表达式语言二、数据集成模块Data Access/Interaction
JDBC模块:提供了一个JDBC的抽象层,大幅度的减少了在开发过程中对数据库操作的编码
ORM模块:对流行的对象关系映射API,包括JPA、JDO和Hibernate提供了集成层支持。
Transaction模块:支持对实现特殊接口以及所有POJO类的编程和声明式的事务管理。
JMS模块:Java消息传递服务,包含使用和产生信息的特性,4.1版本后支持与Spring-message模块的集成。三、Web
WebSocket模块:Spring4.0以后新增的模块,它提供了WebSocket 和SockJS的实现,以及对STOMP的支持。
WebMVC模块:包含Spring模型—视图—控制器(MVC)和REST Web Services实现的Web程序
Web模块:提供了基本的Web开发集成特性,如:多文件上传、使用Servlet监听器来初始化IoC容器以及Web应用上下文。四、Aop
提供了面向切面编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以降低耦合性。
五、Aspects
提供了与AspectJ的集成功能,AspectJ是一个功能强大且成熟的面向切面编程(AOP)框架
Spring容器会负责控制程序之间的关系,而不是由程序代码直接控制。Spring为我们提供了两种核心容器,分别为BeanFactory和ApplicationContext
创建BeanFactory实例(了解即可)
BeanFactory beanFactory = new XmlBeanFactory(new FIleSystemResource("D://bean.xml"));
创建ApplicationContext实例(ApplicationContext为BeanFactory的子接口,其功能更强大)
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
二、IOC控制反转
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)。对象的实例不再由调用者来创建,而是由Spring容器来创建,Spring容器会负责控制程序之间的关系,而不是由调用者的程序代码直接控制。这样,控制权由应用代码转移到了Spring容器,控制权发生了反转,这就是控制反转。
spring中的bean
Bean的本质就是Java中的类,而Spring中的Bean就是实体类的引用,用来生产Java对象,如果想使用spring生产和管理bean,就需要在配置文件中告诉他需要使用那些bean,以及告知容器bean的装配方式
属性 | 描述 |
---|---|
id | 是一个bean的唯一标识符,spring容器对bean的配置,管理都是通过该属性来完成 |
name | spring容器同样可以通过该属性对容器中的bean进行配置和管理,name属性中可 以为了bean指定多个名称,每个名称之间用逗号或分号隔开 |
class | 用于指定bean的具体实现类,必须为一个类的全限定类名 |
scope | 用来指定bean的作用域,常用属性有singleton(默认),prototype |
<?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和name属性,spring会将class值的当做id使用-->
<bean id="bean1" class="com.ligong.ioc.Bean1"/>
<bean id="bean2" class="com.ligong.ioc.Bean2"/>
</beans>
获取容器中的bean通常采用以下两种方式
Object getBean(String name); 根据id或name来获取指定bean,获取之后需要进行类型强转
ApplicationContext ac = new ClassPathXmlApplicationContext("bean..xml"); Bean1 bean1 = (Bean1) ac.getBean("bean1"); bean1.hehe();
T getBean(String name,Class requiredType); 通过id和class文件来获取指定bean
Bean2 bean2= ac.getBean("bean2", Bean2.class); bean2.hehe();
bean的创建方式
如果想要使用某个对象就需要先实例化这个对象,在spring中,提供了三种实例化bean的方式
构造器实例化:被实例化的类需要提供一个空参构造函数
<bean id="bean1" class="com.ligong.ioc.Bean1"/>
静态工厂实例化
//静态工厂类,其中有创建bean的方法getBean2() public class MyStaticBeanFactory { public static Bean2 getBean2(){ return new Bean2(); } }
<bean id="bean2" class="com.ligong.ioc.MyStaticBeanFactory" factory-method="getBean2"/>
实例工厂实例化
//实例工厂 public class MyInstanceBeanFactory { public Bean2 getBean2(){ return new Bean2(); } }
<bean id="factory" class="com.ligong.factory.MyInstanceBeanFactory"/> <bean id="bean2" factory-bean="factory" factory-method="getBean2"/>
bean的作用域
spring中提供了多种作用域,spring和prototype是最常用的两种作用域
作用域 | 描述 |
---|---|
singleton(单例) | 使用singleton定义的Bean在spring容器中将只有一个实例,也就是说, 无论有多少个Bean引用他,始终将指向同一个对象,也是spring的默认作用域 |
prototype(原型) | 每次通过spring容器来获取prototype定义的bean时,容器都将创建一个新的Bean实例 |
在spring的配置文件中,使用<bean>
元素的scope属性,可以定义该bean的作用域
singleton作用域:
<bean id="bean1" class="com.ligong.ioc.Bean1" scope="singleton"/><!--scope="singleton"可以省略-->
prototype作用域:
<bean id="factory" class="com.ligong.factory.MyInstanceBeanFactory"/>
<bean id="bean2" factory-bean="factory" factory-method="getBean2" scope="prototype"/>
public void iocTest(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean..xml");
//singleton,多个引用指向一个实例
Bean1 bean1 = (Bean1) ac.getBean("bean1");
Bean1 bean11 = (Bean1) ac.getBean("bean1");
System.out.println(bean1);//com.ligong.ioc.Bean1@262b2c86
System.out.println(bean11);//com.ligong.ioc.Bean1@262b2c86
//prototype,每次获取都会产生新的实例
Bean2 bean2= ac.getBean("bean2", Bean2.class);
Bean2 bean22= ac.getBean("bean2", Bean2.class);
System.out.println(bean2);//com.ligong.ioc.Bean2@1e7c7811
System.out.println(bean22);//com.ligong.ioc.Bean2@77ec78b9
}
bean的生命周期
可以利用bean的生命周期实现一些功能,比如在bean的初始化后和销毁前执行一些方法
单例对象:单例对象的生命周期和容器相同,spring能够精准的知道该Bean何时被创建,何时初始化完成,何时被销毁
出生(init):当容器创建时,对象出生
消亡(destroy):当容器销毁时,对象消亡
<bean id="bean3" class="com.ligong.ioc.Bean3" init-method="init" destroy-method="destroy"/>
@Test public void lifeMethod(){ ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); Bean3 bean3 = ac.getBean("bean3", Bean3.class); bean3.hehe(); ac.close(); } console: init..出生方法执行 bean3.....hahaha destroy..消亡方法执行
多例对象:spring只负责创建,当容器创建了Bean实例后,Bean的实例就交我们手中,spring容器将不会再跟中其生命周期
出生:在使用对象时,spring框架才会为我们创建对象
消亡:当对象长时间不用,且没有别的对象引用时,由java的垃圾回收机制回收
<bean id="bean3" class="com.ligong.ioc.Bean3" scope="prototype" init-method="init" destroy-method="destroy"/>
@Test public void lifeMethod(){ ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); Bean3 bean3 = ac.getBean("bean3", Bean3.class); bean3.hehe(); ac.close(); } console: init..出生方法执行 bean3.....hahaha
三、DI依赖注入
依赖注入(DI):依赖注入是IOC的一种具体的实现方式,Spring容器负责将被依赖对象赋值给调用者的成员变量,这相当于为调用者注入了它依赖的实例,这就是Spring的依赖注入。
bean的装配方式即依赖注入的方式,spring容器中支持多种形式的装配
基于xml的装配,基于annotation的装配,自动装配
基于xml的装配
set方法注入:Bean类必须有一个无参构造方法,Bean类必须为属性提供setter方法
特点:创建对象时没有限制,可以给需要注入值得属性注入
使用的标签:property
标签出现的位置:bean标签的内部
标签中的属性:
- name:用于指定注入时所调用的set方法名称
- value:用于提供基本类型和String类型的数据
- ref:用于指定其他Bean类型的数据。他指的就是在spring的IoC核心容器中出现过得Bean对象
<!--在bean标签内使用<property>元素为每个属性注入值--> <bean id="now" class="java.util.Date"/> <bean id="user2" class="com.ligong.domain.User"> <property name="username" value="南山南"/> <property name="age" value="19"/> <property name="birthday" ref="now"/> </bean>
构造方法注入:Bean类必须提供有参构造方法
特点:在获取Bean对象时,注入数据是必须的操作,构造器有几个属性就需要注入几个,缺少一个都不行
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性:
- type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
- index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置从0开始
- name:用于给构造函数中指定名称的参数赋值
- value:用于提供基本类型和String类型的数据
- ref:用于指定其他Bean类型的数据。他指的就是在spring的IoC核心容器中出现过得Bean对象
<!--在bean标签内使用constructor-arg>元素为每个参数注入值--> <bean id="user" class="com.ligong.domain.User"> <constructor-arg name="username" value="北海北"/> <constructor-arg name="age" value="18"/> <!--spring无法将字符串转换成Date类型,所以这里需要使用ref属性引用一个Date类型的对象--> <constructor-arg name="birthday" ref="now"/> </bean> <bean id="now" class="java.util.Date"/>
复杂类型注入:
MyComplex.java
@Data @AllArgsConstructor @NoArgsConstructor public class MyComplex { private List<User> aList; private Map<String,String> aMap; private Set<String> aSet; private User[] aUsers; private Properties properties; public void toConsole(){ System.out.println("aUsers"+Arrays.toString(aUsers)); System.out.println("aList"+aList); System.out.println("aMap"+aMap); System.out.println("aSet"+aSet); System.out.println("properties"+properties); } }
bean2.xml
<bean id="myComplex" class="com.ligong.domain.MyComplex"> <!--集合类型和数组类型的类似,引用容器中的对象当做元素时,使用ref标签,否则使用value标签,如下set--> <property name="aList"> <list> <ref bean="user"/> <ref bean="user2"/> </list> </property> <property name="aSet"> <set> <value>梁山伯</value> <value>祝英台</value> </set> </property> <property name="aUsers"> <array> <ref bean="user"/> <ref bean="user2"/> </array> </property> <property name="aMap"> <map> <entry key="aaa" value-ref="user"/> <entry key="bbb" value-ref="user2"/> </map> </property> <property name="properties"> <props> <prop key="ooo">OOO</prop> <prop key="ppp">PPP</prop> <prop key="qqq">QQQ</prop> </props> </property> </bean>
MyTest.java
@Test public void DIMethod3(){ ApplicationContext ac = new ClassPathXmlApplicationContext("bean2.xml"); MyComplex myComplex = ac.getBean("myComplex", MyComplex.class); myComplex.toConsole(); } console: aUsers[User{username='北海北', age='18', birthday='Fri Jul 31 17:58:16 CST 2020'}, User{username='南山南', age='19', birthday='Fri Jul 31 17:58:16 CST 2020'}] aList[User{username='北海北', age='18', birthday='Fri Jul 31 17:58:16 CST 2020'}, User{username='南山南', age='19', birthday='Fri Jul 31 17:58:16 CST 2020'}] aMap{aaa=AAA, bbb=BBB, ccc=CCC} aSet[梁山伯, 祝英台] properties{qqq=QQQ, ppp=PPP, ooo=OOO}
基于Annotation的装配
集合类型的注入只能通过XML方式来实现
注解开发是最常用的方式,如果没有指定id,则目标类的首字母小写默认为bean的id属性
注解 | 描述 |
---|---|
@Component | 用于描述spring中的Bean,仅表示一个组件,下面三个注解都基于@Component |
@Repository | 用于将数据访问层的类标识为Spring中的Bean |
@Service | 用于将业务逻辑层的类标识为spring中的Bean |
@Controller | 用于将控制层的类标识为spring中的Bean |
@Autowired | 用于对Bean的属性变量,setter方法,以及构造方法进行标注 |
@Resource | 作用和@Autowired一样,有两个重要属性,spring将name属性解析 为Bean的实例名称,type属性解析为Bean的类型 |
@Qualifier | 与@Autowired配合使用,会将默认的将按Bean类型装配修改为按Bean的 实例名称装配,Bean的实例名称又@Qualifier注解的参数指定 |
@Value | 用于注入基本数据类型和String类型的数据 |
@Scope | 用于指定bean的作用域,作用于类上 |
@PostConstruct | 用于指定初始化方法,作用于方法之上 |
@PreDestroy | 用于指定销毁方法,作用于方法之上 |
@Autowired注解:
-
使用注解注入时,set方法就不是必须的了
-
如果自动按照类型注入,容器中有唯一一个bean对象类型和需要注入的变量(注解打在的成员变量)类型匹配时,即可注入成功,如果出现多个bean对象类型都与需要注入的变量的类型相同时,会按照注解所打在的成员变量名称进行匹配,如果没有匹配到该名称的bean,则程序报错
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'accountService': Unsatisfied dependency expressed through field 'accountDao'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.ligong.dao.AccountDao' available: expected single matching bean but found 2: accountDaoImpl,accountDaoImpl2
-
当匹配到多个bean时,可以使用@Qualifier注解指定bean的名称,即按照bean的名称进行匹配
自动装配
将Bean自动的注入到其他Bean的Property中,元素中包含autowire属性,通过autowire的属性值来自动装配Bean
属性值 | 说明 |
---|---|
default(默认值) | 由<bean> 的上级标签<beans> 的default-autowire属性值来确定。例如 <beans default-autowire="byName"> ,则该<bean> 元素中的autowire属性对应的属性值就为byName |
byName | 根据属性值的名称自动装配。容器将根据名称查找与属性完全一致的Bean, 并将其属性自动装配。 |
byType | 根据属性的数据类型进行byType模式的自动装配 |
constructor | 根据构造函数参数的数据类型,进行byType模式的自动装配 |
no | 默认情况下,不适用自动装配,Bean依赖必须通过ref元素定义 |
四、spring的其他注解
注解 | 描述 |
---|---|
@Configuration | 作用于类上,用于指定当前类为一个配置类 |
@ComponentScan | 作用于类上,用于通过注解指定spring在创建容器时要扫描的包 |
@Bean | 作用于方法之上,把当前方法的返回值作为bean对象存入 spring的ioc容器中 |
@Import | 导入其他配置类,其他类中可以不标注为注解类 |
@PropertySource | 用于指定properties文件所在的位置 |
//SpringConfiguration.java
@Configuration//指定该类为一个配置类
@ComponentScan(basePackages = "com.ligong")//指定要扫描注解的包
@PropertySource("classpath:db.properties")//指定配置文件的位置
@Import(OtherConfiguration.class)//导入其他配置类
public class SpringConfiguration {
@Value("${jdbc.driverClassName}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
//如果不指定name或value属性,将会使用方法名作为返回bean的id
@Bean("jdbcTemplate")
@Scope("prototype")
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean("dataSource")
public DataSource getDataSource(){
DruidDataSource druid = new DruidDataSource();
druid.setDriverClassName(driver);
druid.setUrl(url);
druid.setUsername(username);
druid.setPassword(password);
return druid;
}
}
//测试类
@Test
public void testAnnotation02(){
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
AccountService accountService = ac.getBean("accountService", AccountService.class);
accountService.queryAccount();
}
五、代理模式
代理模式:为一个对象提供一个替身,在实现目标对象方法功能的基础上,提供额外的增强方法,扩展功能。我们需要获得一个代理对象,使其与目标对象具有相同的方法,然后调用目标对象的方法,增强其功能
静态代理
代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来 调用目标对象的方法
1)优点:在不修改目标对象功能的前提洗,能通过代理对象对目标功能扩展
2)缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
3)一旦接口增加方法,目标对象与代理对象都要维护
//Teacher.java目标对象的接口
public interface Teacher {
void teach();
}
//MathTeacher.java
public class MathTeacher implements Teacher {
@Override
public void teach() {
System.out.println("老师在讲定积分......");
}
}
//ProxyTeacher.java 代理类
//静态代理要求代理对象与目标对象实现相同的接口
public class ProxyTeacher implements Teacher {
//使用聚合的方式将代理目标对象(被代理的教师对象)
private Teacher target;
public ProxyTeacher(Teacher target) {
this.target = target;
}
//代理对象的teach方法,用于增强
@Override
public void teach() {
System.out.println("数学老事有事。。。找了个代理数学老师");//前增强
target.teach();
System.out.println("代理数学老师讲完了,走了。。。");//后增强
}
}
//测试类代码
@Test
public void staticProxy() {
Teacher proxyTeacher = new ProxyTeacher(new MathTeacher());
proxyTeacher.teach();
}
Jdk动态代理
创建代理对象的要求:被代理类最少实现一个接口
需要用到的类:JDK为我们提供的类:java.lang.reflect.Proxy;
需要用到的方法:newProxyInstance(参数列表):static Object
newProxyInstance方法的参数列表:
ClassLoader loader:指定被代理对象的类加载器
Class<?>[] interfaces:指定被代理对象实现的接口
InvocationHandler h:用于提供增强的代码,以及执行目标对象的方法。我们需要创建一个实现InvocationHandler接口的类,或者以匿名内部类的形式
//JdkProxy.java 代理类
public class JdkProxy {
//聚合目标对象(被代理对象)
private Object target;
public JdkProxy(Object target) {
this.target = target;
}
//给目标对象绑定一个代理实例
public Object getProxyInstance(){
ClassLoader cl = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
return Proxy.newProxyInstance(cl, interfaces, new InvocationHandler() {
/**
* @param proxy 代理对象的引用
* @param method 要被执行的方法
* @param args 执行方法需要用到的参数
* @return 和被代理对象方法有相同的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeMethod();//前增强,在执行目标对象的方法之前执行,我们可以定义一系列的方法
//通过反射机制执行目标对象的方法
Object obj = method.invoke(target, args);
afterMethod();//后增强,在执行目标对象的方法之前执行
return obj;
}
private void beforeMethod(){
System.out.println("数学老事有事。。。找了个代理数学老师");
}
private void afterMethod(){
System.out.println("代理数学老师讲完了,走了。。。");
}
});
}
}
//测试类代码
@Test
public void jdkProxy(){
Teacher proxyTeacher = (Teacher) new JdkProxy(new MathTeacher()).getProxyInstance();
proxyTeacher.teach();
}
Cglib代理
在早期的jdk版本之前Cglib动态代理的性能要高于jdk动态代理,Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类,在版本的不断优化,jdk动态代理的性能以及高于Cglib动态代理了
静态代理和Jdk动态代理都要求目标对象实现一个接口才能使用,但有时候我们的目标对象并没有实现任何接口,这个时候可以使用目标对象的子类来实现动态代理
在AOP编程中,如果目标对象需要实现接口,就使用Jdk代理,如果不需要实现接口,就用Cglib代理
基于CGLib字节码动态代理的方案是通过扩展被增强类,动态创建其子类的方式进行AOP增强植入的。由于使用final、static、private 修饰符的方法都不能被子类覆盖,被代理的类不能是final类,否则Cglib就无法拦截目标对象的该方法,就无法对其增强
需要用到的类:cglib包下的:net.sf.cglib.proxy.Enhancer;
需要用到的方法:create(Class type, Callback callback):static Objectcreat方法的参数列表:
Class type:指定被代理对象的类加载器
Callback callback:MethodIntercept接口继承了Calloack接口,用于提供增强的代码,以及执行目标对象的方法。我们需要创建一个实现MethodInterceptor接口的类,或者以匿名内部类的形式
public class CglibProxy{
private Object target;
public CglibProxy(Object target){
this.target = target;
}
public Object getProxyInstance(){
//以匿名内部类的方式创建MethodInterceptor的实现类,,用于增强目标对象的方法
return Enhancer.create(target.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//前增强
beforeMethod();
Object result = method.invoke(target, args);//执行目标对象的方法
//后增强
afterMethod();
return result;
}
private void beforeMethod(){
System.out.println("数学老师走进教室");
}
private void afterMethod(){
System.out.println("数学老师离开教室");
}
});
}
}
另一种编写方式,原理都是一样的
//CglibProxy.java 代理类
public class CglibProxy implements MethodInterceptor {
private Object target;
public CglibProxy(Object target) {
this.target = target;
}
public Object getProxyInstance(){
Enhancer enhancer = new Enhancer();
//设置生成子类的父类为目标类(使生成的子类继承目标类)
enhancer.setSuperclass(target.getClass());
//设置回调函数,就是设置对目标对象的方法进行增强,因为该类实现了MethodInterceptor,而MethodInterceptor又继承了Callback接口,所以可以直接传入this
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
beforeMethod();//前增强
//通过反射机制调用目标对象的方法
Object result = method.invoke(target, args);//参数为目标对象以及其所需要的参数
afterMethod();//后增强
return result;
}
private void beforeMethod(){
System.out.println("数学老事有事。。。找了个代理数学老师");
}
private void afterMethod(){
System.out.println("代理数学老师讲完了,走了。。。");
}
}
//测试类
@Test
public void CglibProxy(){
Teacher proxyTeacher = (Teacher) new CglibProxy(new MathTeacher()).getProxyInstance();
proxyTeacher.teach();
}
六、AOP切面编程
什么是面向切面编程:
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
作用:在程序运行期间,不修改源码对已有方法进行增强
优势:减少重发代码,提高开发效率,维护方便
AOP的相关术语:
切面(aspect):将日志,权限检查等增强功能抽取到一个类,该类就是一个切面类
连接点(joinpoint):指那些被拦截到的点,即目标类中的所有方法(在cglib中有些static/final/private修饰的方法就不会被拦截,他们就算作为连接点)
//目标类中由于cglib无法拦截final修饰的方法,所以meeting方法不是一个连接点,其他方法都会被拦截到,所以其他方法都是连接点 public class Teacher { public void teach(){ System.out.println("老师正在上课。。。。"); } public void prepare(){ System.out.println("老师正在备课。。。。"); } public final void meeting(){ System.out.println("老师正在开会。。。。"); } }
切入点(pointcut):需要被增强的那些方法,在那些已经拦截到的方法中(在连接点中),通过一些业务逻辑选择性的对目标类的某些方法进行增强,被增强的方法就是切入点
@Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //排除掉了teach方法,则该方法就会被正常执行,不会被增强,即他是一个连接点但已经不是一个切入点了 if ("teach".equals(method.getName())) return method.invoke(target, args); //前增强 beforeMethod(); Object result = method.invoke(target, args);//执行目标对象的方法 //后增强 afterMethod(); return result; }
通知(advice):存在于切面类中, 在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面类中的方法(入日志,权限判定等方法)
在执行目标方法之前@Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //排除掉了teach方法,则该方法就会被正常执行,不会被增强,即他是一个连接点但已经不是一个切入点了 if ("teach".equals(method.getName())) return method.invoke(target, args); try { //前置通知 beforeMethod(); Object result = method.invoke(target, args);//执行目标对象的方法 //后置通知 afterMethod(); return result; } catch (Exception e) { //异常通知 System.out.println("执行了某些逻辑。。。如输出错误日志"); } finally { //最终通知 System.out.println("关闭"); } return null; }
目标对象(target):被代理的对象即为目标对象
织如(weaving):将增强目标对象的功能到创建代理对象的过程就是织入
基于xml的AOP配置
导入该依赖,用于解析切入点表达式的
<!-- aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
所有的切面,切入点和通知都必须定义在<aop:config>元素内,其下有三个子元素分别为
<aop:aspect id=“myAspect” ref=“引用的切面类”></aop: aspect>
<aop:pointcut id=“切入点名称” expression=“切入点表达式”/>
<aop:advisor advice-ref="" />
-
配置切面:在Spring的配置文件中,配置切面使用的是<aop:aspect>元素,该元素的ref属性会指定某一个bean为切面
-
配置切入点:使用<aop:pointcut/>标签配置切入点,当定义在<aop:config>元素内表示全局切入点,可以被多个切面的通知引用,但是必须定义在切面之前否则报错 ; 当定义在<aop:aspect>元素内只能对当前切面下的通知所引用,使用expression属性配置切入点表达式,其他通知通过id属性引用该切入点
<aop:pointcut id="myPointcut" expression="execution(public * com.ligong.service.impl.*.*(..))"/>
![](https://i-blog.csdnimg.cn/blog_migrate/57ca8b55d2832744300e6a5d65a27c08.png)
- 配置通知:这些元素的属性入下表
- 前置通知:<aop:before method="" pointcut-ref=""/>
- 后置通知:<aop:after-returning method="" pointcut-ref="" returning=“参数名”/>
- 异常通知:<aop:after-throwing method="" pointcut-ref="" throwing=“参数名”/>
- 最终通知:<aop:after method="" pointcut=“execution(* com.ligong.service.impl..(…))”/>
- 环绕通知:<aop:around method="" pointcut-ref=""/>
属性名称 | 描述 |
---|---|
pointcut | 该属性用于定义一个切入点表达式,如:pointcut=“execution(* com.ligong.service.impl..(…))” |
pointcut-ref | 引用已经定义好的切入点,该属性和pointcut属性只需要使用一个 |
method | 该属性指定一个方法名,该方法即为一个具体的增强操作 |
throwing | 该属性只对元素有效,用于指定一个形参名,异常通知方法 可以通过访问该目标方法所抛出的异常 |
returning | 该属性只对元素有效,用于指定一个形参名,后置通知 方法可以通过该形参访问目标方法的返回值 |
前置通知:spring为我们提供了一个接口JoinPoint,可以通过该接口的方法获得执行方法所需要的参数,以及方法名等
//前置通知
public void printLogBefore(JoinPoint joinPoint){
//获得执行目标方法所需的参数
Object[] args = joinPoint.getArgs();
//获得目标方法的名称
String name = joinPoint.getSignature().getName();
System.out.println("前置通知printLogBefore......目标方法为:"+name+",执行目标方法的参数为:"+ Arrays.toString(args));
}
后置通知:通过xml中<aop:after-returning returning=“参数名” />指定的参数名,获得目标方法返回值
//配置后置通知,用于指定一个形参名,后置通知方法可以通过该形参访问目标方法的返回值,
//<aop:after-returning method="printLogAfterReturning" pointcut-ref="" returning="result"/>
public void printLogAfterReturning(JoinPoint joinPoint,Object result){
System.out.println("后置通知printLogAfterReturning....目标方法的返回值为:"+result);
}
异常通知:通过xml中<aop:after-throwing throwing=“参数名” />指定的参数名,获得目标方法所抛出的异常
//配置异常通知
//<aop:after-throwing method="printLogThrowing" pointcut-ref="" throwing="throwable"/>
public void printLogThrowing(Throwable throwable){
System.out.println("异常通知printLogThrowing......message:"+throwable);
}
//console:
//异常通知printLogThrowing......message:java.lang.ArithmeticException: / by zero
环绕通知:spring为我们提供了一个接口:ProceedingJoinPoint,该接口有一个方法proceed(),调用该方法就相当于调用切入点方法,环绕通知类似于cglib动态代理的intercept方法
public Object printLogAround(ProceedingJoinPoint joinPoint){
//获取指定目标对象方法所需的参数
Object[] args = joinPoint.getArgs();
Object result;
try {
System.out.println("前置通知》》》》");
result = joinPoint.proceed(args);
System.out.println("后置通知》》》》");
} catch (Throwable throwable) {
System.out.println("异常通知》》》》");
throw new RuntimeException(throwable);
}finally {
System.out.println("最终通知》》》》");
}
return result;
}
具体的配置文件
<?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
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="accountService" class="com.ligong.service.impl.AccountServiceImpl"/>
<bean id="aspect" class="com.ligong.utils.MyAspect"/>
<!--配置aop-->
<aop:config>
<!--全局切入点,顺序必须写在切面上,可以被多个切面的通知所引用-->
<!--<aop:pointcut id="globalPointcut" expression="execution(* *..*.*(..))"/>-->
<!--配置切面-->
<aop:aspect id="myAspect" ref="aspect">
<!--配置切入点-->
<aop:pointcut id="myPointcut" expression="execution(public * com.ligong.service.impl.*.*(..))"/>
<!--配置的通知的类型,建立通知方法与切入点的关联-->
<!--配置前置通知-->
<aop:before method="printLogBefore" pointcut-ref="myPointcut"/>
<!--配置后置通知,用于指定一个形参名,后置通知方法可以通过该形参访问目标方法的返回值-->
<aop:after-returning method="printLogAfterReturning" pointcut-ref="myPointcut" returning="result"/>
<!--配置异常通知-->
<aop:after-throwing method="printLogThrowing" pointcut-ref="myPointcut"/>
<!--配置最终通知-->
<aop:after method="printLogAfter" pointcut-ref="myPointcut"/>
<!--配置环绕通知-->
<!--<aop:around method="printLogAround" pointcut-ref="myPointcut"/>-->
</aop:aspect>
</aop:config>
</beans>
基于注解的AOP配置
注解方式非常简单,
注解名称 | 描述 |
---|---|
@Aspect | 用于定义一个切面 |
@Pointcut | 用于定义切入点表达式,需要定义一个空方法,方法名为切入点的id, 以下方法均依赖切入点表达式 |
@Before | 前置通知 |
@AfterReturning | 后置通知,也叫返回通知,returning="" |
@AfterThrowing | 异常通知,throwing="" |
@After | 后置通知 |
@Around | 环绕通知,相当于MethodIntercept |
使用注解方式需要在配置文件中开启spring框架对aspectj的支持**<aop:aspectj-autoproxy />**
注意:如果由于spring默认使用jdk动态代理的,如果目标类没有实现接口,spring可以自动的切换到cglib;如果你就想用cglib,需要配置<aop:aspectj-autoproxy proxy-target-class="true"/>
强制切换到cglib动态代理
慎重选择:如果没有异常,最终通知会先于后置通知执行;如果有异常,最终通知会先于异常通知指向,这明显和实际不符,正常的逻辑是不管有没有异常,最终通知都会最后执行。不过使用环绕通知并不会出现顺序混乱的问题
@Component
@Aspect
public class MyAspect {
//定义切入点,方法名作为切入点的id,可供其他通知引用
@Pointcut("execution(* com.ligong.service.impl.*.*(..))")
public void myPointcut(){
}
//前置通知
@Before("myPointcut()")
public void printLogBefore(JoinPoint jp){
System.out.println("前置通知printLogBefore......");
}
//后置通知(返回通知),如果作用的方法有返回值,则该注解的一个参数可以用来获得返回值returning="参数名"
@AfterReturning(pointcut = "myPointcut()",returning = "result")
public void printLogAfterReturning(JoinPoint jp,Object result){
System.out.println("后置通知printLogAfterReturning......");
}
//异常通知
@AfterThrowing(pointcut = "myPointcut()",throwing="ex")
public void printLogThrowing(Exception ex){
System.out.println("异常通知printLogThrowing......"+ex);
}
//最终通知
@After("myPointcut()")
public void printLogAfter(JoinPoint jp){
System.out.println("最终通知printLogAfter......");
}
}
环绕通知
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(* com.ligong.service.impl.*.*(..))")
public void myPointcut(){
}
public void printLogBefore(){
System.out.println("前置通知printLogBefore......");
}
public void printLogAfterReturning(){
System.out.println("后置通知printLogAfterReturning......");
}
public void printLogThrowing(){
System.out.println("异常通知printLogThrowing......");
}
public void printLogAfter(){
System.out.println("最终通知printLogAfter......");
}
/**
* spring为我们提供了一个接口:ProceedingJoinPoint,该接口有一个方法proceed(),调用该方法就相当于调用切入点方法
*/
@Around("myPointcut()")
public Object printLogAround(ProceedingJoinPoint joinPoint){
//获取指定目标对象方法所需的参数
Object[] args = joinPoint.getArgs();
Object result;
try {
printLogBefore();//前置通知
result = joinPoint.proceed(args);
printLogAfterReturning();//后置通知
} catch (Throwable throwable) {
printLogThrowing();
throw new RuntimeException(throwable);
}finally {
printLogAfter();
}
return result;
}
}
七、Spring的事务管理
事务管理的核心接口
PlatformTransactionManager接口:
该接口是Spring提供的平台事务管理器,主要用于管理事务.该接口中提供了三个事务管理操作的方法,
TransactionStatus getTransaction(TransactionDefinition definition); 用于获取事务状态信息
void commit(TransactionStatus status); 用于提交事务
void rollback(TransactionStatus status); 用于回滚事务
TransactionDefinition接口:
该接口只是代表事务管理的接口,并不知道底层是如何管理事务的,具体如何管理事务则由他的实现类来完成.该接口常见的几个实现类如下:
DataSourceTransactionManager:用于配置JDBC数据源的事务管理器
JtaTransactionManager:用于配置全局事务管理器
TransactionStatus接口:
该接口是事务定义的描述对象,该对象中定义了事务规则,并提供了获取事务相关信息的方法,具体如下:
String getName(); 获取事务对象名称
int getIsolationLevel(); 获取事务的隔离级别
int get PropagationBehavior(); 获取事务的传播行为
int getTimeout(); 获取事务的超时时间
boolean isReadOnly(); 获取事务是否只读
上述方法中,事务的传播行为是指在同一个方法中,不同操作前后所使用的事务。传播行为有很多种,具体如下表所示:(Spring默认传播行为是REQUIRED,用的也就前面两个,通常情况下,数据的查询不会影响原数据的改变,所以查询方法不需要进行事务管理)
属性名称 | 值 | 描述 |
---|---|---|
PROPAGATION_REQUIRED | required | 支持当前事务。如果A方法已经在事务中,B将直接使用。 如果没有将创建新事务(必须有一个事务) |
PROPAGATION_SUPPORTS | supports | 支持当前事务。如果A方法已经在事务中,B将直接使用。 如果没有将以非事务状态执行(有就用,没有就拉倒) |
PROPAGATION_MANDATORY | mandatory | 支持当前事务。如果A方法没有事务,将抛出异常 |
PROPAGATION_REQUIRES_NEW | requires_new | 将创建新的事务,如果A方法已经在事务中,将A事务挂起 |
PROPAGATION_NOT_SUPPORTED | not_supported | 不支持当前事务,总是以非事务状态执行。 如果A方法已经在事务中,将挂起 |
PROPAGATION_NEVER | never | 不支持当前事务,如果A方法已经在事务中,将抛出异常 |
PROPAGATION_NESTED | nested | 嵌套事务,底层将使用Savepoint形成嵌套事务 |
基于xml的声明式事务控制
-
配置数据源
-
配置事务管理器
-
配置事务的通知
-
使用
<tx:advice id="" transaction-manager=""></tx:advice >
标签,配置事务通知
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用 -
事务通知下有一个子元素
<tx:attributes></tx:attributes>
,在该元素下配置具体事务配置事务的属性
isolation:用于指定事务的隔离级别。默认使用数据库的默认隔离级别。
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
read-only:用于指定事务是否只读。默认值是false,查询方法可以设置为true。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。<!-- 配置事务的通知--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!--设置所有方法传播行为REQUIRED必须处于事务中,并且不只读--> <tx:method name="*" propagation="REQUIRED" read-only="false"/> <!--设置find开头的方法传播行为SUPPORTS,并且只读--> <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method> </tx:attributes> </tx:advice>
-
-
配置AOP
- 配置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: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
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:db.properties"/>
<bean id="accountService" class="com.ligong.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<bean id="accountDao" class="com.ligong.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 1、配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${db.driverClassName}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>
<!-- 2、配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--需要注入一个数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 3、配置事务的通知-->
<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:attributes>
</tx:advice>
<!-- 4、配置aop-->
<aop:config>
<!-- 4.1、配置通用切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))" />
<!-- 4.2、建立切入点表达式和事务通知的对应关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1" />
</aop:config>
</beans>
基于注解的声明式事务
我们只需要开启声明式事务注解的支持即可<tx:annotation-driven transaction-manager="transactionManager"/>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--1、开启注解扫描-->
<context:component-scan base-package="com.ligong"/>
<context:property-placeholder location="classpath:db.properties"/>
<!--2、配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--3、配置JdbcTemplate,需要注入一个数据源-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--4、配置事务管理器需要注入一个数据源-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--5、如果事务管理器的id为transactionManager,则transaction-manager属性可以省略-->
<tx:annotation-driven />
</beans>
service层如下
查询方法:传播行为设置为SUPPORTS,并且设置只读
增删改查方法:传播行为需要设置为REQUIRED,默认就是REQUIRED,并且不只读,
//事务的传播行为,默认为REQUIRED,事务是否只读readOnly,默认false,不只读
@Transactional(propagation = Propagation.REQUIRED,readOnly = false)
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
//查询方法传播行为设置为SUPPORTS,并且只读;不追求完美的话,这个注解不打也没有影响,只会影响一点点性能
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
public void findAllAccount() {
accountDao.findAllAccount();
}
@Override
public void transfer(Integer id1, Integer id2, Double money) {
Account source = accountDao.findAccountById(id1);
Account target = accountDao.findAccountById(id2);
Integer rs1 = accountDao.updateMoney(id1, source.getMoney() - money);
//int i = 2/0;
Integer rs2 = accountDao.updateMoney(id2, target.getMoney() + money);
}
}