IOC和AOP是Spring最核心的两个点 IOC: 控制反转 AOP :面向切面编程
IOC就是我们把定义的类注册到Sping容器中 从而我们再也不需要手动去new一个实例 而是通过Spring容器去取 。Spring容器将会根据配置文件(也可不必写配置文件直接使用注解配置)来创建调用者对象,同时把被调用的对象的实例化对象通过构造函数或者set()方法的形式注入到调用者对象当中。
通过IOC容器,开发人员不需要关注对象是如何创建的,同时增加新类也非常方便,只需要修改配置文件即可。
AOP 面向切面编程 例如,开发人员可以在不改变原来业务逻辑模型的基础可以进行动态的增加日志,安全或异常处理功能。
就是不改变原有的源代码 在某一个点(切面)的前后 增加一些新的功能 就是面向切面编程 Spring也支持这一功能 首先我们在配置文件中需要导入AOP相关的配置 我们有三种方式实现AOP
第一种 编写一个实现类实现相对应的接口
public class AfterLog implements AfterReturningAdvice {
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("已经执行了" + method.getName() + " 方法,结果为 " + o);
}
}
public class BeforeLog implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("在" + o.getClass().getName() + "执行了" + method.getName() + "方法");
}
}
MethodBeforeAdvice 接口表示是在切入点之前执行的功能 他有一些参数 可以获得类的名字 和 方法的名字等等。 AfterReturningAdvice 接口表示是在切入点之后执行的功能 他比MethodBeforeAdvice 多了一个参数 表示返回的值(因为他是切入点之后才执行的 切入点的方法可能会有一个返回值 它也能拿到)。
第二种方法 :任意编写一个类 然后在配置文件中对他进行改造 使他可以在切入点之前或之后执行
public class DiyPointCut {
public void before(){
System.out.println("+++++++++++before+++++++++++++++");
}
public void after(){
System.out.println("+++++++++++after+++++++++++++++");
}
}
第二种方法和第一种方法相比更为简便 因为他不需要实现那两个接口 而是通过配置文件改造 而也正是他没有实现那两个接口 所有他的功能很有限,他无法像第一种方法一样 可以获得切入点的一些相应的信息
第三种方法是通过注解配置 和第二种方法的功能类似 这里不多做描述了。
配置文件如下:
<?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 id="afterlog" class="com.Jee.log.AfterLog"/>
<bean id="beforelog" class="com.Jee.log.BeforeLog"/>
<bean id="userserviceimpl" class="com.Jee.service.UserServiceImpl"/>
<!-- <aop:config>-->
<!-- <!–第一种方式 : 切入点–>-->
<!-- <aop:pointcut id="pointcut" expression="execution(* com.Jee.service.UserServiceImpl.*(..))"/>-->
<!-- <!–执行环绕增加–>-->
<!-- <aop:advisor advice-ref="afterlog" pointcut-ref="pointcut"/>-->
<!-- <aop:advisor advice-ref="beforelog" pointcut-ref="pointcut"/>-->
<!-- </aop:config>-->
<bean id="diy" class="com.Jee.diy.DiyPointCut"/>
<aop:config>
<!--第二种方式 : 定义切面-->
<aop:aspect ref="diy">
<!--定义切入点-->
<aop:pointcut id="point" expression="execution(* com.Jee.service.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
aop:config标签是不能少的 aop的相关依赖也需要定义 aop:pointcut指的是切入点 它的expression表达式是这个切入点具体的位置 用一个execution()表示 这个是固定的写法
execution(* com.Jee.service.UserServiceImpl.(…)) 中表示任意的 这个表达式的意思式切入点的位置是 com.Jee.service包下的UserServiceImpl类中的任意方法。
IOC 控制反转
如果不适用注解的话 就直接在配置文件中定义类
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
bean标签用来实例化一个类 id表示这个实例的名字 class表示他的全类名
property标签中的name代表类中的属性名 value表示值
但是 这样的话 value只能表示一些基本数据类型 那些复杂的例如 数组 集合 map等数据类型无法表示
这个时候我们就需要使用其他的一些标签了
<bean id="address" class="com.Jee.entity.Address">
<property name="city" value="武汉"/>
<property name="stress" value="人民医院"/>
</bean>
<bean id="student" class="com.Jee.entity.Student">
<property name="id" value="1"/>
<property name="name">
<null/>
</property>
//ref表示一个引用 如果你定义好了一个属性 你直接可以使用ref来引用你定义好的id
<property name="address" ref="address"/>
<property name="books">
//如果类型是数组 就使用array标签表示 用value设置数组中的值
<array>
<value>"算法导论"</value>
<value>"Java核心"</value>
<value>"计算机组成原理"</value>
<value>"数据库基础"</value>
</array>
</property>
<property name="cards">
//如果是map类型的话 使用map标签 用过entry表示数据 entry中有key和value
<map>
<entry key="校园卡" value="这是一张校园卡"/>
<entry key="门禁卡" value="这是一张门禁卡"/>
<entry key="水卡" value="这是一张水卡"/>
</map>
</property>
<property name="games">
//如果数据类型是set类型 使用set标签 用value标签表示set中的值
<set>
<value>LOL</value>
<value>BOB</value>
<value>FOF</value>
</set>
</property>
<property name="hobbys">
//如果数据类型是集合 就使用list标签 value标签表示list中的值
<list>
<value>唱</value>
<value>跳</value>
<value>Rap</value>
<value>篮球</value>
</list>
</property>
<property name="info">
//类型是prop的话 使用这种(虽然我也不知道prop是什么类型)
<props>
<prop key="driver">driver</prop>
<prop key="url">url</prop>
<prop key="username">username</prop>
<prop key="password">password</prop>
</props>
</property>
</bean>
</beans>
使用注解就不需要在配置文件中定义类中的属性了 直接在文件中开启注解扫描 然后再指定需要扫描的包就行 spring就会自动扫描对应包下的所有注解
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解-->
<context:annotation-config/>
<!--扫描这个包下的所有注解-->
<context:component-scan base-package="com.Jee.entity"/>
<!-- <bean id="dog111" class="com.Jee.entity.Dog">-->
<!-- <property name="name" value="狮子汪"/>-->
<!-- </bean>-->
<!-- <bean id="cat222" class="com.Jee.entity.Cat">-->
<!-- <property name="name" value="喵尔摩斯"/>-->
<!-- </bean>-->
<!-- <bean id="person" class="com.Jee.entity.Person">-->
<!-- <property name="name" value="天人"/>-->
<!-- </bean>-->
</beans>
对应的类
//@Component注解代表这个类是一个组件 可以被@Autowired自动导入
@Component
public class Dog {
//@Value注解定义在字段上 表示这个字段的值
@Value("狮子汪")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
public void speak(){
System.out.println("wow wow wow ");
}
}
@Component
public class Person {
private String name;
//@AutoWired注解 表示自动导入 像这个例子 Cat类和Dog类 如果我们已经把他们添加到了Spring容器中 我们就可以使用@AutoWired实现自动导入 而不需要手动配置
@Autowired
private Cat cat;
@Autowired
private Dog dog;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", cat=" + cat +
", dog=" + dog +
'}';
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
}
使用一个配置类来生成我们定义的类 这样我们就不需要再去使用配置文件了 只需要定义一个配置类就行
User类
@Component
public class User {
@Value("1")
private int id;
@Value("魔法猫咪")
private String name;
private String[] games;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String[] getGames() {
return games;
}
public void setGames(String[] games) {
this.games = games;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", games=" + Arrays.toString(games) +
'}';
}
}
以上两种都是使用配置文件来注册相应的类的 要拿到我们定义好的类 需要使用如下的方法
public class MyTest {
//这个一个junit测试类
@Test
public void test(){
//使用new ClassPathXmlApplicationContext这个类 参数是我们配置文件的名称
//可以使用getBean来获得我们注册的类的实例 参数是我们bean标签中的id
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Person person = (Person) context.getBean("person");
System.out.println(person);
System.out.println(person.getCat());
person.getCat().speak();
System.out.println(person.getDog());
person.getDog().speak();
}
}
配置类(不需要使用配置文件了 直接定义这个类就行)
//@Configuration注解表示这个类是一个配置类
@Configuration
//@Import注解可以导入其他的配置类
@Import(JeeConfig2.class)
public class JeeConfig {
//@Bean注解表示这个方法是一个可以返回一个实例的方法 从而可以在ApplicationContext类中使用
@Bean
public User getUser(){
return new User();
}
@Bean
public Address address(){
return new Address();
}
}
使用配置类,我们获得实例的方法同上述两种有一点细微的区别
public class MyTest {
@Test
public void test(){
//new的是AnnotationConfigApplicationContext这个类 参数变成了我们定义的配置类
ApplicationContext context = new AnnotationConfigApplicationContext(JeeConfig.class);
User getUser = (User) context.getBean("getUser");
System.out.println(getUser);
System.out.println("1111111111111");
System.out.println((Address)context.getBean("address"));
}
}
事务在我们的业务中十分重要 不容马虎 有关数据库的操作 必须使用事务才能保证其安全
事务的四个特性 ACID
1、原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
2、一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。举例来说,假设用户A和用户B两者的钱加起来一共是1000,那么不管A和B之间如何转账、转几次账,事务结束后两个用户的钱相加起来应该还得是1000,这就是事务的一致性。
3、隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如同时操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
4、持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务已经正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成。否则的话就会造成我们虽然看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。这是不允许的。
在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="addUser"/>
<tx:method name="deleteUser"/>
<tx:method name="query"/>
<tx:method name="update"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="txPointCut" expression="execution(* com.Jee.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>