1.Spring IoC
1.1 IOC原理
将对象创建交给Spring去管理。
1.2 实现IOC的两种方式
- IOC配置文件的方式
- IOC注解的方式
1.3 Spring IOC底层实现原理
IOC底层实现使用的技术:
- xml配置文件
- dom4j解析xml文件
- 工厂模式
- 反射
Spring的IoC的底层实现原理是工厂设计模式+反射+XML配置文件。 就拿持久层(也即dao层,数据访问对象)的开发来说,官方推荐做法是先创建一个接口,然后再创建接口对应的实现类。所以,我会以dao层的开发为例来证明Spring的IoC的底层实现原理就是工厂设计模式+反射+XML配置文件。首先,创建一个Userdao接口。
public interface UserDao {
public void add();
}
创建Userdao接口的一个实现类
public class UserDaoImpl implements UserDao {
public void add() {
balabala......
}
}
接着,我们在service层中调用dao层
// 接口 实例变量 = new 实现类
UserDao dao = new UserDaoImpl();
dao.add();
这时我们便可发现一个缺点:service层和dao层耦合度太高了,即接口和实现类有耦合(它俩之间的联系过于紧密),一旦切换底层实现类,那么就需要修改源代码,现的这个问题该如何解决呢?解决方法是使用工厂设计模式进行解耦合操作。所以,我们需要创建一个工厂类,在工厂类中提供一个方法,返回实现类的对象。
public class BeanFactory {
// 提供返回实现类对象的方法
public static UserDao getUserDao() {
return new UserDaoImpl();
}
}
这样,在service层中调用dao层的核心代码就变为了下面的样子。
UserDao dao = BeanFactory.getUserDao();
dao.add();
这样又产生了一个缺点:现在接口和实现类之间是没有耦合了,但是service层和工厂类耦合了。如果真正想实现程序之间的解耦合,那么就需要使用到工厂设计模式+反射+XML配置文件了。所以,我们这里提供一个XML配置文件,并且该配置文件中有如下配置信息。
<bean id="userDao" class="com.meimeixia.dao.impl.UserDaoImpl" />
然后再来创建一个工厂类,在工厂类中提供一个返回实现类对象的方法,但并不是直接new实现类,而是使用SAX解析配置文件,根据标签bean中的id属性值得到对应的class属性值,使用反射创建实现类对象。
public class BeanFactory {
public static Object getBean(String id) {
// 1.使用SAX解析得到配置文件内容
// 直接根据id值userDao得到class属性值
String classvalue = "class属性值";
// 2.使用反射得到对象
Class clazz = Class.forName(classvalue);
UserDaoImpl userDaoImpl = (UserDaoImpl)lazz.newInstance();
return userDaoImpl;
}
}
以上就是Spring的IoC的底层实现原理。
2、解释Spring整合Servlet的背后细节
2.1 为什么要在web.xml中配置listener?
a. 这个listener是实现了ServletContextListener , 只要项目一发布,就会得到通知。
b. 因为它要感知到项目到底有没有发布, 以便我们能够立即创建出来工厂。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
2.2 为什么要在web.xml中配置context-param?
a. 如果不配置这个param, 那么spring的代码必然会固定到一个位置去找 applicationContext.xml 这个配置文件。
b. 由于这个配置文件,spring早期把命名的权利变得开放了。 也就是这个名字可以随便写。假设是这样,我们不用写这个context-param ,那么 spring必然要到默认| 固定位置去找一个固定名字的xml文件。
2.3 为什么在代码里面采用工具类也能获取到工厂,这个工厂创建出来之后到底放在了哪里?
要推理一下。 看看工厂创建完毕之后,对工厂做了什么操作, 为什么使用工具类可以得到工厂。
其实listener创建出来工厂之后,会把工厂存储到servletcontext作用域里面去。 这主要是方便在任何地方都能取得到这个工厂,而且这个工厂存活的时间还很长。
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
那么既然知道spring是存储到servletcontext去, 那么获取这个工厂,也可以使用以前的手法来获取它。
WebApplicationContext context = (WebApplicationContext) getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
但是spring考虑到这个属性的名字太长,程序员记不住,所以自己弄了一个工具类出来,只让我们记住工具方法,也可以得到工厂。
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
3、IOC的注解开发
3.1 xml 和 注解配置的优缺点
3.11 xml
优点: 代码集中、配置都是集中在一个文件,查找比较方便
缺点: 代码太多。
3.12 注解
优点: 代码少 , 只要在类上面、 方法上 打个注解就OK
缺点: 配置比较分散。
Spring MVC 崇尚的是注解配置 、 Spring Boot 完全摒弃了xml 全部都是用注解.
3.2 ioc注解入门
- 导入jar包
spring-aop-xxx.jar //aop联盟包
- 导入context约束
<!-- 1. 导入约束 -->
<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" 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">
- 打开注解的扫描开关
<!-- 1. 配置扫描注解的开关 base-package : 如果有多个包要扫描,那么只要写上基础包即可。 -->
<context:component-scan base-package="com.cao.springIoc.test"/>
- 在托管的类上面打上注解,并且给定value值(也就相当于是id值)
@Component(value="us")
// Component : 组件 1. 托管这个类 , 2 .给这个类打上个标记 us
//us 等于 <bean id="us">
public class UserServiceImpl implements UserService {
3.3 注解创建对象
- 注解创建多实例
默认创建的实例还是单例的
@Scope(“prototype”)
- 给类打上注解,以便让spring托管它,并且和标识符形成映射关系,都是靠@Component注解来实现
- 但是由于web项目采用分层的结构, controller 、 service 、 dao 层,spring为了迎合这种趋势,它也把注解稍微再细分了一下:
@Repository(“userDao”) //持久层
@Service(“us”) //业务逻辑层
@Controller(“action”) //控制层
- 指定初始化&销毁方法
@PostConstruct //用于定义指定方法为初始化方法
public void init(){
System.out.println(“调用了UserServiceImpl的init方法~~”);
}
@PreDestroy //用于定义指定方法为销毁时调用的方法
public void destory(){
System.out.println("调用了UserServiceImpl的destroy方法~~");
}
3.4 属性注入(注解方式)
3.41 注入普通数据
普通数据其实可以不用注解的方式来注入,直接写上赋值即可。
@Value(“北京”)
private String address;
3.42 注入对象数据
3.421 早期的xml注入:
<!--1. 这行代码就是让spring托管这个类,然后给这个类打上一个标识符
在这个类上面打上注解
-->
<bean id="ud" class="com.cao.dao.impl.UserDaoImpl"></bean>
<bean id="us" class="com.cao.service.impl.UserServiceImpl">
<!-- 2. 把ud对应的类实例给注入到userDao中 -->
<property name="userDao" ref="ud"></property>
</bean>
3.422 注解的注入
- 在需要注入进来的那个类打上注解,让spring托管它
@Repository(“ud”)
public class UserDaoImpl implements UserDao{
- 在成员属性上使用注解来注入对象
@Resource(name="ud") //根据给定的标识符找到具体的类 name="" ====> ref=""
@Autowired //自动找到这个接口的实现类然后注入进来
private UserDao userDao ;
如果只有一种实现的情况,又懒得写太多,就可以直接使用 @Autowired 。 但是如果存在多种实现,又想指定具体的某一个实现,那么请使用 @Resource(name=“标识符”)
3.5 混合使用
如果在项目里面使用xml 和 注解 来完成IOC + DI , 那么他们是有分工的, 使用xml来完成IOC , 使用注解来完成DI
xml配置:
<bean id="us" class="com.itheima.service.impl.UserServiceImpl</bean>
<bean id="ud" class="com.itheima.dao.impl.UserDaoImpl"></bean>
<!-- 注解扫描开关 -->
<context:component-scan base-package="com.cao"/>
类上使用注解:
@Resource(name="ud") //直接指定具体的类,注入进来。
@Autowired //自动装配, 找到这个接口的实现类,然后注入进来
private UserDao userDao;
4.Spring测试
Spring测试其实是整合了以前的Junit, 或者称之为对Junit的封装。
- 导入jar包
spring-test-xxx.jar
spring-aop-xxx.jar
- 在注解类上声明注解
@RunWith(SpringJUnit4ClassRunner.class) // 在这里面升级了以前Junit的运行环境, 在里面兼备了创建工厂的代码
@ContextConfiguration("classpath:applicationContext.xml")
public class TestUserService {
- 使用属性注入注解,来获取对象
@Autowired
private UserService userService ;
5.AOP
5.1 什么是AOP呢?
AOP(Aspect Oriented Programming,面向切面编程),可以说是OOP(Object Oriented Programing,面向对象编程)的补充和完善。 其实就是在不改动源码的前提下,对原来的功能做升级 、 增强 、 扩展
5.2 AOP的底层实现原理
aop就是对方法进行升级、扩展、增强。 能够完成这个功能,学到的有、装饰者模式 、 代理模式(静态代理 、动态代理。) , aop的底层采用的是动态代理, 之所以不用前面两个,是因为它们需要我们创建出来代理类、 装饰类 . 动态代理又有两种实现手法, 一种是jdk的动态代理 , 另一种是cglib的动态代理。
- jdk动态代理
针对真实类(目标类) ,有实现接口。
@Test
public void testJDKProxy(){
//需求: 调用UserServiceImpl的save方法时, 先执行logger里面的log方法,完成对save的功能扩展
//1. 创建真实对象
final UserService userService = new UserServiceImpl();
//2. 创建代理对象
UserService proxyObj = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(), //类加载器, 真实类用什么, 代理类也跟着用什么
userService.getClass().getInterfaces(), //真实类实现了什么接口, 代理类也实现接口
new InvocationHandler() { //这里是new 接口的匿名实现类 。 给我们用来调用真实方法
/*
* method : 就是外面调用的方法引用 : save
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("~~~~");
if(method.getName().equals("save")){
Logger.log();
}
/* 正向调用
* userService.save();
userService.update();
userService.delete();
*/
/*
* 反射手法调用
* 参数一:obj :真实对象
* 参数二: args : 外面调用的方法参数。
*/
return method.invoke(userService , args);
//return null;
}
});
//3. 调用方法
proxyObj.save(); //userService.save();
/*proxyObj.update();
proxyObj.delete();*/
}
- cglib的动态代理
针对真实类(目标类) ,没有实现接口。 它就是一个普通类。
@Test
public void testCglibProxy(){
//1. 创建真实对象
final ProductService productServcie = new ProductService();
//2. 创建代理 enhancer 不是代理对象,由它创建代理
Enhancer enhancer = new Enhancer();
//设置父类是谁
enhancer.setSuperclass(ProductService.class);
//设置回调 , 就是用于调用真实对象的方法
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
System.out.println("~~!~~!");
Logger.log();
//productServcie.save();
return arg1.invoke(productServcie, arg2);
}
});
//得到代理对象
ProductService proxyObj = (ProductService) enhancer.create();
proxyObj.save();
}
5.3 AOP 的术语
5.4 AOP 入门
- 定义业务逻辑类
public class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("调用了UserServiceImpl 的 save方法");
}
}
- 定义增强类
public class Logger {
public static void log(){
System.out.println("输出日志了~~");
}
}
- 导入jar包
a. 导入 spring必须的jar
b. 额外导入:
spring-aop-xx.jar,spring-aspect-xx.jar
面向切面过程中,Spring AOP是遵循了AOP联盟的规范实现的,所以需要有AOP联盟的接口包
aopalliance-x.x.jar,接口包依赖aspectjweaver-x.x.x.jar
- 导入约束
要导入aop的约束 - 让spring托管 业务逻辑类 和 增强类
<bean id="us" class="com.itheima.service.impl.UserServiceImpl" ></bean>
<bean id="logger" class="com.itheima.util.Logger" ></bean>
- 配置AOP
<!-- 下面的配置核心,就是把logger里面的log方法应用到us里面的save方法中 -->
<!-- 开始aop的配置 -->
<aop:config>
<!-- 就是用于表示,要对哪一个方法进行增强。 其实就是定义一种规则,然后spring根据这种规则就能够找到对应的方法
后面将要对这些找到的方法进行增强
execution(* com.xyz.myapp.service.*.*(..))
execution : 固定写法,表示执行
第一个* : 表示任意返回值
com.xyz.myapp.service : 表示这个包
第二个* : 表示上面这个包的任意类
第三个* 表示上面找到的类的任意方法
(..) : 表示任意参数
spring根据这个表达式已经找到了具体对应的方法。
-->
<aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="savePointCut"/>
<!-- 还缺少,把什么方法应用到上面找到的那些方法之上,去完成增强
连起来的意思就是: 把一个叫做logger的这个bean 里面的一个方法叫做 log 的方法,用到 一个叫做savePointCut 切入点找到
的那些方法上面去,执行前置增强
-->
<aop:aspect ref="logger">
<!-- 定义前置增强
根据上面的表达式找到的切面,其实就是找到的那些方法,给他们应用前置增强, 把一个叫做log的方法用到他们前面去。
-->
<aop:before method="log" pointcut-ref="savePointCut"/>
</aop:aspect>
</aop:config>
5.5 AOP 增强
配置aop
<aop:config>
<!-- 配置切入点 spring根据这个规则,找到具体的方法
xml方式不好控制具体哪一个方法
-->
<aop:pointcut expression="execution(* com.itheima.service.impl.*.save(..))" id="pointCut01"/>
<!-- 配置切入点 spring根据这个规则,找到具体的方法
xml方式不好控制具体哪一个方法
-->
<aop:pointcut expression="execution(* com.itheima.service.impl.*.save(..))" id="pointCut01"/>
<!-- 这个是对事务操作时,才有用 -->
<!-- <aop:advisor advice-ref=""/> -->
<aop:aspect ref="logger">
<!-- 前置增强 -->
<!-- <aop:before method="log" pointcut-ref="pointCut01"/> -->
<!-- 后置增强 -->
<!-- <aop:after method="log" pointcut-ref="pointCut01"/> -->
<!-- 环绕增强 -->
<!-- <aop:around method="around" pointcut-ref="pointCut01"/> -->
<!-- 异常增强 -->
<!-- <aop:after-throwing method="log" pointcut-ref="pointCut01"/> -->
<!-- 最终增强 -->
<aop:after-returning returning="result" method="log02" pointcut-ref="pointCut01"/>
</aop:aspect>
6.总结
- IOC的注解
- Spring测试