AOP:Aspect Oriented Programming,面向切面编程。
"切面"就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,利于可操作性和可维护性。
在编译期,切面直接以字节码的形式编译到目标字节码文件中。 在编译的时候将AOP逻辑织入到代码中,需要专有的编译器和织入器。
优点:被织入的类性能不受影响。
缺点:不够灵活
JDK动态代理是实际上使用代理模式,在运行期动态生成目标类的代理类,然后在代理方法内部完成代理逻辑,通过代理类做请发转发。
优点:Java标准库原生支持,使用简单,无需引用额外的包。相对于静态AOP更灵活。
缺点:带代理的类必须是接口,灵活性受到一些限制;使用反射会影响一些性能。
在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中。
CGLib是动态代码字节生成的实现,它封装字节码生成工具Asm,原理是在运行期间目标字节码加载后,生成目标类的子类,将切面逻辑加入到子类中,所以使用Cglib实现AOP 不需要基于接口。
CGLIB动态代理主要用到拦截方法的技术,主要涉及的类:Enhancer和MethodInceptor接口
优点:没有接口也可以织入,灵活性高。
缺点:扩展类的实例方法为final时,则无法进行织入。
Spring AOP 中的代理使用逻辑:
AspectJ AOP使用的则是编译时增强,Spring AOP 属于运行时增强。
JDK动态代理是实际上使用代理模式,在运行期动态生成目标类的代理类,然后在代理类中完成织入逻辑。
主要实现方式依赖java.lang.reflect包下的InvocationHandler和Proxy类。 通过实现InvocationHandler来接口创建代理方法,在方法内部实现代理逻辑,并且使用代理类转发请求给目标类。代理类是通过Proxy的静态方法反射生成的,它通过传入相应的类加载器、接口以及目标类的实现,来反射创建代理类。
但是由于Proxy静态方法生成的代理类会有一个共同的父类Proxy,Java又无法实现多继承,所以只能通过接口来完成动态代理。
参考:Java设计模式——代理模式
参考:Proxy类的实现方式。
参考结合源码理解PageHelper。
Java架构直通车——彻底了解DIP、IOC与DI之间的关系
过程参考:Java架构直通车——@SpringApplication自动装配原理。
原理参考:工厂模式加反射机制。
Spring框架的七大模块
1.Spring Core
框架的最基础部分,提供 IoC 容器,对 bean 进行管理。
2.Spring Context
基于 bean,提供上下文信息,扩展出JNDI、EJB、电子邮件、国际化、校验和调度等功能。
3.Spring DAO
提供了JDBC的抽象层,它可消除冗长的JDBC编码和解析数据库厂商特有的错误代码,还提供了声明性事务管理方法。
4.Spring ORM
提供了常用的“对象/关系”映射APIs的集成层。 其中包括JPA、JDO、Hibernate、MyBatis 等。
5.Spring AOP
提供了符合AOP Alliance规范的面向方面的编程实现。
6.Spring Web
提供了基础的 Web 开发的上下文信息,可与其他 web 进行集成。
7.Spring Web MVC
提供了 Web 应用的 Model-View-Controller 全功能实现。
Spring 不保证 bean 的线程安全。
默认 spring 容器中的 bean 是单例的。当单例中存在竞态条件,即有线程安全问题。
常见的有两种解决办法:
只有在 Web 应用中使用Spring时,request、session、global-session 作用域才有效
什么时候使用prototype,什么时候使用singleton,主要看是看bean是有状态bean还是无状态bean:
有状态bean,如果配置为singleton,会出现线程安全问题,比如下面的代码:
package com.test; public class TestServiceImpl implements TestService{ private User user; public void test1(User u) throws Exception { this.user = u; //1 test2(); } public void test2() throws Exception { System.out.println(user.getId()); //2 } }
如果该Bean配置为singleton,在并发访问下如果某一个线程修改了user的值,那么另外的线程可能会出错的。
实际应该是这个例子不应该用实例变量,这样就使得这个Bean由无状态变成了有状态Bean。
如果用有状态的bean,就要用prototype模式,每次在注入的时候就重新创建一个bean,在多线程中互不影响。
spring MVC是一种分层设计思想,达到解耦合的目的。MVC主要是Model View和Controller,他的运行流程是:…
参考:DispatchServlet处理流程
参考:Rest处理流程
@RequestMapping 是一个注解,用来标识 http 请求地址与 Controller 类的方法之间的映射。
@Autowired 是一个注解,它可以对类成员变量、方法及构造函数进行标注,让 spring 完成 bean 自动装配的工作。
@Autowired 默认是按照类去匹配,配合 @Qualifier 指定按照名称去装配 bean。
单独使用 @Controller 不加 @ResponseBody的话一般使用在要返回一个视图的情况,这种情况属于比较传统的Spring MVC 的应用,对应于前后端不分离的情况。
@RestController只返回对象,对象数据直接以 JSON 或 XML 形式写入 HTTP 响应(Response)中,这种情况属于 RESTful Web服务,这也是目前日常开发所接触的最常用的情况(前后端分离)。
spring boot 目的就是为了简化开发,它能够使得编码和配置变得简单,避免了一些重复的编码和配置,减少开发的代码量。另外,在SpringMVC的时候需要去手动配置Tomcat,或者通过打war包放到tomcat下运行个。而SpringBoot可以直接通过maven依赖引入tomcat或者jetty,通过打jar包再使用java -jar命令运行,简化部署。另外,监控也变得简单。
简而言之,好处:
Spring Boot 有两种类型的配置文件,application 和 bootstrap 文件
Spring Boot会自动加载classpath目前下的这两个文件,文件格式为 properties 或 yml 格式
*.properties 文件是 key=value 的形式
*.yml 是 key: value 的形式
*.yml 加载的属性是有顺序的,但不支持 @PropertySource 注解来导入配置,一般推荐用yml文件,看下来更加形象
bootstrap 配置文件是系统级别的,用来加载外部配置,如配置中心的配置信息,也可以用来定义系统不会变化的属性.bootstatp 文件的加载先于application文件
application 配置文件是应用级别的,是当前应用的配置文件
package org.springframework.boot.autoconfigure;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
......
}
package org.springframework.boot;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
可以看出大概可以把 @SpringBootApplication 看作是 @Configuration、@EnableAutoConfiguration、@ComponentScan 注解的集合。根据 SpringBoot官网,这三个注解的作用分别是:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
注意这一句@Import({AutoConfigurationImportSelector.class})
,通过Spring 提供的 @Import 注解导入了AutoConfigurationImportSelector类(@Import 注解可以导入配置类或者Bean到当前类中),AutoConfigurationImportSelector类中getCandidateConfigurations方法会将所有自动配置类的信息以 List 的形式返回。这些配置信息会被 Spring 容器作 bean 来管理。
当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean,内部bean通常是匿名的,它们的Scope一般是prototype。
如:
<bean id="d1" class="com.yang.Dog">
<property name="name" value="lala">property>
<property name="age" value="9">property>
<property name="style" value="Hashiqi">property>
bean>
<bean id="d2" class="com.yang.person">
<constructor-arg index="0" value="andreny">constructor-arg>
<constructor-arg index="1" value="15454">constructor-arg>
<constructor-arg index="2" value="teacher">constructor-arg>
bean>
<bean id="m1" class="com.yang.myshow">
<property name="dog" ref="d1">property>
<property name="p" ref="d2">property>
bean>
1、Spring容器 从XML 文件中读取bean的定义,并实例化bean。
2、Spring根据bean的定义填充所有的属性。
3、如果bean实现了BeanNameAware 接口,Spring 传递bean 的ID 到 setBeanName方法。
4、如果Bean 实现了 BeanFactoryAware 接口, Spring传递beanfactory 给setBeanFactory 方法。
5、如果有任何与bean相关联的BeanPostProcessors,Spring会在postProcesserBeforeInitialization()方法内调用它们。
6、如果bean实现IntializingBean了,调用它的afterPropertySet方法,如果bean声明了初始化方法,调用此初始化方法。
7、如果有BeanPostProcessors 和bean 关联,这些bean的postProcessAfterInitialization() 方法将被调用。
8、如果bean实现了 DisposableBean,它将调用destroy()方法。
Spring提供以下几种集合的配置元素:
类型用于注入一列值,允许有相同的值。
类型用于注入一组值,不允许有相同的值。
类型用于注入一组键值对,键和值都可以为任意类型。
类型用于注入一组键值对,键和值都只能为String类型。
<beans>
<bean id="at" class="test.array.ArrayTest">
<property name="names">
<list>
<value>aaavalue>
<value>bbbvalue>
list>
property>
bean>
beans>
也可以通过注解的方式注入:
public interface InjectService extends InitializingBean{
public void inject();
}
@Service
public class InjectServiceImpl implements InjectService {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("begin class:" + this);
}
@Override
public void inject() {
System.out.println("this is inject service");
}
}
@Controller
public class InjectController {
@Autowired
private List<InjectService> list;
@Autowired
private Map<String, InjectService> map;
@Autowired
private Set<InjectService> set;
@RequestMapping("inject.html")
public void injectUrl() {
for (InjectService service : list) {
System.out.println("inject list service class:" + service);
}
for (String key : map.keySet()) {
System.out.println("inject map service class:" + map.get(key));
}
for (InjectService service : set) {
System.out.println("inject map service class:" + service);
}
}
}
对于list、set填入的是注入类型Spring管理的实例,对于map,Spring会将service的名字作为key,对象作为value封装进入Map。
@RequestMapping("/session")
public Map test(HttpSession session, String otherParam) {
...
}
或者
@RequestMapping("/session")
public String setSession(HttpServletRequest request, HttpServletResponse response){
HttpSession session = request.getSession();
...
}
有以下3种实现方式:
1、FileSystemXmlApplicationContext :从文件系统加载IoC配置文件
2、ClassPathXmlApplicationContext:从文件系统加载IoC配置文件
3、WebXmlApplicationContext:通过WebApplicationContextUtils获取
//加载单个xml文件
ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
//加载多个xml文件
String[] locations = new String[]{"bean1.xml", "bean2.xml", "bean3.xml"};
ctx = new FileSystemXmlApplicationContext(locations);
//加载单个xml文件
ctx = new ClassPathXmlApplicationContext("bean.xml");
//加载多个xml文件
locations = new String[]{"bean1.xml", "bean2.xml", "bean3.xml"};
ctx = new ClassPathXmlApplicationContext(locations);
ServletContext servletContext = request.getSession().getServletContext();
ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
代理设计模式 : Spring AOP 功能的实现。
单例设计模式 : Spring 中的 Bean 默认都是单例的。
模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
详情参考:Spring用了哪些模式
有五种自动装配的方式,可以用来指导Spring容器用自动装配方式来进行依赖注入。
(1)no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。
(2)byName:通过参数名 自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。
(3)byType:通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。
(4)constructor:这个方式类似于byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
(5)autodetect:首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。
参考:Java架构直通车——SpringApplication的初始化过程
spring容器可以理解为对于对象的管理和装配的地方,它负责了对象的整个生命周期–创建、装配、销毁。而这里对象的创建管理的控制权都交给了Spring容器,所以这是一种控制权的反转,称为IOC容器。
BeanFactory和ApplicationContext是Spring的两大核心接口,而其中ApplicationContext是BeanFactory的子接口。它们都可以当做Spring的容器,Spring容器是生成Bean实例的工厂,并管理容器中的Bean。
BeanFactory和ApplicationContext之间的关系:
另外,BeanFactory使用的是懒加载。而ApplicationContext默认是初始化的时候加载所有的Singleton Bean,在系统创建前期会有较大的系统开销,之后性能会有较好的改善。
1)@Transactional(propagation=Propagation.REQUIRED):默认的spring事务传播级别,使用该级别的特点是,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行,所以这个级别通常能满足处理大多数的业务场景。
2)@Transactional(propagation=PROPAGATION.SUPPORTS):从字面意思就知道,supports(支持),该传播级别的特点是,如果上下文存在事务,则支持当前事务,加入到事务执行,如果没有事务,则使用非事务的方式执行。所以说,并非所有的包在transactionTemplate.execute中的代码都会有事务支持。这个通常是用来处理那些并非原子性的非核心业务逻辑操作,应用场景较少。
3)@Transactional(propagation=PROPAGATION.MANDATORY):该级别的事务要求上下文中必须要存在事务,否则就会抛出异常!配置该方式的传播级别是有效的控制上下文调用代码遗漏添加事务控制的保证手段。比如一段代码不能单独被调用执行,但是一旦被调用,就必须有事务包含的情况,就可以使用这个传播级别。
4)@Transactional(propagation=PROPAGATION.REQUIRES_NEW):从字面即可知道,每次都要一个新的事务,该传播级别的特点是,每次都会新建一个事务,并且同时将上下文中的事务挂起,当新建事务执行完成以后,上下文事务再恢复执行。
这是一个很有用的传播级别,举一个应用场景:现在有一个发送100个红包的操作,在发送之前,要做一些系统的初始化、验证、数据记录操作,然后发送100封红包,然后再记录发送日志,发送日志要求100%的准确,如果日志不准确,那么整个父事务逻辑需要回滚。
怎么处理整个业务需求呢?就是通过这个PROPAGATION.REQUIRES_NEW 级别的事务传播控制就可以完成。发送红包的子事务不会直接影响到父事务的提交和回滚。
5)@Transactional(propagation=PROPAGATION.NOT_SUPPORTED) :这个也可以从字面得知,not supported(不支持),当前级别的特点是,如果上下文中存在事务,
则挂起事务,执行当前逻辑,结束后恢复上下文的事务。
这个级别有什么好处?可以帮助你将事务极可能的缩小。我们知道一个事务越大,它存在的风险也就越多。所以在处理事务的过程中,要保证尽可能的缩小范围。比如一段代码,是每次逻辑操作都必须调用的,比如循环1000次的某个非核心业务逻辑操作。这样的代码如果包在事务中,势必造成事务太大,导致出现一些难以考虑周全的异常情况。所以这个事务这个级别的传播级别就派上用场了,用当前级别的事务模板抱起来就可以了。
6)@Transactional(propagation=PROPAGATION.NEVER):该事务更严格,上面一个事务传播级别只是不支持而已,有事务就挂起,而PROPAGATION_NEVER传播级别要求上下文中不能存在事务,一旦有事务,就抛出runtime异常,强制停止执行!
7)@Transactional(propagation=PROPAGATION.NESTED):字面也可知道,nested,嵌套级别事务。该传播级别特征是,如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint。如果这个嵌套事务失败, 我们将回滚到此 savepoint。嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.;外部事务发生错误,嵌套事务会一并回滚。
实现@Transactional原理是基于spring aop的。
// switch to manual commit if necessary。 this is very expensive in some jdbc drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already)。if (con。getautocommit())
{
txobject.setmustrestoreautocommit(true);
if (logger.isdebugenabled())
{
logger.debug("switching jdbc connection [" + con + "] to manual commit");
}
//首先将自动提交属性改为false
con.setautocommit(false);
}
Spring AOP 自调用问题
若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法,有@Transactional 注解的方法的事务会失效。
这是由于Spring AOP代理的原因造成的,因为只有当 @Transactional 注解的方法在类以外被调用的时候,Spring 事务管理才生效。
我们一般使用 @Autowired 注解自动装配 bean,要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类,采用以下注解可实现:
@Component :通用的注解,可标注任意类为 Spring 组件。如果一个Bean不知道属于哪个层,可以使用@Component 注解标注。
@Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
@Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。
@Controller : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
声明式事务又分为两种:
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
下面的方式都可以不被持久化。
static String transient1; // not persistent because of static
final String transient2 = “Satish”; // not persistent because of final
transient String transient3; // not persistent because of transient
@Transient
String transient4; // not persistent because of @Transient
@value
简单配置@ConfigurationProperties
读取@PropertySource
读取指定 properties 文件详情参考Spring 如何优雅读取配置文件?
参考:Java架构直通车——过滤器与拦截器使用