IoC控制反转
实现解耦合 (servlet中的自动创建对象是由tomcat来实现的,同ioc形式)
DI(dependency injection)是IoC的技术实现,底层是反射机制
把对象的创建,赋值和管理工作都交给代码之外的容器实现,也就是对象的创建是由外部资源完成的
控制:创建对象,对象的属性赋值,对象之间的关系管理
反转:把原来开发人员创建和管理对象的权力交给代码之外的容器管理
正转:开发人员主动管理对象
1.加入的依赖
<!--加入依赖-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
2.配置文件
<!-- 告诉spring创建对象
声明bean,就是告诉spring要创建某个类对象
id为对象的自定义名称,唯一值,通过这个名字找到该对象
class:为类的全限定名称,不能是接口
spring将创建好的对象放在了map中,
一个bean声明一个对象
spring创建的是一个无参构造对象-->
<bean id="someServlet" class="org.yixia.impl.SomeServletImpl"/>
<!-- 创建一个非自定义对象-->
<bean id="myDate" class="java.util.Date"/>
@Test
public void test2(){
//使用spring容器创建的对象
//指定spring配置文件名称
String config = "beans.xml";
//2.创建spring容器对象,ApplicationContext
//通过ApplicationContext获取容器
//在spring容器创建之后,会自动创建配置文件中的所有对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
//获取配置文件中创建对象的个数
int count = applicationContext.getBeanDefinitionCount();
System.out.println("spring容器中创建对象的个数"+count);
//获取配置文件中创建对象的名称
String[] name = applicationContext.getBeanDefinitionNames();
for(String str:name){
System.out.println("spring创建的对象为"+str);
}
//getBean("xml文件中创建对象的id名")
SomeServlet someServlet = (SomeServlet) applicationContext.getBean("someServlet");
someServlet.doSome();
}
给spring对象的属性进行赋值
- 在spring的配置文件中,使用标签和属性来完成,基于xml的di实现
- 使用Spring的注解完成属性赋值
di的语法分类
- set注入:spring调用类的set方法,set方法可以实现属性的赋值,
- 构造注入,spring调用类的有参构造方法,创建对象,并完成属性的赋值
xml配置文件set注入
<!-- set注入
1.简单类型注入
<bean id="myStudent" class="org.yixia.ba01.Student">
一个标签只能赋值一个属性
<property name="属性名称" value="属性的值"/>
</bean>
-->
<bean id="myStudent" class="org.yixia.ba01.Student">
<property name="name" value="zhangsan"/>
<property name="age" value="24"/>
</bean>
<!-- set注入
1.简单类型注入
<bean id="myStudent" class="org.yixia.ba01.Student">
一个标签只能赋值一个属性
<property name="属性名称" value="属性的值"/>
</bean>
2.引用数据类型注入
<bean id="myStudent" class="org.yixia.ba01.Student">
<property name="school" ref="bean中的id(对象的名称)"/>
</bean>
-->
<bean id="myStudent" class="org.yixia.ba01.Student">
<property name="name" value="zhangsan"/>
<property name="age" value="24"/>
<property name="school" ref="mySchool"/>
</bean>
<bean id="mySchool" class="org.yixia.ba01.School">
<property name="address" value="河北"/>
<property name="name" value="小张传媒"/>
</bean>
<!-- 构造方法注入
<constructor-arg>标签
<constructor-arg>标签属性:
name:构造方法的形参名
index:表示构造参数的位置,参数从左到右的位置为0,1,2
value:构造方法的形参类型是简单类型
ref:构造方法的形参类型是应用类型
-->
<bean id="myStudent" class="org.yixia.ba02.MyStudent">
<constructor-arg name="name" value="lisi"/>
<constructor-arg name="age" value="24"/>
<constructor-arg name="mySchool" ref="mySchool"/>
</bean>
<bean id="mySchool" class="org.yixia.ba02.MySchool">
<property name="address" value="hebei"/>
<property name="name" value="xiaozhangjlb"/>
</bean>
<!-- 引用类型的自动注入
根据某些规则给引用类型赋值
1.byName(按名称注入):java类中引用类型的属性名和Spring容器中的(配置文件)<bean>的id名称一样
且数据类型是一致的
2.byType(按类型注入):java类中引用类型的数据类型和Spring的容器中(配置文件)<bean>的class属性相同
是同源关系,
同源关系:
1.Java中引用类型的数据类型和bean的class的值相同
2.Java中引用类型的数据类型和bean的class的值为父子关系
3.Java中引用类型的数据类型和bean的class的值为接口和实现类关系
-->
<!-- <bean id="myStudent" class="org.yixia.ba03.Student" autowire="byName">
<property name="name" value="zhangsan"/>
<property name="age" value="24"/>
</bean>
<bean id="school" class="org.yixia.ba03.School">
<property name="address" value="河北试试"/>
<property name="name" value="小张传媒"/>
</bean>-->
<bean id="myStudent" class="org.yixia.ba03.Student" autowire="byType">
<property name="name" value="xiaojin"/>
<property name="age" value="24"/>
</bean>
<bean id="mySchool" class="org.yixia.ba03.School">
<property name="address" value="河北会话"/>
<property name="name" value="小张传媒"/>
</bean>
注解注入
- 加入依赖
- 在类上加入注解
- 声明组件扫描器的标签,指明注解的位置
- 声明容器
包含关系的配置文件
<!-- 包含关系的配置文件:可使用通配符
applicationContext表示主文件:包含其他配置文件,主配置文件一般不配置对象
语法: <import resource="其他配置文件的路径"/>
关键字:"classpath:"表示类路径(class文件所在的目录)
在spring的配置文件中要指定其他文件的位置,需要使用classpath,告诉spring到哪里去加载读取
-->
<import resource="classpath:ba04/spring_school.xml"/>
<import resource="classpath:ba04/spring_student.xml"/>
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/*
* Component:创建对象的,等同于《bean》的功能
* 属性:value 设置对象的名称,也就是bean的id值
* value的值是唯一的
* 位置:在类的上面
*
* 与component功能一致的注解:(给项目的对象分层的)
* @Repository:放在Dao的实现类上,表示创建dao对象
* @Service:放在Service的实现类上,做业务处理的,由事务等功能
* @Controller:放在控制器上的,能够接受用户提供的参数,显示请求的处理结果
*
*/
//@Component(value = "myStudent")
//省略value
//@Component("myStudent")
//不指定对象名称,由spring默认提供,名称为类名的首字母小写
@Component
public class Student {
/*
*@Value:简单类型的属性赋值
* 属性:value 是String类型,表示简单类型的属性值
* 位置:定义在属性之上,无需set方法
* 或者定义在set方法之上
*/
@Value("ajin")
private String name;
@Value("50")
private int age;
/*
* 引用类型
* @Autowired:spring提供的注解,实现引用类型赋值
* spring 中通过注解给引用类型赋值,采用自动注入,支持byName和byType
* 默认是byType类型注入
* 属性:required:boolean类型,默认为true
* true:赋值失败,报错
* false:正常执行不赋值
* 位置:在属性上定义,无需set
* 或者set方法上面
*
* 如果要使用byName方式,需要做的是
* 1.在属性上面加入@Autowired
* 2.在属性上面加入@Qualifier(value="bean中的id"):表示使用注定的bean名称完成赋值
*
*/
/* @Autowired(required = true)
@Qualifier("school")
private School school;*/
/*
* 引用类型
* @Resource:来自jdk中的注解,spring对此注解支持,可以给引用类型赋值
* 使用的也是自动注入原理,支持byName。byType。默认ByName
* 先使用byName原理,如果失败再使用byType
* 位置:1.属性定义上面,无需set方法
* 2.在set方法上面
*
* @Resource 只使用byName方式,需要增加一个属性 name
* name的值是bean的id
*/
@Resource(name = "school")
private School school;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
/* public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}*/
}
AOP面向切面编程
规范化的动态代理
动态代理
实现方式:
1.jdk动态代理,使用jdk中的proxy,Method,InvovationHander创建代理对象,jdk动态代理要求目标必须实现接口
cglib动态代理:第三方工具库,创建代理对象,原理是继承,通过继承目标类,创建子类,子类就是代理对象,要求目标类不能是final的,方法也不能是final的
功能:1.增强功能2.减少代码的重复 3.专注业务逻辑代码4.解耦合,事务和非业供能分开
术语
- Aspect:切面,表示增强的功能,非业务功能,常见的切面功能有日志,事务,统计信息,参数检查,权限验证
- JoinPoint:连接点,连接业务方法和切面的位置,就是类中的业务方法
- Pointcut:切入点,指多个连接点方法的集合,多个方法
- 目标对象:给哪个类的方法增加功能,哪个类就是目标对象
- Advice:通知,通知表示切面功能执行的时间
一个切面的关键要素
- 切面的功能代码
- 切面的执行位置,使用Pointcut表示切面执行的位置
- 切面的执行时间:使用Advice表示时间,在目标方法之前还是之后
AspectJ专门的AOP框架
有五个注解实现(也可使用xml配置文件)
- @Before
- @AfterReturning
- @Around
- @AfterThrowing
- @After
AspectJ的切入点表达式
AspectJ定义了专门的表达式用于指定切入点,表达式的原型是:
execution( modifiers-pattern?
ret-type-pattern
declaring-type-pattern?name-pattern(param-patter)
throws-pattern?)
//execution(访问权限 方法返回值 方法声明(参数)异常类型)
jdk动态代理
- 有目标接口类
- 创建目标类
- 创建切面类
- 创建bean对象
- applicationContext配置文件中,创建声明自动代理生成器,代理对象是在内存中实现的
aop:aspectj-autoproxy/
<!--加入依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Date;
/**
* @Aspect:是aspectj中的注解
* 作用:表示当前类是切面类
* 切面类:似乎用来给业务方法增加功能的类,有切面功能的代码
* 位置:类名上
*/
@Aspect
public class MyAspect {
/**
* 前置通知的:定义方法,方法是实现切面功能的
* 方法的要求:
* 1.公共方法:public
* 2.方法没有返回值
* 3.方法名称自定义
* 4.方法可以有参数,也可以没有
* 如果有参数,参数不是自定义的,有几个参数类型可以使用
*/
/**
* @Before:前置通知注解
* 属性:value,内容为切入点表达式,表是切面的功能执行的位置
* 位置,方法的上面
* 特点: 1.在目标方法之前执行
* 2.不会改变目标方法的执行结果
* 3.不会影响目标方法的执行
*/
@Before("execution(public void com.yixia.ba01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore(){
System.out.println("前置通知,切面功能:在目标方法之前输出执行的时间:"+ new Date());
}
/**
* 指定通知方法中的参数:JoinPoint
* JoinPoint:业务方法,要加入切面功能的业务方法,
* 作用:可以在通知方法中,获取方法执行时的信息,如方法名称,方法的实参
* 要求:必须是第一个位置的参数,
*/
@Before("execution(public void com.yixia.ba01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore1(JoinPoint point){
//获取方法完整的定义
System.out.println("方法的签名(定义)="+point.getSignature());
System.out.println("方法的名称="+point.getSignature().getName());
//获取方法的实参
Object[] args = point.getArgs();
for (Object arg:args){
System.out.println("参数="+arg);
}
System.out.println("前置通知,切面功能:在目标方法之前输出执行的时间:"+ new Date());
}
/**
* 后置通知:有参数,其它同前置方法的要求
*
* @AfterReturning:后置通知注解
* 属性: 1.value,内容为切入点表达式,表是切面的功能执行的位置
* 2.returning 自定义的变量,表示目标方法的返回值
* 自定义变量名必须和通知方法的形参名一样
*
* 位置,方法的上面
* 特点: 1.在目标方法之后执行
* 2.能够获得到目标方法的返回值,可以根据返回值做不同的处理
* 3.可以修改返回值
*/
@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",
returning = "res")
public void myAfterReturning(Object res){
System.out.println("后置通知:在目标方法之后执行的,获取返回值是"+res);
}
@AfterReturning(value = "execution(* *..SomeServiceImpl.doStudent(..))",
returning = "res")
public void myAfterReturningStudent(Object res){
System.out.println("后置通知:在目标方法之后执行的,获取返回值是"+res);
Student student = new Student();
student.setName("xioajin");
student.setAge(24);
res = student;
}
/**
* 环绕通知:
* 方法定义格式:
* 1,public
* 2.必须有一个返回值,推荐使用Object
* 3.方法名称自定义
* 4.方法有参数,固定的参数,ProceedingJoinPoint
*
* @Around:环绕通知
* 属性:value ,切入点表达式
* 位置:在方法的定义上面
* 特点:
* 1。功能最强的通知
* 2.在目标方法前和后都有增强功能;
* 3.控制目标方法是否被调用执行
* 4.修改原来的目标方法的执行结果,影响最后的调用结果
*
* 环绕通知等同于jak中的动态代理的 InvocationHandler接口
*
* 参数:ProceedingJoinPoint 就等于Method
* 作用:执行目标方法的额执行结果,可以被修改
*
* 经常做事务
* @param pjp
* @return
*/
@Around("execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
//实现环绕通知
Object result = null;
//在目标函数前
System.out.println("环绕通知:在目标方法之前,输出时间:"+new Date());
//目标方法调用
result = pjp.proceed();//method.invoke(); object result = doFirst();
//在目标方法之后
System.out.println("环绕通知:在目标方法之后:提交事务");
return result;
}
/**
* 异常通知:
* 方法定义格式:
* 1,public
* 2.没有返回值;
* 3.方法名称自定义
* 4.方法有一个Exception,如果有是JoinPoint,
*
* @AfterThrowing:异常通知
* 属性:value ,切入点表达式
* throwing 自定义的变量,表示目标方法抛出的异常对象
* 变量名必须和方法的参数名一样
* 位置:在方法的定义上面
* 特点:
* 1。在目标方法抛出异常时执行
* 2.可以做异常的监控程序,监控目标方法执行时是不是有异常
* 如果有异常可以发现
*/
@AfterThrowing(value = "execution(* *..SomeService.doSecond(..))",
throwing = "ex")
public void myAfterThrowing(Exception ex){
System.out.println("异常通知:方法发生异常时,执行="+ex.getMessage());
//发送邮件,短信,通知开发人员
}
/**
* 最终通知:
* 方法定义格式:
* 1,public
* 2.没有返回值;
* 3.方法名称自定义
* 4.没有参数,如果有是JoinPoint,
*
* @After:异常通知
* 属性:value ,切入点表达式
* 位置:在方法的定义上面
* 特点:
* 1。总是会执行
* 2.在目标方法之后执行
*/
@After("myPointcut()")
public void myAfter(){
System.out.println("执行最终通知,总是会被执行的代码");
//一般做资源清工作的
}
/**
* @Pointcut :定义和管理切入点,如果你的项目中有多个切入点表达式时重复的,可以复用。
* 可以使用@Pointcut
* 属性:value 切入点表达式
* 位置:在自定义的方法上面
*
* 一般为私有的,无需外面调用
*
* 特点:当使用@Pointcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名
* 其他的通知中,value属性就可以使用这个方法名称,代替切入点表达式
*/
@Pointcut("execution(* *..SomeService.doThird(..))")
public void myPointcut(){
//无需代码
}
}
cglib动态代理
目标类没有接口,使用cglib动态代理,spring框架会自动应用cglib
com.yixia.ba01.SomeServiceImpl…EnhancerBySpringCGLIB…758f9bee
如果想要在没有接口的情况下使用cglib动态代理
需要在applicationContext配置文件中,配置,就会使用cglib动态代理,cglib动态代理的速度快一点
<aop:aspectj-autoproxy proxy-target-class="true"/>
spring整合mybatis
整合后,事务是自动提交的
配置文件的具体内容:spring整合mybatis框架
spring事务处理
1.使用事务管理器(一个接口和众多实现类)
接口PlatformTransactionManager,定义了事务的方法,
实现类:spring将每种数据库访问技术对应的事务对应对实现类都创建好了
只需要的配置文件中声明bean即可
2.说明方法需要的事务
(1)事务的隔离级别
(2)事务的超市时间
(3)事务的传播行为
spring框架中提供的事务处理方案
- 适合中小项目使用,注解方案
spring框架自己用aop实现给业务增加事务的功能,使用@Transactional注解增加事务
@Transactional是spring框架自己的注解,放在public方法的上面,表示当前方法具有事务。
可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等。
<!-- spring的事务处理
声明事务管理器
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 连接数据库-->
<property name="dataSource" ref="myDataSource"/>
</bean>
<!-- 开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象
transaction-manager代表事务管理器的id
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
/**
* @Transactional:事务注解,自动使用环绕通知
* @return
*/
@Transactional(
//传播行为
propagation = Propagation.REQUIRED,
//隔离级别
isolation = Isolation.DEFAULT,
//
readOnly = false,
//发生指定异常一点回滚
rollbackFor = {
ClassNotFoundException.class,NullPointerException.class
}
)
@Override
public List<Student> queryStudent() {
List<Student> students = studentDao.selectStudent();
return students;
}
- 适合大型项目,有很多的类,方法需要大量的配置事务,使用aspectJ框架功能,在spring配置文件中,声明类,方法需要的事务,这种方式业务方法和事务配置完全分离
实现步骤:都是在xml配置文件中实现的
1.使用aspectJ框架,需要加入依赖
2.声明事务管理器对象
3.声明方法的事务类型,(配置方法的事务属性,隔离级别,传播行为,超时)
4.配置aop:指定哪类需要
<!-- 声明式事务处理,和源代码完全分离的-->
<!-- 1.声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 连接数据库-->
<property name="dataSource" ref="myDataSource"/>
</bean>
<!--
2.声明业务方法它的事务属性(隔离级别,传播行为i,超时)
id:自定义名称,表示<tx:advice>和</tx:advice>之间的配置内容
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!-- tx:attributes:配置事务属性-->
<tx:attributes>
<!-- tx:method: 给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务属性
name:方法名称:1.完整的方法名称,不带有包和类
2.方法可以使用通配符,8表示任意字符
还有其他属性,为事务的配置属性
rollback-for:指定的异常类名,发生异常一定回滚
-->
<tx:method name="" propagation="" isolation="" rollback-for=""/>
</tx:attributes>
</tx:advice>
<!-- 配置aop-->
<aop:config>
<!-- 配置切点点表达式:指定那些表中的类,要用事务
id:自定义,切入点表示的名称,唯一值
expression:切入点表达式,指定哪些类需要事务,aspectJ会创建代理对象
-->
<aop:pointcut id="servicePt" expression="execution(* *(..))"/>
<!-- 配置增强器:关联advice和pointcut
advice-ref:通知,上面tx:advice那里的配置
pointcut-ref:切入点表达式的id
-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
</aop:config>