Spring的AOP和IOC

IOC

基本介绍

Spring的核心思想之一:Inversion of Control , 控制反转 IOC
所谓控制反转,就是对象的创建以及生命周期的管理交给外部容器完成,这个开发过程中不再需要关注对象的创建和生命周期的管理,而是在需要的时由Spring框架提供。

为什么

对象的构建如果依赖非常多的对象,且层次很深,外层在构造对象时很麻烦且不一定知道如何构建这么多层次的对象。 IOC 帮我们管理对象的创建,只需要在配置文件里指定如何构建,每一个对象的配置文件都在类编写的时候指定了,所以最外层对象不需要关心深层次对象如何创建的,前人都写好了。

简单使用过程

第一步

用配置文件配置类

简单来说,就是利用外部的配置文件来管理对象的创建与生命周期
在spring的配置文件的xml中,一般名为application.xml,在其中定义需要装配的Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 使用spring管理对象的创建,还有对象的依赖关系 -->    
<bean id="userManager" class="scau.zzf.service.UserMessage">
<!-- (1)UserMessageImpl使用了userDao,Ioc是自动创建相应的UserDao实现,都是由容器管理-->
<!-- (2)在UserMessageImpl中提供构造函数,让spring将UserDao实现注入(DI)过来 -->
<!-- (3)让spring管理我们对象的创建和依赖关系,必须将依赖关系配置到spring的核心配置文件中 -->
<property name="userDao" ref="UserDao"></property>
<!-- 构造注入 -->
<!-- <constructor-arg ref="userDao"/> -->
</bean>
<bean id="UserDao" class="scau.zzf.Dao.UserDao">
<bean id="user" class="User">

<!--通过constructor这个节点来指定构造函数的参数类型、名称、第几个-->
<constructor-arg index="0" name="id" type="java.lang.String" value="1"></constructor-arg>
<constructor-arg index="1" name="username" type="java.lang.String" value="zhongfucheng"></constructor-arg>
</bean>
</bean>

注解方法配置

除了用配置文件的方式定义类信息外,也可以通过注解的方式
常用的注解:
@ComponentScan扫描器
@Configuration表明该类是配置类
@Component 指定把一个对象加入IOC容器—>@Name也可以实现相同的效果【一般少用】
@Repository 作用同@Component; 在持久层使用
@Service 作用同@Component; 在业务逻辑层使用
@Controller 作用同@Component; 在控制层使用
@Resource 依赖关系
如果@Resource不指定值,那么就根据类型来找,相同的类型在IOC容器中不能有两个
如果@Resource指定了值,那么就根据名字来找

实例:
DAO层

1
2
3
4
5
6
7
8
9
10
11
import org.springframework.stereotype.Repository;
//把对象添加到容器中,首字母会小写
@Repository
public class UserDao {

public void save() {
System.out.println("DB:保存用户");
}


}

Service层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.stereotype.Service;

import javax.annotation.Resource;


//把UserService对象添加到IOC容器中,首字母会小写
@Service
public class UserService {

//如果@Resource不指定值,那么就根据类型来找--->UserDao....当然了,IOC容器不能有两个UserDao类型的对象
//@Resource

//如果指定了值,那么Spring就在IOC容器找有没有id为userDao的对象。
@Resource(name = "userDao")
private UserDao userDao;

public void save() {
userDao.save();
}
}

Controller层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.stereotype.Controller;

import javax.annotation.Resource;

/**
* Created by ozc on 2017/5/10.
*/

//把对象添加到IOC容器中,首字母会小写
@Controller
public class UserController {

@Resource(name = "userService")
private UserService userService;

public String execute() {
userService.save();
return null;
}
}

配置类配置(利用Bean注解)

另外,也可以创建一个配置类来管理Bean
对于被bean修饰的方法:
使用@Bean来修饰方法,该方法返回一个对象。
不管方法体内的对象是怎么创建的,Spring可以获取得到对象就行了。
Spring内部会将该对象加入到Spring容器中
容器中bean的ID默认为方法名

1
2
3
4
5
6
7
8
9
10
11
12
@org.springframework.context.annotation.Configuration
public class Configuration {

@Bean
public UserDao userDao() {

UserDao userDao = new UserDao();
System.out.println("我是在configuration中的"+userDao);
return userDao;
}

}

第二步

XmlBeanFactory读取xml文件,从而获取相关的Bean信息。

1
2
3
4
5
6
7
8
public class test {
public static void main(String[] args) throws Exception {
BeanFactory factory=new XmlBeanFactory(new FileSystemResource("src/appllication.xml"));
UserMessage userMessage=(UserMessage)factory.getBean("UserMessage");
userMessage.add("message");

}
}

这是获取IOC容器对象的一种方式,另外还有

1
2
3
4
5
//加载Spring的资源文件
Resource resource = new ClassPathResource("applicationContext.xml");

//创建IOC容器对象【IOC容器=工厂类+applicationContext.xml】
BeanFactory beanFactory = new XmlBeanFactory(resource);

1
2
3
4
// 得到IOC容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

System.out.println(ac);

细节研究

依赖注入

spring ioc容器初始化好bean的实例对象之后,会对该对象中的属性进行初始化,初始化的过程依然是由容器自动来完成,这个被称为是依赖注入(dependency injection缩写是DI)。spring里面常用的注入方式有两种,setter方法注入,构造方法注入。

