9、使用java的方式配置Spring
@Configuration
【涉及到底层的关系】
我们现在要完全不使用Spring的xml配置了,全权交给 Java 来做
JavaConfig 是 Spring的一个子项目,在Spring4 之后,他成为了一个核心功能。
实体类
//@Component
//这个注解的意思,就是说明这个类被Spring接管了,注册到容器中
public class User {
private String name;
public String getName() {
return name;
}
@Value("时倾aaa")
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
配置类
@Configuration
//这个也会被Spring 托管,注册到容器中,因为他本来就是一个@Component,@Configuration代表这是一个配置类,就和之前的beans.xml一样
@ComponentScan("com.lsw.pojo")
@Import(SqConfig2.class)
public class SqConfig {
@Bean
// 注册了一个bean,就相当于我们之前写的一个bean 标签。
// 这个方法的名字就相当于bean 标签中的id属性
// 这个方法的返回值就相当于bean标签中的class属性
public User getUser(){
return new User();//就是返回要注入到bean的对象!
}
}
测试类
public class MyTest {
public static void main(String[] args) {
// 如果我们完全使用了配置类的方式去做,我们就只能通过AnnotationConfig 上下文来获取容器。通过配置类的class对象加载
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SqConfig.class);
User getUser = context.getBean("getUser", User.class);
System.out.println(getUser.getName());
}
}
这种的纯 Java 配置方式,在Springboot中随处可见
10、代理模式
为什么要学习 【代理模式】?
- 因为这就是SpringAOP的底层
- 面试 SpringAOP 和 SpringMVC
代理模式的分类
- 静态代理
- 动态代理
10.1、静态代理
角色分析:
- 抽象角色:一般会使用接口 或者 抽象类来解决
- 真是角色:被代理的角色【房东】
- 代理角色:代理真实的角色,代理真实角色后,我们一般会做一些附属操作【中介】
- 客户:访问代理对象的人【租房人】
代码步骤
- 接口【房子】
//虚拟存在的房子
public interface Rent {
public void rent();
}
- 真实角色
//房东
public class Host implements Rent{
public void rent() {
System.out.println("房东要出租房子");
}
}
- 代理角色
//代理
public class Proxy implements Rent{
private Host host;
public Proxy(){
}
public Proxy(Host host){
this.host = host;
}
public void rent() {
seeHost();
host.rent();
hostong();
fare();
}
// 看房
public void seeHost(){
System.out.println("中介带你看房");
}
// 收中介费
public void fare(){
System.out.println("中介收费");
}
// 签合同
public void hostong(){
System.out.println("前租赁合同");
}
}
- 客户端访问代理
//租客
public class Client {
public static void main(String[] args) {
// Host host = new Host();
// host.rent();
// 房东要租房
Host host = new Host();
// 代理,中介帮房东租房子,但是,代理会有一些附属操作
Proxy proxy = new Proxy(host);
// 你不用面对方法,直接找中介租房
proxy.rent();
}
}
代理模式的好处
- 可以使真实角色的操作更加纯粹,不用关注一些公共的业务
- 公共也就交给了代理角色,实现业务分工
- 公共业务发生扩展的时候,方便集中管理
缺点
- 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低
10.2、加深理解
我们可以这样认为,建立一个接口,想象成“房子”里面的方法就是他自己的条件。房子就是一个虚拟的事物
//虚拟事物——房子
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
那么房东要卖房子,就要对房子进行说明,也就是实现里面的方法。房东就是真实存在的事物。—>主观事物
//真实事物——房东
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("修改了一个用户");
}
public void query() {
System.out.println("查询一个用户");
}
}
房东要买房子就需要找到中介帮忙出售。那么中介就是我们的代理对象了。中介不光是来 表述 房东的要求,还可以从中加入自己的条件来筛选租客,也就是下面提到的“织入模式”。
//代理角色——中介
public class UserServiceProxy implements UserService{
UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
public void add() {
log("add");
userService.add();
}
public void delete() {
log("delete");
userService.delete();
}
public void update() {
log("update");
userService.update();
}
public void query() {
log("query");
userService.query();
}
// 日志方法
public void log(String msg){
System.out.println("[Debug]使用了"+ msg + "方法");
}
}
最后就是租客通过中介 租到房子了,这个过程中会实现里面的所有方法。
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
UserServiceProxy userServiceProxy = new UserServiceProxy();
userServiceProxy.setUserService(userService);
userServiceProxy.add();
}
}
上面的这种方式可以实现在不破坏原有代码的基础上,利用==“织入”模式==来实现代码的动态配置。
AOP模式
10.3、动态代理
- 动态代理和静态代理 角色一样
- 动态代理的类是动态生成的,不是我们直接写好的
- 动态代理分为两大类:
- 基于接口的动态代理——JDK 动态代理
- 基于类的动态代理——cglib
- java字节码:Javasist
需要了解两个类:
-
Proxy:代理;
-
InvocationHandler:调用处理程序
InvocationHandler
java.lang.reflect包下的
是由代理实例实现的接口
只有一个 invoke()方法
Proxy
提供了动态代理类和实例的静态方法,它是这些方法创建的所有代理类的超类。
动态代理类创建
//使用这个类,自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
// 被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
// 生成代理类
public Object getProxy(){
/**
*this.getClass().getClassLoader()加载的类在什么位置
* rent.getClass().getInterfaces()表示代理的接口是哪一个,即获取代理类的接口
* this代表InvocationHandler
*/
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
}
// 处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
// 动态代理的本质就是反射机制实现的
Object result = method.invoke(target,args);
return result;
}
public void log(String msg){
System.out.println("执行了"+msg+"方法");
}
}
this.getClass().getClassLoader()
加载的类在什么位置rent.getClass().getInterfaces()
表示代理的接口是哪一个,即获取代理类的接口this
代表InvocationHandler
,自己实现的接口
实现代理对象
public class Client {
public static void main(String[] args) {
// 真实角色
UserServiceImpl userService = new UserServiceImpl();
// 代理角色:不存在——> 使用代理类生成代理角色
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userService);//设置要代理的对象
// 动态生成代理类——>创建代理对象
UserService proxy = (UserService) pih.getProxy();
proxy.add();
}
}
动态代理的好处
- 可以使真实角色的操作更加纯粹,不用关注一些公共的业务
- 公共也就交给了代理角色,实现业务分工
- 公共业务发生扩展的时候,方便集中管理
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务。
- 一个动态代理类可以代理多个类,主要是实现了同一个接口即可。
11、AOP
11.1、什么是AOP
AOP(Aspect Oriented Programming)翻译为:面向切面编程,通过预编译方式和运行期动态代理实现程序的功能的统一维护的一种技术。AOP是 OOP (面向对象)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生泛型,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
11.2、AOP在Spring中的作用
提供声明式事务:允许用户自定义切面
- 横切关注点:跨越应用程序多个模块的方法或者功能,既是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如:日志,安全,缓存,事务等等……
- 切面(ASPECT):横切关注点被模块化的特殊对象,即,他是一个类。
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
- 目标(Target):被通知的对象
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知执行的“地点”的定义。
- 连接点(JointPoint):与切入点匹配的执行点
Spring AOP 中,通过Advice定义横切逻辑,String中支持5种类型的Advice
即AOP在 不改变原有代码的情况下,去增加新的功能
11.3、使用Spring实现AOP
【重点】使用AOP织入,需要导入一个依赖包
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
方式一:使用Spring的API接口【主要是SpringAPI接口实现】
操作接口
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("修改了一个用户");
}
public void select() {
System.out.println("查询了一个用户");
}
}
前置通知日志
//前置通知
public class Log implements MethodBeforeAdvice {
//method:要执行的目标对象的方法
//Objects:参数
//target:目标对象
public void before(Method method, Object[] objects, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
}
}
后置通知日志
//后置通知
public class AfterLog implements AfterReturningAdvice {
//returnValue:返回值
public void afterReturning(Object returnValue, Method method, Object[] objects, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"方法。返回结果为:"+returnValue);
}
}
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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册bean-->
<bean id="userService" class="com.lsw.service.UserServiceImpl"/>
<bean id="log" class="com.lsw.log.Log"/>
<bean id="afterLog" class="com.lsw.log.AfterLog"/>
<!-- 方式一:使用原生 Spring API约束-->
<!-- 配置AOP-->
<aop:config>
<!-- 切入点 expression:表达式,expression="execution(要执行的位置)"-->
<aop:pointcut id="pointcut" expression="execution(* com.lsw.service.UserServiceImpl.*(..))"/>
<!-- 执行环绕增加-->
<!-- 把类切入到某个切入点-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试
public class MyTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 动态代理代理的是接口
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
execution表达式
execution(* com.lsw.service.UserServiceImpl..*.*(..))
execution()
:表达式主体- 第一个
*
:表达式返回值类型,*
号表示所有的类型 - 包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的子包,com.lsw.service.UserServiceImpl、子孙包下的所有类的方法
- 第二个
*
:表示类名,*
表示所有类 *(..)
:最后这个星号表示 方法名,*
表示所有方法,后面括号表示 方法的参数,两个句点表示所有参数
方式二:自定义类实现AOP【主要是切面定义】
自定义类
public class DiyPointCut {
public void before(){
System.out.println("======方法执行前======");
}
public void after(){
System.out.println("======方法执行后======");
}
}
<bean id="diy" class="com.lsw.diy.DiyPointCut"/>
<aop:config>
<!-- 自定义切面,ref 是要引用的类-->
<aop:aspect ref="diy">
<!-- 切入点-->
<aop:pointcut id="point" expression="execution(* com.lsw.service.UserServiceImpl.*(..))"/>
<!-- 通知-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
方式三:使用注解实现
@Aspect
==> 标注这个类是一个切面@Before()
@After()
@Around()
//使用注解方式 实现AOP
@Aspect//标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* com.lsw.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("=====方法执行前======");
}
@After("execution(* com.lsw.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("=====方法执行后========");
}
// 在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
@Around("execution(* com.lsw.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
Signature signature = jp.getSignature();//获取签名
System.out.println("signature"+signature);
// 执行方法
Object proceed = jp.proceed();
System.out.println("环绕后");
}
}
12、整合Mybatis
步骤:
1、导入相关 jar包
- Junit
- mybatis
- mysql数据库
- spring相关的包
- AOP织入
- mybatis-spring 【new】
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!-- spring 操作数据库还需要 spring-jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.5</version>
</dependency>
</dependencies>
12.1、mybatis回顾
- 编写实体类
- 编写核心配置文件
- 编写接口
- 编写mapper.xml
- 测试
12.2、Mybatis-spring方式一
(1)、编写数据源
DataSource
使用Spring的数据源替换Mybatis 中的配置c3p0、 dbcp 、deuid
。这个时候我们就不在需要通过mybatis的文件进行配置了。
我们这里使用的是 spring提供的JDBC org.springframework.jdbc.datasource
来创建数据源
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/eesy_mybaits?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
(2)、SqlsessionFactory
在里面可以绑定 mybatis的配置文件和 进行一些mybatis的配置如:别名管理,映射器
<!-- sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 绑定mybatis 配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/lsw/mapper/*.xml"/>
</bean>
(3)、salSessionTemplate
Template——>模板
salSessionTemplate就是sqlsession 的模板
<!-- SqlSessionTemplate 就是 我们使用的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!-- 只能通过构造器进行注入 sqlsessionFactory 。因为没有set 方法-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
(4)、需要给接口添加实现类
public class UserMapperImpl implements UserMapper {
// 我们所有操作,以前都是由 sqlSession 来执行,现在都使用sqlSessionTemplate
private SqlSessionTemplate sqlSessionTemplate;
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
public List<User> selectUser() {
UserMapper mappper = sqlSessionTemplate.getMapper(UserMapper.class);
return mappper.selectUser();
}
}
(5)、将自己写的实现类注入到Spring中
<bean id="userMapper" class="com.lsw.mapper.UserMapperImpl">
<property name="sqlSessionTemplate" ref="sqlSession"/>
</bean>
(6)、测试类
@Test
public void test1() throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
12.3、Mybatis-spring方式二
SqlSessionDaoSupport
(1)数据源
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/eesy_mybaits?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/lsw/mapper/*.xml"/>
</bean>
(2)sqlsession创建
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
public List<User> selectUser() {
SqlSession sqlSession = getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}
(3)注入sqlsession
<bean id="userMapper2" class="com.lsw.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
(4)测试
@Test
public void test1() throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
13、声明式事务
13.1、回顾事务
要么都成功,要么都失败
-
把一组事务当作一个事务来做
-
事务在项目开发中,十分重要,涉及到数据的一致性,不可马虎
-
确保完整性和一致性
事务的ACID原则
- 原子性——不可分
- 一致性——资源和状态保持一致
- 隔离性
- 多个业务可能操作同一个资源是互相隔离的,防止数据损害
- 持久性
- 事务一旦提交,无论系统发生什么问题,结果都不会受到影响。被持久化的写到存储器中
13.2、spring事务管理
- 声明式事务:AOP
- 编程式事务:需要在代码中进行事务管理
思考:为什么需要事务
- 如果不配置事务,可能存在数据提交不一致的情况
- 如果我们不在spring中配置声明式事务,我们就需要在代码中手动配置事务
- 事务在项目的开发中十分重要,设计到数据的一致性和完整性问题。
配置
<!-- 配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
<!-- 结合AOP实现事务的织入-->
<!-- 配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 给哪些方法配置事务-->
<!-- 配置事务的传播特性-->
<tx:attributes>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="add" propagation="REQUIRED"/>
<!-- 只读-->
<tx:method name="query" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 配置事务切入-->
<aop:config>
<!-- 事务切入点-->
<aop:pointcut id="txPointCut" expression="execution(* com.lsw.mapper.*.*(..))"/>
<!-- 切入 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
实现类
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
public List<User> selectUser() {
User user = new User(7, "事这", "1234");
UserMapper mappper = getSqlSession().getMapper(UserMapper.class);
mappper.addUser(user);
mappper.deleteUser(5);
return mappper.selectUser();
}
public int addUser(User user) {
return getSqlSession().getMapper(UserMapper.class).addUser(user);
}
public int deleteUser(int id) {
return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
}
}
测试
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
}
事务的传播特性
传播行为(PROPAGATION) | 描述 |
---|---|
REQUIRED | 如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。 |
SUPPORTS | 支持当前事务,如果没有当前事务,就以非事务方法执行。 |
MANDATORY | 使用当前事务,如果没有当前事务,就抛出异常。 |
REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
NEVER | 以非事务方式执行操作,如果当前事务存在则抛出异常。 |
NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED 类似的操作 |
Spring基础到此就学习完了,详细请关注狂神