Spring
实现类与类之间的解耦合:便于类与类之间的管理
1.轻量:运行时占用资源少,运行效率高,不依赖其他jar
2.解耦合:spring提供了ioc控制反转 实现了由容器管理对象,对象的依赖关系,原来在程序代码中实现对象的创建,现在由容器完成,实现对象之间的解耦合
3.aop的支持
4.可以集成各种优秀的框架(mybatis)
核心
ioc
DI(作为ioc的技术实现):依赖注入 只需要对象的名字,就可以使用对象
spring底层使用的反射来创建对象
AOP
如果说 IoC 是 Spring 的核心,那么面向切面编程就是 Spring 最为重要的功能之一了,在数据库事务中切面编程被广泛使用。
AOP 即 Aspect Oriented Program 面向切面编程
首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。
- 所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
- 所谓的周边功能,比如性能统计,日志,事务管理等等
周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面
在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 “编织” 在一起,这就叫AOP
AOP 的目的
AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
AOP 当中的概念
- 切入点(Pointcut)
在哪些类,哪些方法上切入(where) - 通知(Advice)
在方法执行的什么实际(**when:**方法前/方法后/方法前后)做什么(**what:**增强的功能) - 切面(Aspect)
切面 = 切入点 + 通知,通俗点就是:在什么时机,什么地方,做什么增强! - 织入(Weaving)
把切面加入到对象,并创建出代理对象的过程。(由 Spring 来完成)
一个例子
为了更好的说明 AOP 的概念,我们来举一个实际中的例子来说明:
在上面的例子中,包租婆的核心业务就是签合同,收房租,那么这就够了,灰色框起来的部分都是重复且边缘的事,交给中介商就好了,这就是 AOP 的一个思想:让关注点代码与业务代码分离
加载方法
ApplicationContext:表示spring的容器,它是一个接口,有很多实现:
1. ClassPathXmlApplicationContext() -> 类路径扫描xml来创建容器
2. FileSystemXmlApplicationContext() -> 根据文件系统路径来读取xml创建容器
3. AnnotationConfigApplicationContext -> 根据注解配置类来创建容器
4. webXmlApplicationContext() -> web项目
基于配置文件
标准的spring配置类 命名:applicationContext.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">
<bean id="deom" class="xmlSpring.service.impl.DeomImpl"/>
<!--原型模式 勤快加载 每调用一次初始化一次-->
<bean id="deom1" class="xmlSpring.service.impl.DeomImpl" scope="prototype"/>
<!--会发现使用xml加载外部类相对灵活-->
<bean id="date" class="java.util.Date"/>
</beans>
基于xml需要一个xml配置文件,里面配置需要托管的bean
//通过类路径加载xml文件
ApplicationContext ac=new ClassPathXmlApplicationContext("beans.xml");
Deom d= (Deom) ac.getBean("deom");
d.printHello();
DI注入
-
通过property注入(bean中必须存在set方法 但是可以不存在属性名)
ApplicationContext ac=new ClassPathXmlApplicationContext("beans.xml"); Student student = (Student) ac.getBean("student"); System.out.println(student);
<bean id="student" class="xmlSpring.bean.Student"> <!-- 设置值的方法 --> <property name="age" value="12"/> <!-- 设置属性的方法 --> <property name="school" ref="school"/> <property name="name" value="马道宇"/> </bean> <bean id="school" class="xmlSpring.bean.School"> <property name="name" value="湖南工学院"/> <property name="age" value="100"/> </bean>
-
通过构造方法注入
Student student1= (Student) ac.getBean("student1"); System.out.println(student1);
<bean id="student1" class="xmlSpring.bean.Student"> <!--可以使用name,index或者不声明 index需要按有参构造的顺序指定 不声明不能调整位置--> <constructor-arg name="age" value="1"/> <constructor-arg name="name" value="asd"/> <constructor-arg name="school" ref="school"/> </bean> <bean id="school" class="xmlSpring.bean.School"> <property name="name" value="湖南工学院"/> <property name="age" value="100"/> </bean>
-
自动注入(只能给引用自动注入,不能给基本字段注入 如:String int)
1.byName
<bean id="student2" class="xmlSpring.bean.Student" autowire="byName"> <!-- 设置值的方法 --> <property name="age" value="12"/> <property name="name" value="马道宇"/> </bean> <bean id="school" class="xmlSpring.bean.School"> <property name="name" value="湖南工学院"/> <property name="age" value="100"/> </bean>
student中有一个字段名为school的属性 autowrite通过byname 字段名自动注入配置类中名为school的字段
-
ByType
<bean id="student3" class="xmlSpring.bean.Student" autowire="byType"> <!-- 设置值的方法 --> <property name="age" value="12"/> <!-- 设置属性的方法 --> <!-- <property name="school" ref="school"/>--> <property name="name" value="马道宇"/> </bean> <bean id="school" class="xmlSpring.bean.School"> <property name="name" value="湖南工学院"/> <property name="age" value="100"/> </bean>
student中有一个字段为school的属性 autowrite通过byType 查找同类型的school自动注入
-
多配置文件
项目过大配置文件会很大,所以要分开写
import关键字
<!-- <import resource="classpath:spring/spring-School.xml"/>-->
<!-- <import resource="classpath:spring/spring-Student.xml"/>-->
<!-- 在使用通配符时,注意主配置文件名防止和通配文件冲突,
配置文件上层一定要用一个文件夹 否则查找不到
-->
<import resource="classpath:spring/spring-*.xml"/>
基于注解
声明bean的注解(类上)
value属性:默认为类名首字母小写
@Component 组件,没有明确的角色
@Service 在业务逻辑层使用(service层)
@Repository 在数据访问层使用(dao层)
@Controller 在展现层使用,控制器的声明(C)
注入bean的注解
ps:给user对象自动注入一个对象
@Autowired
user u
有两个个实现类时,初始化接口有2种办法
* 1.在实现类上加@Primary
* 2.在变量初始化时加@Qualifier("想要的实现类")
* 3.在变量使用resource(name=要实现的类)
@Autowired:由Spring提供 默认byType
@Inject:由JSR-330提供
@Resource:由JSR-250提供 先使用byName原则 再使用byType
都可以注解在set方法和属性上,推荐注解在属性上(一目了然,少写代码)。
java配置类相关注解
1.纯java
@Configuration 声明当前类为配置类,相当于xml形式的Spring配置(必须有,可以是空类)一般放置实现类,提供main方法使用//把该类单做ioc容器,该过程为依赖注入(di)//@Configuration//扫描包//@ComponentScan(basePackages = {"Bean"})public class App { public static void main(String[] args) { // 注解解析器的功能 tomcat loadClass(); class-> 读取 ApplicationContext ctx = new AnnotationConfigApplicationContext(App.class); System.out.println("容器创建"); Hello h = (Hello) ctx.getBean("h"); h.sayHello(); } }@Bean 注解在方法上,声明当前方法的返回值为一个bean,替代xml中的方式(方法上)@Configuration 声明当前类为配置类,其中内部组合了@Component注解,表明这个类是一个bean(类上)@ComponentScan 用于对Component进行扫描,相当于xml中的(类上)@WishlyConfiguration 为@Configuration与@ComponentScan的组合注解,可以替代这两个注解
2.配置文件
<!--包扫描器--><context:component-scan base-package="noteSpring;xmlSpring"/>
ApplicationContext ac=new ClassPathXmlApplicationContext("noteSpring/applicationContext.xml");TestNote student = (TestNote) ac.getBean("testNote");
读取配置文件
使用$符
public class TestNote { @Value("${name}") private String name;// @Autowired private School s;}
<context:property-placeholder location="app.properties" file-encoding="utf8"/>
AOP(aspectj依赖)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aKpOA5s0-1644158766294)(C:\Users\fox11\AppData\Roaming\Typora\typora-user-images\image-20201205110940321.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qr0LJWXi-1644158766306)(C:\Users\fox11\AppData\Roaming\Typora\typora-user-images\image-20201205110903242.png)]
执行顺序
构造函数(初始化容器时)–>
@PostConstruct(初始化容器时,构造函数一完成就执行)–>
@Around(执行方法和前置增强之前 .proceed()方法之前的代码)–>
@Before(前置增强)–>
类的方法–>
@AfterThrowing||@AfterReturning(有异常或者没异常)–> 出现异常停止
After()–>
@Around(.proceed()方法之后的代码,方法中出现异常则不运行)
代码实现
Aspect类
@Aspect(添加到类上面 表示这是一个aspect的类 还要托管到spring 依赖为 aspectj)
@org.aspectj.lang.annotation.Aspect@Componentpublic class Aspect { /** * 前置通知注释 * 要求 * 1.公共方法 public * 2.方法没有返回值 * 3.方法名称自定义 * 属性value 是切入点表达式 * 特点: * 1.在目标方法之前先执行 * 2.不会改变目标方法的执行结果 * 3.不会影响目标方法的执行 * 参数: * JoinPoint:业务方法,要加入切面功能的业务方法 * 作用: 可以在通知方法中获取方法执行时的信息,例如方法名,方法实参 * 这个参数是由框架赋予的不行是第一个位置的参数 * * @param jp */ @Before("execution(public * AOP..*.add*(..))") public void before(JoinPoint jp) { System.out.println("=========前置通知开始============"); for (Object o : jp.getArgs()) { System.out.println(o); } System.out.println("=========前置通知结束============"); } /**** * 后置通知 * 要求 * 1.公共方法 public * 2.方法没有返回值 * 3.方法名自定义 * 4.方法有参数的,推荐是Object * 属性 * 1.value 切入点表达式 * 2.returning 自义定变量,表示目标方法的返回值 * 自义定名称必须和通知方法的形参名一样 * 特点 * 1.在目标方法之后执行 * 2.能获取目标方法的返回值 * 3.可以修改这个返回值 * @param res */ @AfterReturning(value = "execution(public * AOP..*.add*(..))", returning = "res") public void AfterReturning(JoinPoint jp, Object res) { //值传递 所有不改变原参数的值 System.out.println("=========后置通知开始=========="); System.out.println(res); System.out.println("方法信息" + jp.getSignature()); System.out.println("=========后置通知结束=========="); } /** * 环绕通知 * 属性 * value 切入点表达式 * 特点 * 1.他是功能最强的通知 * 2.在目标方法的前和后都能增强功能 * 3.控制目标方法是否被调用 * 4.修改原来的目标方法的执行结果,影响最后的调用结果 **** * 返回值 * Object * 它等同于jdk动态代理 * ProceedingJoinPoint 等同于Method * * 一般用来做事务处理 * * @param pjp */ @Around(value = "execution(public * AOP..*.add*(..))") public Object Around(ProceedingJoinPoint pjp) throws Throwable { Object result = null; System.out.println("=========环绕通知开始=========="); //接口需要继承JoinPoint 获取信息 if (12 == Integer.valueOf(pjp.getArgs()[0].toString())) { result = pjp.proceed(); } System.out.println("方法信息" + pjp.getSignature()); System.out.println("=========环绕通知结束=========="); //环绕通知牛逼的地方,可以改变结果 if (result != null) { result = 55; } return result; } /** * 要求 * 1.public * 2.没有返回值 * 3.方法名自定义 * 4.方法有一个Exception,如果还有就是 jp * 属性 * 1.value 切入点表达式 * 2.throwing 抛出异常的对象 名字和传入参数名一样 * @param jp * @param ex */ @AfterThrowing(value = "execution(public * AOP..*.add*(..))", throwing = "ex") public void AfterReturning(JoinPoint jp, Exception ex) { //值传递 所有不改变原参数的值 System.out.println("=========异常通知开始=========="); ex.printStackTrace(); System.out.println("方法信息" + jp.getSignature()); System.out.println("=========异常通知结束=========="); } /** * 要求 * 1.public * 2.没有返回值 * 3.方法名自定义 * 属性 * 1.value * 特点: * 总是被执行 * 可以做资源关闭 * @param jp */ @After(value = "mypt()") public void After(JoinPoint jp) { //值传递 所有不改变原参数的值 System.out.println("=========最终通知开始=========="); System.out.println(jp.getSignature()); System.out.println("=========最终通知结束=========="); } /** * 定义和管理切入点,如果你的项目中有多个切入点表达式是重复的 * 属性 * value 切点表达式 * 特点 * 在其他通知中可以使用这个方法名 代替切入点表达式 */ @Pointcut(value = "execution(public * AOP..*.add*(..))") public void mypt() { }}
注解类的实现类
@Configuration@ComponentScan(basePackages = "AOP")//声明使用了aop@EnableAspectJAutoProxypublic class app { public static void main(String[] args) { ApplicationContext ac=new AnnotationConfigApplicationContext(app.class); System.out.println(ac.getApplicationName()); add add = (AOP.add) ac.getBean(AOP.add.class); add.add(12); }}
配置文件的实现方式
<bean id="add" class="AOP.impl.addImpl"/><!-- 声明切面--> <bean id="as" class="AOP.Aspect"/><!-- 声明自动代理--> <aop:aspectj-autoproxy />
在spring中
如果类是有上级接口的则自动使用jdk代理
com.sun.proxy.$Proxy17
如果没有则自动使用cglib代理
AOP.impl.addImpl$$EnhancerBySpringCGLIB$$4d366cd
强制使用cglib动态代理
注解方式
@EnableAspectJAutoProxy(proxyTargetClass = true)
配置文件方式
<aop:aspectj-autoproxy proxy-target-class="true"/>
动态增强两种实现方式
jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。
jdk动态代理是由Java内部的反射机制来实现的
cglib动态代理底层则是借助asm来实现的
总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。。
(创建代理对象,调用方法时自动回调接口方法)
jdk
public class App implements InvocationHandler{ private Object a; public Object createCroxy(Object a) { this.a=a; //第一个参数 目标类的类加载器 //第二个参数 目标类接口(模板) 因为proxy要根据这个模板来生成新的代理对象 //第三个参数 表示激活被调用方法,自动回调,InvocationHandler实例中的 invoke return Proxy.newProxyInstance(a.getClass().getClassLoader(), a.getClass().getInterfaces(), this); } @Override /** * 参数 * 代理对象 连接点 被调用方法的参数 */ public Object invoke(Object proxy, Method m, Object[] arg) throws Throwable { //先调用目标类对象 Object o=m.invoke(a, arg); if(m.getName().startsWith("add")) { //相当于execution(* com.LightseaBlue.Spring..*.add*(..)) System.out.println(""); } return o; } }
test
UserDaoimpl ud=new UserDao();ud.addUser(); App a=new App();UserDaoimpl ud1=(UserDaoimpl) a.createCroxy(ud);ud1.addUser();ud1.sayName();
cglib
(需要引入第三方jar包 cglib-nodep)
public class cglib implements MethodInterceptor{ private Object o; public Object createProxy(Object o) { this.o=o; Enhancer e=new Enhancer(); //设置父类也就是接口类 e.setSuperclass(o.getClass()); //设置回调 e.setCallback(this); return e.create(); } @Override /** * 代理对象 类的方法 方法参数 */ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { Object x=method.invoke(o, args); System.out.println("后置增强"); return x; }}
Test
UserDaoimpl ud=new UserDao();cglib c=new cglib();UserDaoimpl ud1=(UserDaoimpl) c.createProxy(ud);ud1.addUser();
Spring事务
1.事务隔离级别:读未提交 读已提交 可重复读 串行化
2.超时时间
3.事务传播行为
required supports required-new(一定是新建自己的事务,如果存在当前事务挂起,先执行自己的的事务)
使用
@EnableTransactionManagement 开启事务放在启动类,其实默认开启
例:(一般是放在service层上)
@Override@Transactional(rollbackFor = Exception.class)public boolean upDateUserStu(Integer uId, Integer uStu) throws Exception { UpdateWrapper<TableUser> tableUserUpdateWrapper = new UpdateWrapper<>(); tableUserUpdateWrapper.lambda().eq(TableUser::getUId, uId).set(TableUser::getUStu, uStu); boolean update = this.update(tableUserUpdateWrapper); boolean updateAudioNameByUid = tableAudioNameService.updateAudioNameStuByUid(uId, uStu); boolean b = update && updateAudioNameByUid; if(!b){ throw new Exception("回滚。。"); } return true;}
小知识点
创建时机
单例模式 在容器创建时创建好对象
勤快加载,单例模式
别名机制
1. 在注释中添加value值 ps:server(value="s")2. @Bean public server zz(){ return new server(); } 适用于无法修改源代码的外部包
有个实现类时,初始化接口有2种办法
- 在实现类上加@Primary(设置类优先)
- 在初始化时加@Qualifier(“想要的实现类”)
单例或多例 ?
默认单例
什么时候创建的?
勤快加载 -> 懒加载???
创建容器时初始化
默认情况 勤快 设置懒加载在bean上添加@lazy(true)开启 开启后 获取获取对象时初始化
多例模式也是获取对象时初始化 设置@scope()
1. singleton 单例模式, 全局有且仅有一个实例
- prototype 原型模式 每次获取Bean的时候会有一个新的实例
- request request 表示该针对每一次HTTP请求都会产生一个新的bean,
- session session 作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
- global session 作用域类似于标准的HTTP Session作用域,
调用无参构造方法
调用无参构造方法,必须提供无参构造方法
- 是否有更多的生命周期回调方案??? init destroy
@PostConstruct:在构造方法和init方法(如果有的话)之间得到调用,且只会执行一次。
@PreDestory:注解的方法在destory()方法调用后得到执行。