Spring解读
什么是Spring框架
-
它是一个容器.它是整合其它框架的框架
-
核心:
-
IOC:控制反转,通过Spring框架完成创建对象相关操作的服务,减轻了程序员的负担。
-
AOP:面向切面编程,将代码中公共的部分提取出来作为一个块单独做开发,极大提高了代码的灵活性,可变性。
-
-
组成 :它由20多个模块构成.它在很多领域都提供优秀的解决方案.
Spring的特点
轻量级
由20多个模块构成,每个jar包都很小,小于1M,核心包也就3M左右. 对代码无污染.
面向接口编程
使用接口,就是面向灵活,项目的可扩展性,可维护性都极高.接口不关心实现类的类型.使用时接口指向实现类,切换实现类即可切换整个功能.
AOP:面向切面编程
就是将公共的,通用的,重复的代码单独开发,在需要的时候反织回去.底层的原理是动态代理.
整合其它框架
它整合后使其它框架更易用
IOC
以前如果像使用类(非static)中的方法,就要创建对象调方法.这无疑是繁琐的
-
以前的方式
@Test
public void BeforeStudent() {
// 程序员创建对象
Student stu = new Student();
System.out.println(stu);
}
只有创建了对象才能调用方法。但是问题是只需要调用方法就可以完成业务的操作。但是以前传统的方法却多了一步创建对象。
-
IOC实现
@Test
public void testStudentSpring() {
// 由Spring容器进行对象的创建
// 如果要从Spring容器中取出对象,则要先创建容器对象,并启动才可以取出对象。
ApplicationContext ac = new ClassPathXmlApplicationContext("StudentApplicationContext.xml");
Student stu = (Student) ac.getBean("stu");
System.out.println(stu);
}
Spring中XML文件配置
<!--创建学生对象-->
<!--
这个动作就等同于Student stu = new Student();
id就是创建的对象的名称,class就是创建对象的类型,底层通过反射构建对象
启动容器的同时,创建对象
-->
<bean id="stu" class="com.pront.entity1.Student"></bean>
注意:如果要在XML中配置对象,在对象类中一定要配置Setter,Getter.底层就是调用Setter,Getter函数来赋值的
注意:Spring配置文件中所创建的对象名称是唯一的,默认是类的驼峰命名法
IOC就是创建对象有Spring接管。只需要在Spring配置文件里面做一个简单的配置。就可以拿取对象调用方法了
注入类型
普通类型注入(value)
<bean id="stu" class="com.pront.entity1.Student">
<--
name为对应类中的属性名,value为属性值,value为简单类型注入 (只能注入基本数据类型)
-->
<property name="name" value="zhangsan"/>
<property name="age" value="13"/>
</bean-->
引用类型注入(ref)
<!--创建学校的对象-->
<bean id="school" class="com.pront.entity2.School">
<property name="name" value="清华大学"/>
<property name="address" value="北京"/>
</bean>
<!--创建学生对象-->
<bean id="student" class="com.pront.entity2.Student">
<property name="name" value="李四"/>
<property name="age" value="22"/>
<!--ref使用的前提是对应的Student实体类中有School成员变量-->
<property name="school" ref="school"/>
</bean>
使用构造方名称注入值(<constructor-arg>)
private String name;
private String address;
// 使用带参数的构造方法注入值
// 没有getter,使用有参数的构造方法在xml文件中给School对象注入值
public School(String name, String address) {
this.name = name;
this.address = address;
}
<!--使用构造方法的参数名称进行注入值-->
<bean id="school" class="com.pront.entity.School">
<constructor-arg name="name1" value="清华大学"></constructor-arg>
<constructor-arg name="address1" value="海淀区"></constructor-arg>
</bean>
使用构造方法参数的下标注入(index),可以自己指定参数的顺序注入值
<bean id="stu" class="com.pront.entity.Student">
<constructor-arg index="0" value="钱七"></constructor-arg>
<constructor-arg index="1" value="22"></constructor-arg>
<constructor-arg index="2" ref="school"></constructor-arg>
</bean>
注意:如果不写index,就会以默认顺序注入值(构造方法里面参数的顺序)
这好处就体现在我们可以一心一意的处理业务逻辑,业务处理,Spring将我们创建对象这种繁琐的小事处理掉。
基于注解的IOC
-
使用注解代替在Spring配置文件创建对象的操作,进一步优化代码
-
基于注解的IOC也称为DI(Dependency Injection),它是IOC的具体实现的技术.
-
基于注解的IOC,必须要在Spring的核心配置文件中添加包扫描. <context:component-scan base-package="com.pornt.entity"/>
<!--基于注解的IOC都需要添加包扫描,只要这个包上面有这个注解,立刻创建对象-->
<context:component-scan base-package="org.example.object01"/>
创建对象的注解
@Component:可以创建任意对象.创建的对象的默认名称是类名的驼峰命名法.也可以指定对象的名称@Component("指定名称"). @Controller:专门用来创建控制器的对象(Servlet),这种对象可以接收用户的请求,可以返回处理结果给客户端. @Service:专门用来创建业务逻辑层的对象,负责向下访问数据访问层,处理完毕后的结果返回给界面层. @Repository:专门用来创建数据访问层的对象,负责数据库中的增删改查所有操作.
案例:
@Component("stu") //交给Spring去创建对象,就是在容器启动时创建
public class Student {
@Value("张三") ===>简单类型的值注入
private String name;
@Value("22")
private int age;
...}
依赖注入的注解
-
简单类型(8种基本类型+String)的注入 @Value:用来给简单类型注入值
-
引用类型的注入
-
@Autowired:使用类型注入值,从整个Bean工厂中搜索同源类型的对象进行注入. 同源类型也可注入. 什么是同源类型: a.被注入的类型(Student中的school)与注入的类型是完全相同的类型 b.被注入的类型(Student中的school父)与注入的类型(子)是父子类 c.被注入的类型(Student中的school接口)与注入的类型(实现类)是接口和实现类的类型
注意:在有父子类的情况下,使用按类型注入,就意味着有多个可注入的对象.此时按照名称进行二次筛选,选中与被注入对象相同名称的对象进行注入.
B.@Autowired @Qualifier("名称"):使用名称注入值,从整个Bean工厂中搜索相同名称的对象进行注入.
注意:如果有父子类的情况下,直接按名称进行注入值.
-
-
示例:
-
// 引用类型按类型注入,Autowired会从BeanFactory中寻找有没有Student的对象 // 按照类型注入(@Autowired) // 按名称注入(@Autowired,@Qualifier) @Autowired @Qualifier("student") private Student student;
-
基于注解的IOC要添加包扫描
单个包扫描(推荐使用)
<context:component-scan base-package="com.bjpowernode.controller">/context:component-scan <context:component-scan base-package="com.bjpowernode.service.impl">/context:component-scan <context:component-scan base-package="com.bjpowernode.dao">/context:component-scan
多个包扫描,多个包之间以逗号或空格或分号分隔
<context:component-scan base-package="com.bjpowernode.controller com.bjpowernode.service ,com.bjpowernode.dao">/context:component-scan
扫描根包(不推荐)
<context:component-scan base-package="com.bjpowernode">/context:component-scan 会降低容器启动的速度,导致多做无用功.
为应用指定多个 Spring 配置文件 当项目越来越大,需要多人合作开发,一个配置就存在很大隐患. 拆分配置文件的策略 A.按层拆 applicationContext_controller.xml <bean id="uController" class="com.pront.controller.UsersController"> <bean id="bController" class="com.pront.controller.BookController"> applicationContext_service.xml <bean id="uService" class="com.pront.controller.UsersService"> <bean id="bService" class="com.pront.controller.BookService"> applicationContext_mapper.xml <bean id="uMapper" class="com.pront.controller.UsersMapper"> <bean id="bMapper" class="com.pront.controller.BookMapper">
Spring配置文件的整合
单个文件的导入(import)
<import resource="ApplicatoinContext_mapper.xml"></import>
<import resource="ApplicatoinContext_service.xml"></import>
<import resource="ApplicatoinContext_controller.xml"></import>
多个文件的导入(*)
<import resource="applicatoinContext_*.xml"></import>
AOP
手写AOP框架(理解Spring原生AOP)
1.0:业务和切面没有解耦合,缠在一起
public class BookServiceImpl {
// 业务和公共的事务切面紧耦合在一起
public void buy(){
try {
System.out.println("开启事务");
System.out.println("关闭事务");
} catch (Exception e) {
System.out.println("回滚事务");
}
}
}
-
使用这种方式的话当业务发生改变或者切面发生改变都不能单独修改,改一点而动全身
-
一旦使用这种方式来实现业务,就意味着就写死了。没当业务需求发生改变。修改的代码无疑是重新写一套的重新的代码
-
这种方式并没有提供接口,没有业务规范所在
2.0:子类代理来拆分和切面
public class BookServiceImpl {
// 提供一个购买方法
// 这个方法里面只 提供业务,不提供事务
public void buy(){
System.out.println("购买图书.......");
}
public class SubBookServiceImpl extends BookServiceImpl {
public void buy(){
try {
System.out.println("事务开启");
// 调用业务
super.buy();
System.out.println("事务关闭");
} catch (Exception e) {
System.out.println("事务回滚");
}
}
}
-
子类代理;一般都是父类实现核心基础功能,子类在父类的基础上实现扩展
-
使用子类代理,虽然切面和事务时分开了。但是切面写死了。一旦切面发生改变,都需要修改源代码。
3.0:使用静态代理拆分业务和切面,业务和业务接口已拆分,此时切面紧耦合在业务中
public interface Service {
void buy();
}
public class Agent implements Service {
/**
* 静态代理是以接口为成员变量,用户传入值完成target的初始化
*/
Service target;
public Agent(Service target) {
this.target = target;
}
@Override
public void buy() {
// 事务切面混合着业务,还是紧紧的耦合
try {
// 切面
System.out.println("开启事务");
// 事务
target.buy();
// 切面
System.out.println("关闭事务");
} catch (Exception e) {
// 切面
System.out.println("回滚事务");
}
}
}
public class BookServiceImpl implements Service {
@Override
public void buy() {
System.out.println("购买图书的实现......");
}
}
-
使用了接口,接口定义了规范。业务发生改变的时候,修改规范就是了。不用直接修改源代码的逻辑。接口发生改变。相应的实现类对应发生改变。
-
虽然使用了业务接口,但是业务和切面还是紧紧的耦合在一起
4.0: 使用静态代理拆分业务和业务接口,切面和切面接口
public interface AOP {
// 默认空实现这三个方法,实现类不需要强制实现接口中的方法,需要实现那些方法就实现那些方法。
default void before(){};
default void after(){};
default void Throws(){};
}
public interface Service {
void buy();
}
public class LogAOP implements AOP{
@Override
public void before() {
System.out.println("前置日志输出");
}
}
public class TransactionAOP implements AOP{
@Override
public void before() {
System.out.println("事务开启");
}
@Override
public void after() {
System.out.println("事务关闭");
}
@Override
public void Throws() {
System.out.println("事务回滚");
}
}
public class BookServiceImpl implements Service{
@Override
public void buy() {
System.out.println("购买图书的实现........");
}
}
public class Agent implements Service {
Service target;
AOP aop;
public Agent(Service target, AOP aop) {
this.target = target;
this.aop = aop;
}
/**
* AOP接口中的每一个方法都要写,不会报错的,因为默认空实现
*
*/
@Override
public void buy() {
try {
aop.before();
target.buy();
aop.after();
} catch (Exception e) {
aop.Throws();
}
}
}
-
使用这种方式拆分了不仅拆分了业务和业务接口,同时也拆分了切面和切面接口
-
体现了业务和切面的灵活性
-
同时,这种方式业务和切面已经分开了,在需要的地方调用就可以了
-
但是,静态代理代理对象在类加载的时候就已经确定了,不能更改。100个业务目标对象,100个代理对象。对内存负载较大。效率不高。所以使用动态代理实现
5.0: 使用动态代理灵活实现
public interface AOP {
default void before(){}
default void after(){}
default void Throws(){}
}
public interface Service {
void buy();
default int sell(int number){return 0;}
}
public class LogAOP implements AOP{
@Override
public void before() {
System.out.println("前置日志输出........");
}
}
public class TransactionAOP implements AOP{
@Override
public void before() {
System.out.println("事务开启......");
}
@Override
public void after() {
System.out.println("事务关闭.....");
}
@Override
public void Throws() {
System.out.println("事务回滚......");
}
}
public class BookServiceImpl implements Service{
@Override
public void buy() {
System.out.println("购买图书的实现......");
}
@Override
public int sell(int number) {
return number;
}
}
public class ProxyFactory {
// 一般工具类,或者功能类的方法都需要加上static 关键字,调用的时候比较方便
public static Object getAgent(Service target,AOP aop){
// 返回生成的动态代理对象Agent
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
// 切面
Object obj = null;
try {
aop.before();
// 业务
// target.buy();
obj = method.invoke(target, args);
// 切面
aop.after();
// 切面
} catch (Exception e) {
aop.Throws();
}
// 返回方法的返回值
return obj;
}
);
}
}
-
与4.0版本主要区别还是使用了代理工厂实现了动态代理。使用这种方式,切面和业务灵活的分开。
-
5.0版本基本上就是Spring原生的AOP逻辑
Spring支持的AOP实现
-
@Before
-
在目标方法被调用前调用,涉及的接口org.springframework.aop.MethodBeforeAdvice
-
-
@After
-
在目标方法被调用后调用,涉及的接口org.springframework.aop.AfterReturningAdvice;
-
-
@Throws
-
目标方法抛出异常的时候调用,涉及的接口org.springframework.aop.ThrowsAdvice;
-
-
@Around
-
拦截对目标对象方法调用,涉及的接口org.aopalliance.intercept.MethodInterceptor。
-
AOP常用的术语
-
切面:就是那些重复的,公共的,通用的功能称为切面,例如:日志,事务,权限.
-
连接点:就是目标方法.因为在目标方法中要实现目标方法的功能和切面功能.
-
切入点(Pointcut):指定切入的位置,多个连接点构成切入点.切入点可以是一个目
标方法,可以是一个类中的所有方法,可以是某个包下的所有类中的方法.
-
目标对象:操作谁,谁就是目标对象.
-
通知(Advice):来指定切入的时机.是在目标方法执行前还是执行后还是出错时,还是环绕目标方法切入切面功能.
ASpectJ
什么是ASpectJ框架
ASpectJ是一个轻量级的面向切面的框架,体积又小,功能又强。它扩展了Java语言,提供了强大的切面实现。所以Spring框架将ASpectJ框架整个进Spring。只需要添加依赖即可使用
AspectJ常见的通知类型
常见的通知有四种类型
-
前置通知@Before
-
后置通知@AfterReturning
-
环绕通知@Around
-
最终通知@After
-
定义切入点@Pointcut
AspectJ 的切入点表达式
规范的公式:
execution(访问权限 方法返回值 方法声明(参数) 异常类型) 简化后的必须写的公式: execution( 方法返回值 方法声明(参数) )
代码任意个任意的字符(通配符) .. 如果出现在方法的参数中,则代表任意参数 如果出现在路径中,则代表本路径及其所有的子路径
示例: execution(public * *(..)) //任意的公共方法 execution(* set*(..))//任何一个以“set”开始的方法 execution(* com.xyz.service.impl.*.*(..))//任意的返回值类型,在com.xyz.service.impl包下的任意类的任意方法的任意参数 execution(* com.xyz.service..*.*(..))//任意的返回值类型 ,在com.xyz.service及其子包下的任意类的任意方法的任意参数 com.xyz.service.a.b.*.*(..) com.xyz.service.*.*(..) execution(* *..service.*.*(..))//service之前可以有任意的子包 execution(* *.service.*.*(..))//service之前只有一个包