Spring IOC DI AOP
前言
框架
IOC框架(用于解决对象创建,基于控制反转思想):Spring、Android Spring、butterknife
ORM框架(用于处理数据和对象联系,基于对象关系映射):mybatis、Hibernate等
Spring
spring公司开发的一个开源java框架
核心思想
- 控制反转InversionOfControl
把对象的控制权(创建、销毁、对象赋值、属性赋值)交给别人(Spring)实施
依赖注入DependencyInjection(控制反转的实现方式)
为对象及其内部属性赋值 - 面向切面编程AspectsOreintedProgramming
作用/优点
- 方便解 耦 ,简化开发,提高效率
- 支持AOP、IOC
- 声明式事物管理
- 方便测试(Junit4支持,通过注解测试)
- 可以充当容器,支持集成各种开源框架
- 封装了常用难用的JAVAEE API,降低其使用难度
核心模块(开发时需要的核心依赖)
spring-Bean 用于Beanfactory装配和对象创建(IOC)
spring-core 核心控件,实现IOC和AOP的基础
spring-context 扩展支持
IOC&DI
XML方式
构造函数创建对象
@Test
public void test1(){
//根据xml文件名加载配置文件(若不指定延迟加载Bean对象,加载xml文件时就创建对象)
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//根据xml文件绝对路径加载配置文件(了解即可)
ApplicationContext ac2 = new FileSystemXmlApplicationContext("F:\\idea\\D1217\\spring01\\src\\main\\resources\\applicationContext.xml");
//对象创建原理:XML加载、解析+反射
//1. xml解析:通过class属性获得全类名
//2. 反射创建对象: Class.forName("全类名")获取字节码对象---字节码对象.newInstance获得类对象
//3. 反射注入依赖:字节码对象.getDeclaredFileds获取属性数组,后遍历并setAccessible(true)开启暴力反射,后调用属性对象的set方法赋值
Person p = ac.getBean("p2",Person.class);
System.out.println(p.play());
System.out.println(p);
}
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- scope 单例模式(对象只创建一次,取多少次Bean得到的对象地址值都一样)-->
<!-- lazy-init 延迟加载:项目对系统性能要求较高时建议开启,优化内存占用的性能(非延迟加载:确保对象一定存在)-->
<!-- 原型模式(每次都重新创建对象,按Bean中配置的属性值为原型,属性一致但地址值不同) -->
<!-- name 名称,不建议重复,创建对象时定位常用-->
<!-- id 唯一标识不可重复-->
<!-- class 创建对象的类-->
<bean id="p1" name="p1" class="com.zoya.spring.domain.Person" scope="prototype">
</bean>
<bean id="p2" name="p2" class="com.zoya.spring.domain.Person" lazy-init="true">
<constructor-arg name="age" value="12"></constructor-arg>
<constructor-arg name="name" value="小辣鸡"></constructor-arg>
</bean>
</beans>
创建对象时方法的执行顺序
-
静态代码块
-
Bean的生命周期
2-1. 构造函数(多例模式每次创建对象执行一次,单例模式首次创建执行一次) 2-2. 初始化方法(每次调用Bean构造对象成功后都会执行1次) 2-3. 销毁方法 在Bean标签中配置后仍需要使用applicationcontext对象手动调用destroy或close(销毁整个application)
工厂方法创建对象
<!-- 静态工厂-一步到位地创建工厂对象并调用创建STu对象的【静态】方法,最终返回Stu对象-->
<bean name="StuStaticFactory" class="com.zoya.factory.StaticFactory" factory-method="createStu" />
<!-- 实例工厂:1. 创建工厂对象(为其命名StuInstanceFacBean)-->
<bean class="com.zoya.factory.InstanceFactory" name="StuInstanceFacBean"></bean>
<!-- 2.通过factory-Bean调用前面创建的工厂对象,工厂对象调用创建STU对象的方法,返回STU对象-->
<bean factory-bean="StuInstanceFacBean" name="StuInstanceFactory" factory-method="createStuInstance"></bean>
public class Demo {
@Test
public void test(){
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("appContext.xml");
//xml文件中直接指定了工厂方法,将在创建完工厂对象后调用工厂方法,返回Student对象
Student stu = ac.getBean("StuInstanceFactory",Student.class);
Student stu2 = ac.getBean("StuStaticFactory",Student.class);
//xml文件中不指定方法等价于以下代码
// StaticFactory stuFac = ac.getBean("StuStaticFactory",StaticFactory.class);
// Student stu = stuFac.createStu();
System.out.println(stu);
System.out.println(stu2);
}
依赖注入DI
不同xml文件中的值互相引用需要在beans内,最前面一行用import的resource属性引入其他xml
同一个bean中可以掺杂不同的注入方式,但不可以重复赋值
-
配置property,通过set方法注入
实现原理还是xml解析+反射-调用set方法
-
全参构造函数
构造函数若存在多个重载(参数类型和数量完全一致,只是顺序不同时),需要通过type指定参数类型或index指定参数位置,以定位唯一的构造方法
-
P命名空间
p(properties) 在文件声明中指定命名空间p
bean中指定类名
----> 在bean标签中 直接通过 p:属性名 直接赋值
-
SPEL表达式
“#{a1.name}” xml中name为a1的bean节点的name属性的值
-
复杂类型(集合类型)注入
<bean name="col1" class="com.zoya.domain.Colles">
<!-- List对应 List(推荐)/array(不推荐)节点-->
<property name="l" >
<!-- <array>-->
<!-- <value>111</value>-->
<!-- <value>222</value>-->
<!-- </array>-->
<list>
<value>111</value>
<value>222</value>
</list>
</property>
<!-- Array数组对应 array节点-->
<property name="os" >
<array>
<value>AAA</value>
<value>BBB</value>
</array>
</property>
<!-- map对应map节点(下设entry节点,用name和value属性存放键值对)-->
<property name="m" >
<map>
<entry key="啦啦" value="~~"/>
<entry key="哈哈" value="!!"/>
</map>
</property>
<!-- set对应set节点(set无序,没有key,直接value赋值即可)-->
<property name="s" >
<set>
<value>(~ ̄▽ ̄)~)</value>
<value>(#^.^#))</value>
</set>
</property>
<!-- properties对应props节点(properties类用于装载properties文件,本质是map)-->
<property name="p" >
<props>
<prop key="url">value1</prop>
<prop key="driverClassName">value2</prop>
</props>
</property>
</bean>
结果:
Colles{s=[(~ ̄▽ ̄)~), (#^.^#))], m={啦啦=~~, 哈哈=!!,}, l=[111, 222], os=[AAA, BBB], p={url=value1, driverClassName=value2}}
注解方式实现
<?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"
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">
<!--以上添加3行关于springframework.org/schema/context的内容-->
<!-- 声明需要被扫描注解的包-->
<context:component-scan base-package="com.zoya">
</context:component-scan>
</beans>
- 用于创建对象的注解
- @Component(name)声明这是一个组件,将会被创建对象
Component的子类注解:
- @Controller(name) 特指控制层组件
- @Repository (name)特指Dao层组件(数据访问组件)
- @Service (name)特指业务访问组件
- 用于测试类
-
@RunWith(SpringJUnit4ClassRunner.class)指定运行测试的类(规定为Runner的子类)
-
@ContextConfiguration(“classpath:annotation032.xml”)加载要执行的配置文件
替代原先的手动创建ApplicationContext对象 可以在一个测试类中形成一个全局的上下文对象,方便调用
- 用于注入值的注解
- @Value("") 简单类型赋值(也可以用于set方法)
- @AutoWired 自动装配(当变量是一个接口类型时,找其实现类,不配合Qualifier就默认按类型注入)
- @Qualifier(“组件名”) 定位自动装配的具体实现类,与AutoWired配合使用就是默认按名称注入
- @Resource(name=“组件名”) 等价于 autowired+Qualifier 是java原生注解
- 用于声明bean的bean属性的注解
- @PostConstruct 声明这是个初始化方法
- @PreDestroy 声明这是个销毁方法
- @Scope(scopeName=“singleton”) 声明作用域
- @Lazy(true/false) 延迟加载,默认true
AOP:AspectOrientedProgramming
使代码的颗粒度更细
有利于提高横向层次中代码的复用性
降低耦合度
实现原理:代理模式
要操作的类实现了接口:利用动态代理
要操作的类没有实现接口:利用cglib子代理
-
cglib子代理
1.导入cglib依赖 2.创建代理工厂类,实现MethodInterceptor接口 2.1 代理工厂类将目标类对象、增强功能类对象、enhancer对象作为私有属性 通过有参构造函数传参初始化目标类对象、增强功能类对象; 在构造函数中直接调用set初始化enhancer对象 setClassLoader--目标类.class.getClassLoader setSuperClass--复制创建的父本,即目标类.class setCallback--MethodInterceptor的实现类对象,即this(设置执行增强的方法) 2.2 代理工厂类重写MethodInterceptor接口方法Intercept 可以对Method进行筛选过滤,然后调用目标类方法并在前后调用增强类的方法 2.3 提供获取代理工厂对象的方法getCgProxyInstance,返回一个enhancer.create() 3.测试使用 创建工厂对象,使用getCgProxyInstance获取增强后的代理对象 强转成父类-目标类 调用方法
public class CglibProxy implements MethodInterceptor {
private UserDaoImpl target;
private Transaction adivice;
private Enhancer enhancer;
public CglibProxy(UserDaoImpl target, Transaction adivice) {
this.target = target;
this.adivice = adivice;
this.enhancer =new Enhancer();
Class c = target.getClass();
enhancer.setSuperclass(c);
enhancer.setClassLoader(c.getClassLoader());
//设置增强的回调函数,需要一个MethodInterceptor的实现类(本类就实现了,所以传this即可)
enhancer.setCallback(this);
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
String methodName = method.getName();
Object obj = null;
System.out.println("执行"+methodName);
//筛选不需要增强的方法,直接放行
if("select".equals(methodName)){
method.invoke(target, objects);
}else {
//其他方法前后先执行增强
adivice.beginT();
obj = method.invoke(target, objects);
adivice.commitT();
}
return obj;
}
public Object createProxyInstance(){
//创建一个以target为父类的代理增强对象
return enhancer.create();
}
}
public class Demo {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy(new UserDaoImpl(), new Transaction());
UserDaoImpl ud = (UserDaoImpl)proxy.createProxyInstance();
ud.select();
}
}
使用场景
检查网路安全性代码
事务
日志代码
异常处理
权限检查
核心概念
核心概念 | 理解 | 相关概念 |
---|---|---|
切点pointcut | 选定要切入(增加辅助功能)的位置 | 切点表达式 |
连接点joinpoint | 连接增强类和目标类的纽带,作为参数传入通知类的通知方法中,可以调用proceed方法执行目标方法 | proceedingJoinPoint等子类 |
目标对象Target | 核心代码所在的类,要被增强的类 | |
增强/通知方法Advice | 非核心的,辅助增强的代码 | 引介;切面类 |
织入Weaving | 是一个过程,实现“把增强添加到具体连接点”的过程 |
- 切点表达式
关键字( | 权限修饰符 | 返回值 | 全类名 | .方法名(参数类型) | ) |
---|---|---|---|---|---|
可省略 | 返回值 | 可以用通配符确定范围大小 | 参数类型可以用..表示通配 | ||
execution( | * | * | com.zoya.service.UserSe* | .insert(..) | ) |
使用
XML方式
//接口
public interface UserService {
int insert();
}
//实现类
public class UserServiceImpl implements UserService {
@Override
public int insert() {
System.out.println("添加用户");
return 6;
}
}
//增强类
public class Advice {
public void before(){
System.out.println("----前置通知----");
}
public void after(){
System.out.println("----最终通知----");
}
//环绕通知一旦显示写出,必须在其中调用proceed才能执行目标方法,并且必须返回目标方法的返回值
public Object around(ProceedingJoinPoint pj){
Object obj = null;
try {
obj = pj.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("----环绕通知----"+obj);
return obj;
}
public void afterThrowing(){
System.out.println("----异常通知----");
}
public void afterReturning(){
System.out.println("----后置通知----");
}
}
//测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:aop2.xml")
public class demo {
@Autowired
private UserService us;
@Test
public void test(){
int i = us.insert();
System.out.println(i);
}
}
/*
执行结果:
----前置通知----
添加用户
----环绕通知----6
----后置通知----
----最终通知----
6
*/
1.前置通知
一般用于检查网络环境
2.1环绕通知前半部分
一般用于身份校验、权限检查等
if(符合条件){
3.执行目标方法(proceed)
}else{
3.其他操作
}
2.2.环绕通知后半部分
4.后置通知(若目标方法无异常/异常已被捕获处理)
4.异常通知(若目标方法有异常且未被捕获而是被抛出)
一般用于信息采集、写入日志文件
5.最终通知
一般用于资源关闭等
使用案例:
权限判断——使用环绕通知
用户访问A页面时,以访问方法为切点,织入环绕通知(环绕通知中先判断用户权限,有权限再执行proceed,没有权限执行其他内容)
注解方式
执行顺序
多个通知方法的执行顺序
-
前:环绕前/前置
-
中:目标方法(若调用了proceed)
-
后:环绕后/后置/异常/最终
xml中同级通知之间执行顺序与注册顺序(配置顺序)有关
注解方式中默认 环绕前–前置–目标–环绕后–最终–后置
多个切面类的执行顺序
- 优先级高的在外层(最前最后)
- 默认按切面类类名的字母顺序,a-z优先级递减
- 手动配置xml中 aop:aspect 标签的order属性
在切面类上使用@Order注解;
数值越小优先级越高(支持负数)