spring框架两大核心:IOC和AOP的详解
目录
一、IOC和AOP的介绍
IOC:(Inverse of Control)控制反转,也叫DI依赖注入,指的是将对象的创建权交给 Spring 容器去创建,利用了工厂模式将对象交给容器管理,只需要在spring配置文件总配置相应的bean,以及设置相关的属性,让spring容器来生成类的实例对象以及管理对象。不需要我们手动new去创建对象,大大降低了代码间的耦合度,使资源更加容易管理。
AOP:(Aspect Oriented Programming)面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。 AOP可以分离业务代码和关注点代码(重复代码),在执行业务代码时,动态的注入关注点代码。切面就是关注点代码形成的类。Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。
二、IOC
1.IOC初始化属性的方式
1.使用构造方法完成属性初始化
<!--使用构造方法完成对象属性的初始化 初始化属性个数要和类中的构造方法相匹配-->
<bean id="student" class="spring.spring.basic.entity.Student">
<constructor-arg name="stuName" value="aa"></constructor-arg>
<constructor-arg name="stuNo" value="11"></constructor-arg>
<constructor-arg name="age" value="22"></constructor-arg>
<constructor-arg name="score" value="90.0"></constructor-arg>
</bean>
2.使用type数据类型完成属性初始化
<!--
使用type 数据类型完成属性的初始化操作 如果有相同的数据类型存在 需要配合 index指定属性的索引 索引指代属性在类中的顺序
-->
<bean id="student" class="spring.spring.basic.entity.Student">
<constructor-arg type="java.lang.String" index="0" value="aa"></constructor-arg>
<constructor-arg type="java.lang.String" index="1" value="aa"></constructor-arg>
<constructor-arg type="java.lang.Integer" value="22"></constructor-arg>
<constructor-arg type="double" value="99.0"></constructor-arg>
</bean>
3.使用p命名空间初始化对象
使用p命名空间需要在xml文件中加入约束:xmlns:p="http://www.springframework.org/schema/p"
<bean id="student" class="spring.spring.basic.entity.Student"
p:stuName="ss" p:stuNo="dd" p:age="12" p:score="33.3"></bean>
作用:简化使用使用property标签完成初始化的过程 ,但是依然要求类中要有对应的set方法,本质还是使用set方式完成属性的初始化。
4.使用c命名空间初始化对象
使用c命名空间需要在xml文件中加入约束:xmlns:c="http://www.springframework.org/schema/c"
<bean id="student" class="spring.spring.basic.entity.Student"
c:stuName="ss" c:stuNo="dd" c:age="12" c:score="33.3"></bean>
本质: c命名空间初始化本质是使用有参构造完成初始化。
2.属性自动注入的方式
1.通过属性类型注入
<bean id="teacher" class="spring.spring.basic.entity.Teacher" autowire="byType"></bean>
autowire="byType" 根据实体类中属性类型进行自动注入,spring容器中只能有一个指定类型的bean。
2.通过属性名注入
<bean id="teacher" class="spring.spring.basic.entity.Teacher" autowire="byName"></bean>
autowire="byName" 根据实体类中属性名字进行自动注入,根据属性的名字在spring容器中查找哪个bean的id和属性名一致。
3.复杂对象的创建
1.Connection实例工厂对象
public class ConnectionFactoryBean {
public Connection getConnection() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql:///java2217?serverTimezone=UTC&useSSL=false", "root", "123456");
return connection;
}
}
xml代码:
<bean id="connFactory" class="spring.factory.ConnectionFactoryBean"></bean>
<bean id="connection" factory-method="getConnection" factory-bean="connFactory"></bean>
2. Connection静态工厂对象
public class ConnectionFactoryBean {
public static Connection getConnection() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql:///java2217?serverTimezone=UTC&useSSL=false", "root", "123456");
return connection;
}
}
xml代码:
<bean id="connection" class="spring.factory.ConnectionStaticFactoryBean"
factory-method="getConnection"></bean>
3. 实现FactoryBean接口的工厂对象
public class ConnectionInterfaceFactoryBean implements FactoryBean<Connection> {
@Override
public Connection getObject() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql:///java2215?serverTimezone=UTC&useSSL=false", "root", "201314dwt");
return connection;
}
@Override
public Class<?> getObjectType() {
return Connection.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
xml代码:
<bean id="connection" class="spring.factory.ConnectionInterfaceFactoryBean"></bean>
4.spring注解开发
使用注解注入属性可以减少xml文件的代码配置,只需要开启注解的包扫描然后在类或方法前面加对应的注解就可将类纳入spring容器的管理中。
1.开启注解包扫描
<context:component-scan base-package="cn.kgc.spring"/>
2.常用的注解
@Repository dao层
@Component 组件通用
@Service service层 标记类纳入spring容器的管理 ,如果没有指定key ,那么默认的key是类名的小写, 如果指定了key 则获取是要是用指定的key。
@Controller 控制层
@Autowired 默认根据类型注入byType,在注入属性值时 发现多于一个符合要求的bean实例,那么spring容器会尝试在spring容器中寻找和属性名一致的id作为注入的值 byName。
三、AOP
1.代理模式
aop的代理模式可以分为动态代理和静态代理,动态代理有两种实现方式,通过jdk和cglib两种代理,通过代理类可以为原始类增加额外的功能。
1.静态代理
静态代理优缺点
1)优点 :在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展 2)缺点 :因为代理对象需要与目标对象实现一样的接口,所以会很多代理类 ,一旦接口增加方法,目标对象与代理对象都要维护
开发代理对象的原则:
1.代理对象和目标对象实现相同的接口
2.代理对象依赖于目标对象
2.动态代理(反射)
1.基于jdk的动态代理
程序运行的过程中,通过jdk提供代理技术动态的为某个类产生动态代理对象的过程。
开发代理对象的原则:
1)代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用JDK动态代理。 2)代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象。 3)动态代理也叫做 :JDK代理、接口代理
代码:
@Test
public void test4() { //jdk的动态代理
// 被代理对象
UserServiceImpl userService = new UserServiceImpl();
InvocationHandler handler = new InvocationHandler() {
/**
* @param proxy 生成的代理对象 可忽略
* @param method 原始对象中的方法
* @param args 原始对象中的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------log---------");
Object invoke = method.invoke(userService, args);
return invoke;
}
};
/**
*参数1:类加载器,动态代理对象没有类加载器 需要借助别的类的加载器实现类的加载
* 类加载器的作用:
* 1.加载class文件到JVM虚拟机
* 2.创建类的class对象,进而创建类的实例化对象
*
* 参数2:原始类实现的所有接口 基于jdk的动态代理要求代理对象和原始对象要实现相同的接口
*
* 参数3:代理对象额外增加的功能 InvocationHandler
*
*/
UserService o =(UserService) Proxy.newProxyInstance(TestStaticProxy.class.getClassLoader(), userService.getClass().getInterfaces(), handler);
o.login();
o.register();
}
2.基于CGLib的动态代理
开发代理对象的原则:
1.代理对象无需和原始类对象实现相同的接口
2.代理对象和原始类对象要存在父子类关系
代码:
@Test
public void test5(){ //基于cglib的动态代理
UserServiceImpl userService = new UserServiceImpl();
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(this.getClass().getClassLoader());
enhancer.setSuperclass(userService.getClass());
//设置额外的功能
MethodInterceptor callback = new MethodInterceptor() {
//等价于 jdk代理中 invocationHandler 中的 invoke 方法
/**
* @param o 代理对象
* @param method 原始对象中的方法
* @param objects 原始对象中的参数
* @param methodProxy 代理方法
* @return 原始方法的返回值
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("---------cglib log------------");
return method.invoke(userService, objects);
}
};
enhancer.setCallback(callback);
// 创建代理对象
UserService usrService =(UserService) enhancer.create();
usrService.login();
usrService.register();
}
2. Aop术语
-
横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等。
-
切面(ASPECT) :横切关注点被模块化的特殊对象。即,它是一个类。
-
通知(Advice) :切面必须要完成的工作。即,它是类中的一个方法。
前置通知 后置通知 环绕通知 异常通知 最终通知。
-
目标(Target) ;被通知对象。
-
代理(Proxy) :向目标对象应用通知之后创建的对象。
-
切入点(PointCut) :切面通知执行的“地点"的定义。
-
连接点(JointPoint) :与切入点匹配的执行点。
3.Aop的实现方式
1.基于接口方式的实现
使用Spring aop接口方式实现aop, 可以通过自定义通知来供Spring AOP识别对应实现的接口是:
1)前置通知: MethodBeforeAdvice
2)返回通知:AfterReturningAdvice
3)异常通知:ThrowsAdvice
4)环绕通知:MethodInterceptor
通知的类型
Spring 方面可以使用下面提到的五种通知工作:
通知 | 描述 |
---|---|
前置通知 | 在一个方法执行之前,执行通知。 |
后置通知 | 在一个方法执行之后,不考虑其结果,执行通知。 |
返回后通知 | 在一个方法执行之后,只有在方法成功完成时,才能执行通知。 |
抛出异常后通知 | 在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。 |
环绕通知 | 在一个方法调用之前和之后,执行通知。 |
这里以前置通知为例:
public class Before implements MethodBeforeAdvice {
//添加需要增强的业务 无需手动调用方法执行
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("---------------前置通知开始----------------");
System.out.println("前置通知添加的方法名是:"+method.getName());
System.out.println("前置通知添加的方法的参数列表:"+ Arrays.toString(args));
System.out.println("---------------前置通知结束----------------");
}
}
2.基于Aspectj的方式实现
基于aspectj是主要的实现方式,一共有三种方法。
1.组装切面:切点:通知所添加的位置 类中被添加通知的方法 切面:切点+通知
xml代码:
<aop:config>
<!--配置切点-->
<spring.aop:pointcut id="pc" expression="execution(* *(..))"/>
<spring.aop:pointcut id="pc" expression="execution(* delete(..))"/>
<spring.aop:pointcut id="pc" expression="execution(* *(Integer))"/>
<spring.aop:pointcut id="pc" expression="execution(* spring.service.TeacherServiceImpl.*(..))"/>
<spring.aop:pointcut id="pc" expression="execution(* spring.service.*.*(..))"/>
<!--配置通知-->
<spring.aop:advisor advice-ref="before" pointcut-ref="pc"></spring.aop:advisor>-->
</aop:config>
expression:切点表达式 类似css选择器 选中需要添加通知的位置
execution(* *(..))
第一个 * 表示访问权限修饰符和返回值类型,第二个 * 表示包名+类名+方法名,(..)表示(参数列表) 方法级别:delete
execution(* delete(..))
类级别:
execution(* spring.service.UserServiceImpl.*(..))
包级别:
execution(* spring.service.*.*(..))
设置参数类型时:如果参数类型是java.lang包中的数据类型可以直接写,否则要书写参数类型的 全限定名spring.entity.User
2.配置切面类中通知的切点
<aop:config>
<aop:aspect ref="all">
<aop:pointcut id="pc" expression="execution(* *(..))"/>
<!--前置通知-->
<spring.aop:before method="before" pointcut-ref="pc"></spring.aop:before>
<!--后置通知-->
<spring.aop:after-returning method="afterReturning" pointcut-ref="pc"></spring.aop:after-returning>
<!--异常通知-->
<spring.aop:after-throwing method="afterThrowing" pointcut-ref="pc"></spring.aop:after-throwing>
<!--最终通知-->
<spring.aop:after method="ending" pointcut-ref="pc"></spring.aop:after>
环绕通知
<spring.aop:around method="around" pointcut-ref="pc"></spring.aop:around>
</aop:aspect>
</aop:config>
3. 使用注解完成Aop的使用
1)开启包扫描:
<context:component-scan base-package="spring"></context:component-scan>
2)开启注解扫描:
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@aspect:切面注解
@Pointcut:切点注入
@Before:前置增强
@AfterReturning:后置增强
@AfterThrowing:抛出增强
@After:final增强
@Around:环绕增强
实现类增强代码:
package spring.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AllAdvice {
@Pointcut("execution(* *(..))")
public void all(){}
//前置通知
@Before("all()")
public void before(){
System.out.println("------------前置通知-------------");
}
//后置通知 核心业务出现异常 不会执行
@AfterReturning("all()")
public void afterReturning(){
System.out.println("------------后置通知-------------");
}
//异常通知 核心业务出现异常时添加的通知 没有异常时不添加
@AfterThrowing("all()")
public void afterThrowing(){
System.out.println("------------异常通知--------------");
}
//最终通知 不管核心业务是否有异常 均要执行
@After("all()")
public void ending(){
System.out.println("------------最终通知-------------");
}
//环绕通知
@Around("all()")
public Object around(ProceedingJoinPoint proceedingJoinPoint){
try {
System.out.println("-------------环绕前置通知--------------");
Object proceed = proceedingJoinPoint.proceed();
System.out.println("-------------环绕后置通知--------------");
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("-------------环绕异常通知--------------");
}finally {
System.out.println("-------------环绕最终通知--------------");
}
return null;
}
}
service代码:
@Service
public class UserServiceImpl implements UserService{
@Override
public boolean addUser(User user) {
System.out.println("service的核心业务:一个用户对象被添加"+user);
return false;
}
test代码:
public class TestAOP {
@Test
public void test(){
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userServiceImpl = ac.getBean("userServiceImpl", UserService.class);
System.out.println(userServiceImpl.getClass());
User user = User.builder().username("ff").password("123").build();
userServiceImpl.addUser(user);
}
运行结果:
class com.sun.proxy.$Proxy20
-------------环绕前置通知--------------
------------前置通知-------------
service的核心业务:一个用户对象被添加User(username=ff, password=123)
------------后置通知-------------
------------最终通知-------------
-------------环绕后置通知--------------
-------------环绕最终通知--------------
Process finished with exit code 0