Bean属性

1) 对象创建: 单例/多例
scope=”singleton”, 默认值, 即 默认是单例 【service/dao/工具类】
scope=”prototype”, 多例; 【Action对象】

2) 什么时候创建?
scope=”prototype” 在用到对象的时候,才创建对象。
scope=”singleton” 在启动(容器初始化之前), 就已经创建了bean,且整个应用只有一个。

3)是否延迟创建
lazy-init=”false” 默认为false, 不延迟创建,即在启动时候就创建对象
lazy-init=”true” 延迟初始化, 在用到对象的时候才创建对象(只对单例有效)

4) 创建对象之后,初始化/销毁
init-method=”init_user” 【对应对象的init_user方法,在对象创建之后执行 】
destroy-method=”destroy_user” 【在调用容器对象的destroy方法时候执行,(容器用实现类)】

AOP

代理模式

https://www.junglezero.top/2019/07/26/%E6%A8%A1%E5%BC%8F%E6%80%BB%E7%BB%93/

基本介绍

https://my.oschina.net/guangshan/blog/1797461
所谓AOP,是aspect object programming 面向切面编程
面向切面编程就是指: 对很多功能都有的重复的代码抽取,再在运行的时候往业务方法上动态植入“切面类代码”
关注点:一套业务中多次出现的代码
切面:把各个关注点抽离出来,构成一个类
切入点:执行目标对象方法,动态植入切面代码。
可以通过切入点表达式,指定拦截哪些类的哪些方法; 给指定的类在运行的时候植入切面类代码。

过程

引入jar(Maven)
spring-aop-3.2.5.RELEASE.jar 【spring3.2源码】
aopalliance.jar 【spring2.5源码/lib/aopalliance】
aspectjweaver.jar 【spring2.5源码/lib/aspectj】或【aspectj-1.8.2\lib】
aspectjrt.jar 【spring2.5源码/lib/aspectj】或【aspectj-1.8.2\lib】
bean.xml中引入aop名称空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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:p="http://www.springframework.org/schema/p"
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="aa"/>

<!-- 开启aop注解方式 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

切面类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
@Aspect//指定为切面类
public class AOP {


//里面的值为切入点表达式
@Before("execution(* aa.*.*(..))")
public void begin() {
System.out.println("开始事务");
}


@After("execution(* aa.*.*(..))")
public void close() {
System.out.println("关闭事务");
}
}

IUser接口

1
2
3
public interface IUser {
void save();
}

UserDao

1
2
3
4
5
6
7
8
9
@Component
public class UserDao implements IUser {

@Override
public void save() {
System.out.println("DB:保存用户");
}

}

Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class App {

public static void main(String[] args) {

ApplicationContext ac =
new ClassPathXmlApplicationContext("aa/applicationContext.xml");

//这里得到的是代理对象....
IUser iUser = (IUser) ac.getBean("userDao");

System.out.println(iUser.getClass());

iUser.save();

}
}

upload successful

若目标对象没有接口

1
2
3
4
5
6
7
8
9
@Component
public class OrderDao {

public void save() {

System.out.println("我已经进货了!!!");

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class App {

public static void main(String[] args) {

ApplicationContext ac =
new ClassPathXmlApplicationContext("aa/applicationContext.xml");

OrderDao orderDao = (OrderDao) ac.getBean("orderDao");

System.out.println(orderDao.getClass());

orderDao.save();

}
}

upload successful

注解

@Aspect 指定一个类为切面类

@Pointcut(“execution(* cn.itcast.e_aop_anno..(..))”) 指定切入点表达式

@Before(“pointCut_()”) 前置通知: 目标方法之前执行

@After(“pointCut_()”) 后置通知:目标方法之后执行(始终执行)

@AfterReturning(“pointCut_()”) 返回后通知: 执行方法结束前执行(异常不执行)

@AfterThrowing(“pointCut_()”) 异常通知: 出现异常时候执行

@Around(“pointCut_()”) 环绕通知: 环绕目标方法执行

使用例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 前置通知 : 在执行目标方法之前执行
@Before("pointCut_()")
public void begin(){
System.out.println("开始事务/异常");
}

// 后置/最终通知:在执行目标方法之后执行 【无论是否出现异常最终都会执行】
@After("pointCut_()")
public void after(){
System.out.println("提交事务/关闭");
}

// 返回后通知: 在调用目标方法结束后执行 【出现异常不执行】
@AfterReturning("pointCut_()")
public void afterReturning() {
System.out.println("afterReturning()");
}

// 异常通知: 当目标方法执行异常时候执行此关注点代码
@AfterThrowing("pointCut_()")
public void afterThrowing(){
System.out.println("afterThrowing()");
}

// 环绕通知:环绕目标方式执行
@Around("pointCut_()")
public void around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕前....");
pjp.proceed(); // 执行目标方法
System.out.println("环绕后....");
}

引入point_cut切点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component
@Aspect//指定为切面类
public class AOP {


// 指定切入点表达式,拦截哪个类的哪些方法
@Pointcut("execution(* aa.*.*(..))")
public void pt() {

}

@Before("pt()")
public void begin() {
System.out.println("开始事务");
}


@After("pt()")
public void close() {
System.out.println("关闭事务");
}
}