一、控制反转-Inversion Of Control
1.关于IOC
业务层调用持久层,并且此时业务层在依赖持久层的接口和实现类。如果此时没有持久层实现类,编译将不能通过。这种编译期依赖关系,应该在我们开发中杜绝。在应用加载时,创建一个 Map,用于存放三层对象。我们获取对象时,可以直接跟工厂要,有工厂为我们查找或者创建对象。是被动的。
2.基于 XML 的配置
配置文件:
<!-- bean标签:用于配置让spring创建对象,并且存入ioc容器之中
id 属性:对象的唯一标识。
class 属性:指定要创建对象的全限定类名
-->
<!-- 配置 service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> </bean>
<!-- 配置 dao -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>
测试类:
public static void main(String[] args) {
//1.使用 ApplicationContext 接口,就是在获取 spring 容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据 bean 的 id 获取对象
IAccountService aService = (IAccountService) ac.getBean("accountService"); System.out.println(aService);
IAccountDao aDao = (IAccountDao) ac.getBean("accountDao"); System.out.println(aDao);
}
先通过ApplicationContext指定spring容器(也就是那个配置文件),然后直接用getBean(bean的id)从spring中获得想要的对象,就不用new了。
3.BeanFactory 和 ApplicationContext 的区别
ApplicationContext是BeanFactory的子接口。大部分情况下都使用ApplicationContext。
ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。 BeanFactory:什么使用什么时候创建对象。
4.什么是依赖注入
依赖注入:Dependency Injection。它是spring框架核心ioc的具体实现。 我们的程序在编写时,通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。
ioc 解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。 那这种业务层和持久层的依赖关系,在使用 spring 之后,就让 spring 来维护了。也就是我们告诉spring容器,我们需要的这个对象(A)里面又需要哪些成员(可以是基本数据类型和 String 类型,也可以是其他bean类型比如对象B),spring容器帮我们把这些成员注入到A之后再交给我们A,A、B都需要提前放在spring容器中。
5.通过配置文件注入
1.通过构造函数注入
<!-- 使用构造函数的方式,给 service 中的属性传值
要求:类中需要提供一个对应参数列表的构造函数。
涉及的标签:constructor-arg
属性: index:指定参数在构造函数参数列表的索引位置
type:指定参数在构造函数中的数据类型
name:指定参数在构造函数中的名称 用这个找给谁赋值
=======上面三个都是找给谁赋值,下面两个指的是赋什么值的==============
value:它能赋的值是基本数据类型和 String 类型
ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="张三"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
<bean id="now" class="java.util.Date">
2.通过set方法注入(常用!!!)
<!-- 通过配置文件给 bean 中的属性传值:使用 set 方法的方式 涉及的标签:
property
属性:name:找的是类中 set 方法后面的部分 (setName的name,setAge的age)
ref:给属性赋值是其他 bean 类型的
value:给属性赋值是基本数据类型和 string 类型的
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="name" value="test"></property>
<property name="age" value="21"></property>
<property name="birthday" ref="now"></property>
<bean id="now" class="java.util.Date">
6.基于注解的IOC
解配置和 xml 配置要实现的功能都是一样的,都是要降低程序间的耦合。只是配置的形式不一样。当我们使用注解注入时,set 方法不用写。
7.创建 spring 的 xml 配置文件并开启对注解支持
<!-- 告知 spring 创建容器时要扫描的包 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
8.常用注解
1.用于创建对象的,相当于:bean id="" class=""
@Component
value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写。
@Controller: 一般用于表现层的注解。
@Service: 一般用于业务层的注解。
@Repository: 一般用于持久层的注解。
他们的作用及属性和@Component都是一模一样的。
2.用于注入数据的,相当于:property name="" ref="“或value=”"
@Autowired
自动按照类型注入。当使用注解注入属性时,set 方法可以省略。它只能注入其他 bean 类型。当有多个 类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到 就报错。
@Value: 注入基本数据类型和 String 类型数据的
9.spring 的纯注解配置
我们发现,之所以我们现在离不开 xml 配置文件,是因为我们有一句很关键的配置:
<context:component-scan base-package="com.itheima"></context:component-scan>
如果他要也能用注解配置,那么我们就离脱离 xml 文件又进了一步。
另外,数据源和 JdbcTemplate 的配置也需要靠注解来实现。
@Configuration: 用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用AnnotationApplicationContext(有@Configuration 注解的类.class)。
@ComponentScan: 用于指定 spring 在初始化容器时要扫描的包。
@Configuration
@ComponentScan("com.itheima") public class SpringConfiguration {
}
指明这是一个配置类,spring初始化容器时要扫描的包是”com.itheima“。
@Bean: 该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。
@Bean(name="dbAssit")
public createDBAssit(DataSource dataSource) {
return new DBAssit(dataSource);
}
@Component作用在类上,表明一个类会作为组件类,并告知Spring要为这个类创建bean。而@Bean是作用在方法上的,告诉Spring这个方法将会返回一个对象,这个对象要注册为Spring应用上下文中的bean。通常方法体中包含了最终产生bean实例的逻辑。
@PropertySource: 用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到 properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。
此时我们已经有了两个配置类,但是他们还没有关系。如何建立他们的关系呢?
@Import: 用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。当然,写上也没问题。
@Configuration
@ComponentScan(basePackages = "com.itheima.spring")
@Import({ JdbcConfig.class})
public class SpringConfiguration {
}
@Configuration //可以不写
@PropertySource("classpath:jdbc.properties")
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;
/**
* 创建一个数据源,并存入 spring 容器中 * @return
*/
@Bean(name="dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password); return ds;
} catch (Exception e) {
throw new RuntimeException(e);
} }
}
}
jdbc.properties 文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/day44_ee247_spring
jdbc.username=root
jdbc.password=1234
10.通过注解获取容器
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
二、AOP
1.AOP概述
AOP:全称是 Aspect Oriented Programming 即:面向切面编程。简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的 基础上,对我们的已有方法进行增强。
2.动态代理常用的两种方式
1.基于接口的动态代理:
提供者:JDK 官方的 Proxy 类。
要求:被代理类最少实现一个接口。
在很久以前,演员和剧组都是直接见面联系的。没有中间人环节。
而随着时间的推移,产生了一个新兴职业:经纪人(中间人),这个时候剧组再想找演员就需要通过经纪人来找了。
2.基于子类的动态代理
提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。
要求:被代理类不能用 final 修饰的类(最终类)。
3.Spring 中的 AOP
1.相关术语
Joinpoint(连接点 ):所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。就是指对哪些方法进行拦截。
Pointcut(切入点 ):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。就是指要对哪些方法进行增强。
Advice(通知/增强):所谓通知是指拦截到Joinpoint 之后所要做的事情就是通知。通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。就是在对某个方法的一些增强。
Introduction(引介 ):引介是一种特殊的通知在不修改类代码的前提下,Introduction 可以在运行期为类动态地添加一些方法或 Field。
Target(目标对象 ):代理的目标对象。
Weaving(织入 ): 是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。 Proxy(代理) :一个类被 AOP 织入增强后,就产生一个结果代理类。 Aspect(切面 ):是切入点和通知(引介)的结合。
4.基于 XML 的 AOP 配置
1.前置通知、后置通知、异常通知、最终通知
先配置spring的ioc,然后准备一个通知类(增强类),里面是需要制作成通知的信息。
public class TransactionManager {
//定义一个 DBAssit
private DBAssit dbAssit ;
public void setDbAssit(DBAssit dbAssit) { this.dbAssit = dbAssit;
}
//开启事务(前置)
public void beginTransaction() {
try {
dbAssit.getCurrentConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
//提交事务(后置)
public void commit() {
try {
dbAssit.getCurrentConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
//回滚事务(异常)
public void rollback() {
try {
dbAssit.getCurrentConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
//释放资源(最终)
public void release() {
try {
dbAssit.releaseConnection();
} catch (Exception e) {
e.printStackTrace();
}
}
}
先把通知类放到ioc容器中
<bean id="txManager" class="com.itheima.utils.TransactionManager">
<property name="dbAssit" ref="dbAssit"></property>
</bean>
然后配置aop
<aop:config>
<aop:aspect id="txAdvice" ref="txManager">
<!--配置通知的类型要写在此处-->
<!--用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。-->
<aop:pointcut expression="execution(public void com.itheima.service.impl.AccountServiceImpl.transfer( java.lang.String, java.lang.String, java.lang.Float))" id="pt1"/>
<!--配置前置通知,在切入点方法执行之前执行-->
<aop:before method="beginTransaction" pointcut-ref="pt1"/>
<!--配置后置通知,切入点方法正常执行之后。它和异常通知只能有一个执行-->
<aop:after-returning method="commit" pointcut-ref="pt1"/>
<!--配置异常通知,它和后置通知只能执行一个-->
<aop:after-throwing method="rollback" pointcut-ref="pt1"/>
<!--配置最终通知,无论切入点方法执行时是否有异常,它都会在其后面执行-->
<aop:after method="release" pointcut-ref="pt1"/>
</aop:aspect>
</aop:config>
3.环绕通知
配置环绕通知
<aop:config>
<aop:pointcut expression="execution(*com.itheima.service.impl.*.*(..))" id="pt1"/>
<aop:aspect id="txAdvice" ref="txManager">
<!-- 配置环绕通知 -->
<aop:around method="transactionAround" pointcut-ref="pt1"/>
</aop:aspect>
</aop:config>
在环绕通知执行时,spring 框架会为我们提供该接口的实现类对象,我们直接使用就行。
public Object transactionAround(ProceedingJoinPoint pjp) {
//定义返回值
Object rtValue = null;
try {
//获取方法执行所需的参数
Object[] args = pjp.getArgs(); //前置通知:开启事务
beginTransaction();
//执行方法
rtValue = pjp.proceed(args); //后置通知:提交事务
commit();
}catch(Throwable e) {
//异常通知:回滚事务 rollback();
e.printStackTrace();
}finally {
//最终通知:释放资源
release();
}
return rtValue;
}
5.基于注解的 AOP 配置
1.前置通知、后置通知、异常通知、最终通知
把通知类也使用注解配置,在通知类上使用@Aspect 注解声明为切面(切面类就是用来给别的类增强方法的类)。
@Component("txManager")
@Aspect//表明当前类是一个切面类
public class TransactionManager {
//定义一个 DBAssit
@Autowired
private DBAssit dbAssit ;
//开启事务
@Before("execution(* com.itheima.service.impl.*.*(..))") //前置通知
public void beginTransaction() {
try {
dbAssit.getCurrentConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
//提交事务
@AfterReturning("execution(* com.itheima.service.impl.*.*(..))") //后置通知
public void commit() {
try {
dbAssit.getCurrentConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
//回滚事务
@AfterThrowing("execution(* com.itheima.service.impl.*.*(..))") //异常通知
public void rollback() {
try {
dbAssit.getCurrentConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
//释放资源
@After("execution(* com.itheima.service.impl.*.*(..))") //最终通知
public void release() {
try {
dbAssit.releaseConnection();
} catch (Exception e) {
e.printStackTrace();
}
}
}
最后在 spring 配置文件中开启 spring 对注解 AOP 的支持
<aop:aspectj-autoproxy/>
2.环绕通知
@Around("execution(* com.itheima.service.impl.*.*(..))")
public Object transactionAround(ProceedingJoinPoint pjp) {
//定义返回值
Object rtValue = null;
try {
//获取方法执行所需的参数
Object[] args = pjp.getArgs();
//前置通知:开启事务
beginTransaction();
//执行方法
rtValue = pjp.proceed(args);
//后置通知:提交事务
commit();
}catch(Throwable e) {
//异常通知:回滚事务
rollback();
e.printStackTrace();
}finally {
//最终通知:释放资源
release();
}
return rtValue;
}