1.Spring概念
Spring框架是一个轻量级开源的应用程序框架,用于开发Java企业级应用程序。它提供了一种综合的编程和配置模型,可以帮助开发人员构建灵活、可扩展且易于维护的应用程序。
Spring的根本使命:简化Java开发
【Spring给予了Java新生】
1.1 Spring特点
-
解耦和简化开发:通过IOC容器管理对象的依赖关系,简化了代码的耦合度,让开发者专注于业务逻辑的实现。
-
IOC容器管理对象:Spring负责对象的创建和注入,降低了对象之间的耦合度。
-
集成支持:提供对主流框架的集成支持,方便开发者使用其他框架的功能。
-
面向切面编程(AOP):实现横切关注点的模块化,如权限拦截、事务管理、日志记录等。
-
高度可开放性:开发者可以选择使用Spring的部分或全部特性,也可以与其他框架灵活集成。
2.Spring核心(Spring Core)
spring为企业级开发提供了丰富的功能,这些功能的底层都依赖于他的两个核心特性:依赖注入(DI)、面向切面编程(AOP)
2.1 依赖注入
DI能够让相互协作的软件组件保持松散耦合,依赖注入通过IOC容器实现
2.1.1 耦合度
耦合就是联系的紧密程度,一方面,紧密耦合的代码难以测试、难以复用、难以理解,经常修复一个bug,出现一个或者更多新的bug另一方面,一定程度的耦合又是必须的,完全没有耦合的代码什么也做不了
通过DI,对象无需自己创建依赖关系,依赖关系将被自动注入到需要他们的对象中
2.1.2 控制反转(IOC)
IOC是一种软件设计原则,基本概念:不创建对象
传统的程序设计中,对象之间的依赖关系是由对象自己创建和管理的。这样的设计方式导致了高耦合度、难以维护和测试的代码。
而控制反转则是将对象之间的依赖关系的控制权反转给了框架或容器。对象不再直接创建和管理它所依赖的对象,而是由外部的框架来负责创建和注入依赖。对象只需声明它所需要的依赖,由框架在合适的时机将依赖注入进去。
IOC容器负责创建、组装和管理对象,对象只需通过配置文件或注解来声明依赖关系,而不需要显式地创建和管理这些对象。框架会根据配置文件或注解的信息,自动创建和注入依赖对象,从而实现了对象之间的解耦。
2.1.3 依赖注入的方式
1.构造函数注入
创建对象模式: | |
singleton | “单例”,默认情况下Spring只会给一个bean创建一个对象,在配置文件加载时创建 |
prototype | 原型,可以创建多个对象,配置文件加载时不会创建对象,只有在需要时才会创建 |
<!--调用构造方法创建对象-->
<bean id="user" class="com.woniuxy.entity.User" scope="prototype">
<constructor-arg name="account" value="zhagnsan"></constructor-arg>
<constructor-arg name="password" value="zhagnsan"></constructor-arg>
</bean>
//1.加载spring的配置文件,得到应用程序上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
//2.获取想要的对象
Object aaa = context.getBean("user");
2.Setter方法注入
先在相应的xml文件中创建对象,并赋值
<!--通过setter赋值:无参构造+setter-->
<bean id="aaa" class="com.entity.User" >
<property name="account" value="list"></property>
<property name="password" value="123"></property>
</bean>
再在需要调用对象的类中加载spring的配置文件来得到上下文对象,再用上下文对象获取要调用的对象
//1.加载spring的配置文件,得到应用程序上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
//2.获取想要的对象
Object aaa = context.getBean("aaa");
3.注解注入:日常开发用的最多
实现起来简单,只需给变量添加一个@Resource或者@Autowired注解,给类加一个@Component注解(或@Controller或@Service)并在xml文件中开启注解扫描即可
@Resource默认通过id匹配,@Autowired默认通过class匹配
@Resource
private EmployeeServiceimpl employeeServiceimpl;
@Autowired
private EmployeeServiceimpl employeeServiceimpl;
<!--开启注解扫描,扫描指定的包,创建对象-->
<context:component-scan base-package="com.*"></context:component-scan>
2.2 面向切面编程(AOP)
面向切面编程允许把遍布应用各处的功能分离出来形成可重用的组件
2.2.1 不使用面向切面编程的问题
1.有时一段代码可能会重复出现在多个组件中,当你想改变这段代码时,必须修改各个组件中的相关实现,就算把这段代码封装起来,其他组件只是调用他的方法,但方法的调用还是会重复出现在各个组件中
2.组件会因为那些与自身核心业务无关的代码而变得混乱,一个向地址册增加地址条目的方法应该只关注如何添加地址,而不应该关注它是不是安全的或者是否需要支持事务
2.2.2 常用概念
切点(Pointcut) | 描述 |
---|---|
切点方法① | 需要添加额外功能的方法 |
前置通知(Before Advice)② | 在切点之前执行的功能。在方法执行之前触发的通知。 |
后置通知(After Advice)③ | 在切点之后执行的功能。在方法执行之后触发的通知,无论方法是否出现异常。 |
异常通知(Throws Advice)④ | 如果切点执行过程中出现异常,会触发异常通知。捕获方法抛出的异常,并执行相应的处理逻辑。 |
切面(Aspect)⑤ | 所有功能总称,包含了切点和通知的组合。 |
织入(Weaving)⑥ | 把切面嵌入到原有功能的过程。将切面应用到目标对象的过程,使得切面的功能与目标对象的功能进行绑定。 |
2.2.3实现AOP
Spring提供了两种实现方式
1.xml配置实现AOP
1.1导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
1.2创建切面类
public class Aop {
public void begin() {
System.out.println("开始事务/异常");
}
public void after() {
System.out.println("提交事务/关闭");
}
public void afterReturning() {
System.out.println("afterReturning()");
}
public void afterThrowing() {
System.out.println("afterThrowing()");
}
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前....");
pjp.proceed(); // 执行目标方法
System.out.println("环绕后....");
}
}
1.3配置spring-context.xml
<!-- dao实例 -->
<bean id="userService" class="com.my.g_aop_xml.UserService"></bean>
<bean id="otherService" class="com.my.g_aop_xml.OtherService"></bean>
<!-- 切面类 -->
<bean id="aop" class="com.my.g_aop_xml.Aop"></bean>
<!-- AOP配置 -->
<aop:config>
<!-- 定义一个切入点表达式 -->
<aop:pointcut expression="execution(* com.my.g_aop_xml.*.*(..))" id="pt"/>
<!-- 配置切面 -->
<aop:aspect ref="aop">
<!-- 前置通知 -->
<aop:before method="begin" pointcut-ref="pt"/>
<!-- 后置通知 -->
<aop:after method="after" pointcut-ref="pt"/>
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="pt"/>
<!-- 返回后通知 -->
<aop:after-returning method="afterReturning" pointcut-ref="pt"/>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
1.4在某个类中调用
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("com/my/f_aop_anno/bean.xml");
//目标对象有实现接口,spring会默认采用JDK代理
IUserService userService = (IUserService) ac.getBean("userService");
System.out.println(userService.getClass());//$Proxy001
userDao.save();
}
2.注解实现AOP
2.1 公共接口添加@Component注解
@Component
public class UserServiceImpl implements UserService{}
2.2 创建通知类
@Component
@Aspect //切面配置
public class AnnoAdvice {
//1.配切点
// @Pointcut("execution(* com.woniuxy.ioc.UserServiceImpl.*(..))")
@Pointcut("@annotation(com.woniuxy.anno.CustomTransaction)")
public void pointcut(){};
//2.通知
@Before("pointcut()")
public void before(){
System.out.println("前置通知");
}
@After("pointcut()")
public void after(){
System.out.println("后置通知");
}
//环绕通知经常用来做日志的处理
@Around("pointcut()")
public void around(ProceedingJoinPoint point) throws Throwable {
System.out.println("around before");
/*1.知道马上要调用的方法是哪个*/
/*获取方法签名*/
MethodSignature methodSignature = (MethodSignature) point.getSignature();
/*获得方法*/
Method method = methodSignature.getMethod();
System.out.println("method:"+method.getName());
//被代理类的类型
String name = point.getTarget().getClass().getName();
System.out.println(name);
/*获取参数*/
Object[] args = point.getArgs();
//放行 让后续代码继续执行(通知方法)
point.proceed();
System.out.println("around after");
}
}
2.3 配置xml文件,开启注解扫描
<!--1.开启component注解扫描,创建实现类对象、通知类对象-->
<context:component-scan base-package="com.woniuxy.ioc"></context:component-scan>
<!--2.开启aspectj的注解扫描:识别通知、切点-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2.4 调用
//1.加载spring的配置文件,得到应用程序上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-anno.xml");
//获取对象
System.out.println(Arrays.toString(context.getBeanDefinitionNames()));
Object bean = context.getBean("userServiceImpl");
UserService userService = (UserService) bean;
userService.add();
注: 还可以用注解方式的切入点表达式,利用自定义注解来精细化获取对象
四大元注解:Java本身自带的注解,用来标识自定义注解使用方式
@Target:指定该注解能用在什么地方(FIELD属性、METHOD方法、TYPE类)
@Documented:该注解是否保存到doc文档中
@Inherited:是否继承,如果加上该注解,表示父类使用了该注解,子类默认也使用该注解
@Retention:指定注解的生命周期,在什么时候可以访问到该注解
(source源代码阶段能看到注解,代码编译后看不到;class在编译之后代码中有,在运行时没有;runtime在运行时也能访问到)
3.声明式事务配置
3.1注解的方式配置声明式事务
3.1.1 在EmployeeServiceImpl相关方法上添加@Transactional注解
@Transactional
@Override
public boolean add(Employee employee) {
boolean add = employeeMapper.add(employee);
return add;
}
3.1.2 配置spring-mybatis.xml文件
<!--5.利用aop实现事务管理:注解-->
<!--5.1配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定管理哪个数据库-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--5.2开启事务注解功能-->
<tx:annotation-driven></tx:annotation-driven>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
4.代理模式
代理模式是一种结构设计模式,它允许通过代理对象来控制对其他对象的访问。代理模式主要用于在不改变原始对象的情况下,增加额外的功能或控制对原始对象的访问。
4.1 静态代理
通过创建一个代理类来包装对象,并在代理类中实现对对象方法的增强或拦截
要求:1.需要一个公共的接口
2.被代理类需要实现该接口
3.代理类也需要实现该接口
4.代理类中需要拥有一个被代理类对象
4.1.1 创建代理类,代理类实现公共接口,并实现接口的方法
class UserServiceProxy implements UserService {
@Override
public void add() {
System.out.println("开启事务");
try {
target.add();
System.out.println("提交");
}catch (Exception e){
System.out.println("回滚");
}
}
}
4.1.2 在代理类中创建被代理对象,并创建构造方法给被代理对象赋值
//被代理对象
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
4.1.3 在某个类中调用
// 目标对象
UserService target = new UserServiceImpl();
// 代理
UserService proxy = new UserServiceProxy(target);
proxy.add(); // 执行的是,代理的方法
4.1.4 静态代理的优缺点
优点 | 可以做到在不修改目标对象的功能前提下,对目标对象功能扩展。 |
缺点 | 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,导致类太多。 一旦接口增加方法,目标对象与代理对象都要维护。 |
4.2 动态代理
4.2.1 JDK代理
代理类与被代理类之间是兄弟,代理对象的生成,是利用JDK API, 动态的在内存中构建代理对象
要求:被代理类必须实现至少一个接口
1.创建一个工厂类来创建代理类对象
//创建一个工厂类,用来创建代理类对象
class ProxyFactory{
//拥有一个被代理类对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//获取代理类对象
public Object getInstance(){
//参数1:classLoder类加载器
//参数2:被代理类实现的接口
//参数3:处理器,用来在执行某个方法之前或之后执行对应代码
return Proxy.newProxyInstance(ProxyFactory.class.getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
//匿名内部类
//参数1:代理类对象
//参数2:要调用的方法
//参数3:调用参数2时需要的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始");
//执行被代理类方法
Object invoke = method.invoke(target, args); //此处为UserServiceImpl.add
System.out.println("结束");
return invoke;
}
});
}
}
2.调用
//创建工厂
ProxyFactory proxyFactory = new ProxyFactory(new UserServiceImpl());
//获取代理类对象
Object instance = proxyFactory.getInstance();
UserService userService = (UserService) instance;
userService.add();
4.2.2 CGLIB代理
又叫子类代理,生成的代理类是被代理类的子类,用来弥补JDK代理不能代理没实现接口的类的缺点
要求:被代理类不能由final修饰
1.创建一个工厂类来创建代理类对象
class CglibFactory{
//被代理对象
private Object target;
public CglibFactory(Object target) {
this.target = target;
}
//获取代理类对象
public Object getInstace(){
//创建工具类对象
Enhancer enhancer = new Enhancer();
//设置代理类的父类是谁
enhancer.setSuperclass(target.getClass());
//设置方法拦截
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("开始");
Object invoke = method.invoke(target, objects);
System.out.println("结束");
return invoke;
}
});
//生成代理类对象
return enhancer.create();
}
}
2.调用
//创建工厂
ProxyFactory proxyFactory = new ProxyFactory(new UserServiceImpl());
//获取代理类对象
Object instance = proxyFactory.getInstance();
UserService userService = (UserService) instance;
userService.add();